Overview

Packages

  • None
  • Vcs

Classes

  • Horde_Vcs
  • Horde_Vcs_Base
  • Horde_Vcs_Cvs
  • Horde_Vcs_Directory_Base
  • Horde_Vcs_Directory_Cvs
  • Horde_Vcs_Directory_Git
  • Horde_Vcs_Directory_Rcs
  • Horde_Vcs_Directory_Svn
  • Horde_Vcs_File_Base
  • Horde_Vcs_File_Cvs
  • Horde_Vcs_File_Git
  • Horde_Vcs_File_Rcs
  • Horde_Vcs_File_Svn
  • Horde_Vcs_Git
  • Horde_Vcs_Log_Base
  • Horde_Vcs_Log_Cvs
  • Horde_Vcs_Log_Git
  • Horde_Vcs_Log_Rcs
  • Horde_Vcs_Log_Svn
  • Horde_Vcs_Patchset
  • Horde_Vcs_Patchset_Base
  • Horde_Vcs_Patchset_Cvs
  • Horde_Vcs_Patchset_Git
  • Horde_Vcs_Patchset_Svn
  • Horde_Vcs_QuickLog_Base
  • Horde_Vcs_QuickLog_Cvs
  • Horde_Vcs_QuickLog_Git
  • Horde_Vcs_QuickLog_Rcs
  • Horde_Vcs_QuickLog_Svn
  • Horde_Vcs_Rcs
  • Horde_Vcs_Svn
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Horde_Vcs_Rcs implementation.
  4:  *
  5:  * Copyright 2004-2007 Jeff Schwentner <jeffrey.schwentner@lmco.com>
  6:  *
  7:  * @author  Jeff Schwentner <jeffrey.schwentner@lmco.com>
  8:  * @author  Chuck Hagenbuch <chuck@horde.org>
  9:  * @package Vcs
 10:  */
 11: class Horde_Vcs_Rcs extends Horde_Vcs_Base
 12: {
 13:     /**
 14:      * The current driver.
 15:      *
 16:      * @var string
 17:      */
 18:     protected $_driver = 'Rcs';
 19: 
 20:     /**
 21:      * TODO
 22:      */
 23:     public function isValidRevision($rev)
 24:     {
 25:         return $rev && preg_match('/^\d+\.(\d+\.)*\d+$/', $rev);
 26:     }
 27: 
 28:     /**
 29:      * Create a range of revisions between two revision numbers.
 30:      *
 31:      * @param Horde_Vcs_File_Rcs $file  The desired file.
 32:      * @param string $r1                The initial revision.
 33:      * @param string $r2                The ending revision.
 34:      *
 35:      * @return array  The revision range, or empty if there is no straight
 36:      *                line path between the revisions.
 37:      */
 38:     public function getRevisionRange(Horde_Vcs_File_Base $file, $r1, $r2)
 39:     {
 40:         switch ($this->cmp($r1, $r2)) {
 41:         case 0:
 42:             return array();
 43: 
 44:         case 1:
 45:             $curr = $this->prev($r1);
 46:             $stop = $this->prev($r2);
 47:             $flip = true;
 48:             break;
 49: 
 50:         case -1:
 51:             $curr = $r2;
 52:             $stop = $r1;
 53:             $flip = false;
 54:             break;
 55:         }
 56: 
 57:         $ret_array = array();
 58: 
 59:         do {
 60:             $ret_array[] = $curr;
 61:             $curr = $this->prev($curr);
 62:             if ($curr === $stop) {
 63:                 return ($flip) ? array_reverse($ret_array) : $ret_array;
 64:             }
 65:         } while (!is_null($curr) && ($this->cmp($curr, $stop) != -1));
 66: 
 67:         return array();
 68:     }
 69: 
 70:     /**
 71:      * TODO
 72:      */
 73:     public function getFile($filename, $opts = array())
 74:     {
 75:         $filename = ltrim($filename, '/');
 76:         $fname = $filename . ',v';
 77:         if (!@is_file($this->sourceroot . '/' . $fname)) {
 78:             throw new Horde_Vcs_Exception(sprintf('File "%s" not found', $filename));
 79:         }
 80:         return parent::getFile($fname, $opts);
 81:     }
 82: 
 83:     /**
 84:      * Checks an RCS file in with a specified change log.
 85:      *
 86:      * @param string $filepath    Location of file to check in.
 87:      * @param string $message     Log of changes since last version.
 88:      * @param string $user        The user name to use for the check in.
 89:      * @param boolean $newBinary  Does the change involve binary data?
 90:      *
 91:      * @return string  The new revision number on success.
 92:      */
 93:     public function ci($filepath, $message, $user = null, $newBinary = false)
 94:     {
 95:         putenv('LOGNAME=' . ($user ? $user : 'guest'));
 96: 
 97:         $ci_cmd = $this->getPath('ci') . ' ' . escapeshellarg($filepath) . ' 2>&1';
 98:         $rcs_cmd = $this->getPath('rcs') . ' -i -kb ' . escapeshellarg($filepath) . ' 2>&1';
 99:         $output = '';
100: 
101:         $message_lines = explode("\n", $message);
102: 
103:         $pipe_def = array(0 => array("pipe", 'r'),
104:                           1 => array("pipe", 'w'));
105: 
106:         $process = proc_open($newBinary ? $rcs_cmd : $ci_cmd, $pipe_def, $pipes);
107:         if (is_resource($process)) {
108:             foreach ($message_lines as $line) {
109:                 if ($line == '.\n') {
110:                     $line = '. \n';
111:                 }
112:                 fwrite($pipes[0], $line);
113:             }
114: 
115:             fwrite($pipes[0], "\n.\n");
116:             fclose($pipes[0]);
117: 
118:             while (!feof($pipes[1])) {
119:                 $output .= fread($pipes[1], 8192);
120:             }
121:             fclose($pipes[1]);
122:             proc_close($process);
123:         } else {
124:             throw new Horde_Vcs_Exception('Failed to open pipe in ci()');
125:         }
126: 
127:         if ($newBinary) {
128:             exec($ci_cmd . ' 2>&1', $return_array, $retval);
129: 
130:             if ($retval) {
131:                 throw new Horde_Vcs_Exception("Unable to spawn ci on $filepath from ci()");
132:             } else {
133:                 foreach ($return_array as $line) {
134:                     $output .= $line;
135:                 }
136:             }
137:         }
138: 
139:         $rev_start = strpos($output, 'new revision: ');
140: 
141:         // If no new revision, see if this is an initial checkin.
142:         if ($rev_start === false) {
143:             $rev_start = strpos($output, 'initial revision: ');
144:             $rev_end = strpos($output, ' ', $rev_start);
145:         } else {
146:             $rev_end = strpos($output, ';', $rev_start);
147:         }
148: 
149:         if ($rev_start !== false && $rev_end !== false) {
150:             $rev_start += 14;
151:             return substr($output, $rev_start, $rev_end - $rev_start);
152:         } else {
153:             unlock($filepath);
154:             $temp_pos = strpos($output, 'file is unchanged');
155:             if ($temp_pos !== false) {
156:                 throw new Horde_Vcs_Exception('Check-in Failure: ' . basename($filepath) . ' has not been modified');
157:             } else {
158:                 throw new Horde_Vcs_Exception("Failed to checkin $filepath, $ci_cmd, $output");
159:             }
160:         }
161:     }
162: 
163:     /**
164:      * Checks the locks on a CVS/RCS file.
165:      *
166:      * @param string $filepath    Location of file.
167:      * @param string &$locked_by  Returns the username holding the lock.
168:      *
169:      * @return boolean  True on success.
170:      */
171:     public function isLocked($filepath, &$locked_by)
172:     {
173:         $cmd = $this->getPath('rlog') . ' -L ' . escapeshellarg($filepath);
174: 
175:         exec($cmd . ' 2>&1', $return_array, $retval);
176: 
177:         if ($retval) {
178:             throw new Horde_Vcs_Exception("Unable to spawn rlog on $filepath from isLocked()");
179:         } else {
180:             $output = '';
181: 
182:             foreach ($return_array as $line) {
183:                 $output .= $line;
184:             }
185: 
186:             $start_name = strpos($output, 'locked by: ');
187:             $end_name = strpos($output, ';', $start_name);
188: 
189:             if ($start_name !== false && $end_name !== false) {
190:                 $start_name += 11;
191:                 $locked_by = substr($output, $start_name, $end_name - $start_name);
192:                 return true;
193:             }  elseif (strlen($output) == 0) {
194:                 return false;
195:             } else {
196:                 throw new Horde_Vcs_Exception('Failure running rlog in isLocked()');
197:             }
198:         }
199:     }
200: 
201:     /**
202:      * Locks a CVS/RCS file.
203:      *
204:      * @param string $filepath  Location of file.
205:      * @param string $user      User name to lock the file with
206:      *
207:      * @return boolean  True on success.
208:      */
209:     public function lock($filepath, $user = null)
210:     {
211:         // Get username for RCS tag.
212:         if ($user) {
213:             putenv('LOGNAME=' . $user);
214:         } else {
215:             putenv('LOGNAME=guest');
216:         }
217: 
218:         $cmd = $this->getPath('rcs') . ' -l ' . escapeshellarg($filepath);
219:         exec($cmd . ' 2>&1', $return_array, $retval);
220: 
221:         if ($retval) {
222:             throw new Horde_Vcs_Exception('Failed to spawn rcs ("' . $cmd . '") on "' . $filepath . '" (returned ' . $retval . ')');
223:         } else {
224:             $output = '';
225:             foreach ($return_array as $line) {
226:                 $output .= $line;
227:             }
228: 
229:             $locked_pos = strpos($output, 'locked');
230:             if ($locked_pos !== false) {
231:                 return true;
232:             } else {
233:                 throw new Horde_Vcs_Exception('Failed to lock "' . $filepath . '" (Ran "' . $cmd . '", got return code ' . $retval . ', output: ' . $output . ')');
234:             }
235:         }
236:     }
237: 
238:     /**
239:      * Unlocks a CVS/RCS file.
240:      *
241:      * @param string $filepath  Location of file.
242:      * @param string $user      User name to unlock the file with
243:      *
244:      * @return boolean  True on success.
245:      */
246:     public function unlock($filepath, $user = null)
247:     {
248:         // Get username for RCS tag.
249:         if ($user) {
250:             putenv('LOGNAME=' . $user);
251:         } else {
252:             putenv('LOGNAME=guest');
253:         }
254: 
255:         $cmd = $this->getPath('rcs') . ' -u ' . escapeshellarg($filepath);
256:         exec($cmd . ' 2>&1', $return_array, $retval);
257: 
258:         if ($retval) {
259:             throw new Horde_Vcs_Exception('Failed to spawn rcs ("' . $cmd . '") on "' . $filepath . '" (returned ' . $retval . ')');
260:         } else {
261:             $output = '';
262: 
263:             foreach ($return_array as $line) {
264:                 $output .= $line;
265:             }
266: 
267:             $unlocked_pos = strpos($output, 'unlocked');
268: 
269:             if ($unlocked_pos !== false) {
270:                 return true;
271:             } else {
272:                 // Already unlocked.
273:                 return true;
274:             }
275:         }
276:     }
277: 
278:     /**
279:      * Given a revision number, remove a given number of portions from
280:      * it. For example, if we remove 2 portions of 1.2.3.4, we are
281:      * left with 1.2.
282:      *
283:      * @param string $val      Input revision.
284:      * @param integer $amount  Number of portions to strip.
285:      *
286:      * @return string  Stripped revision number.
287:      */
288:     public function strip($val, $amount = 1)
289:     {
290:         $this->assertValidRevision($val);
291: 
292:         $pos = 0;
293:         while ($amount-- > 0 && ($pos = strrpos($val, '.')) !== false) {
294:             $val = substr($val, 0, $pos);
295:         }
296: 
297:         return ($pos !== false) ? $val : false;
298:     }
299: 
300:     /**
301:      * Given two revision numbers, this figures out which one is
302:      * greater than the other by stepping along the decimal points
303:      * until a difference is found, at which point a sign comparison
304:      * of the two is returned.
305:      *
306:      * @param string $rev1  Period delimited revision number
307:      * @param string $rev2  Second period delimited revision number
308:      *
309:      * @return integer  1 if the first is greater, -1 if the second if greater,
310:      *                  and 0 if they are equal
311:      */
312:     public function cmp($rev1, $rev2)
313:     {
314:         return strnatcasecmp($rev1, $rev2);
315:     }
316: 
317:     /**
318:      * Return the logical revision before this one. Normally, this
319:      * will be the revision minus one, but in the case of a new
320:      * branch, we strip off the last two decimal places to return the
321:      * original branch point.
322:      *
323:      * @param string $rev  Revision number to decrement.
324:      *
325:      * @return string  Revision number, or null if none could be determined.
326:      */
327:     public function prev($rev)
328:     {
329:         $last_dot = strrpos($rev, '.');
330:         $val = substr($rev, ++$last_dot);
331: 
332:         if (--$val > 0) {
333:             return substr($rev, 0, $last_dot) . $val;
334:         } else {
335:             --$last_dot;
336:             while (--$last_dot) {
337:                 if ($rev[$last_dot] == '.') {
338:                     return substr($rev, 0, $last_dot);
339:                 } elseif (is_null($rev[$last_dot])) {
340:                     return null;
341:                 }
342:             }
343:         }
344:     }
345: 
346: }
347: 
API documentation generated by ApiGen