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:  * Version Control generalized library.
  4:  *
  5:  * Copyright 2008-2012 Horde LLC (http://www.horde.org/)
  6:  *
  7:  * See the enclosed file COPYING for license information (LGPL). If you
  8:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  9:  *
 10:  * @package Vcs
 11:  */
 12: abstract class Horde_Vcs_Base
 13: {
 14:     /**
 15:      * The source root of the repository.
 16:      *
 17:      * @var string
 18:      */
 19:     public $sourceroot;
 20: 
 21:     /**
 22:      * Hash with the locations of all necessary binaries.
 23:      *
 24:      * @var array
 25:      */
 26:     protected $_paths = array();
 27: 
 28:     /**
 29:      * Hash caching the parsed users file.
 30:      *
 31:      * @var array
 32:      */
 33:     protected $_users = array();
 34: 
 35:     /**
 36:      * The current driver.
 37:      *
 38:      * @var string
 39:      */
 40:     protected $_driver;
 41: 
 42:     /**
 43:      * If caching is desired, a Horde_Cache object.
 44:      *
 45:      * @var Horde_Cache
 46:      */
 47:     protected $_cache;
 48: 
 49:     /**
 50:      * Driver features.
 51:      *
 52:      * @var array
 53:      */
 54:     protected $_features = array(
 55:         'deleted'   => false,
 56:         'patchsets' => false,
 57:         'branches'  => false,
 58:         'snapshots' => false);
 59: 
 60:     /**
 61:      * Current cache version.
 62:      *
 63:      * @var integer
 64:      */
 65:     protected $_cacheVersion = 5;
 66: 
 67:     /**
 68:      * The available diff types.
 69:      *
 70:      * @var array
 71:      */
 72:     protected $_diffTypes = array('column', 'context', 'ed', 'unified');
 73: 
 74:     /**
 75:      * Constructor.
 76:      */
 77:     public function __construct($params = array())
 78:     {
 79:         $this->_cache = empty($params['cache']) ? null : $params['cache'];
 80:         $this->sourceroot = $params['sourceroot'];
 81:         $this->_paths = $params['paths'];
 82:     }
 83: 
 84:     /**
 85:      * Does this driver support the given feature?
 86:      *
 87:      * @return boolean  True if driver supports the given feature.
 88:      */
 89:     public function hasFeature($feature)
 90:     {
 91:         return !empty($this->_features[$feature]);
 92:     }
 93: 
 94:     /**
 95:      * Validation function to ensure that a revision string is of the right
 96:      * form.
 97:      *
 98:      * @param mixed $rev  The purported revision string.
 99:      *
100:      * @return boolean  True if a valid revision string.
101:      */
102:     abstract public function isValidRevision($rev);
103: 
104:     /**
105:      * Throw an exception if the revision number isn't valid.
106:      *
107:      * @param mixed $rev  The revision number.
108:      *
109:      * @throws Horde_Vcs_Exception
110:      */
111:     public function assertValidRevision($rev)
112:     {
113:         if (!$this->isValidRevision($rev)) {
114:             throw new Horde_Vcs_Exception('Invalid revision number "' . $rev . '"');
115:         }
116:     }
117: 
118:     /**
119:      * Given two revisions, this figures out which one is greater than the
120:      * the other.
121:      *
122:      * @param string $rev1  Revision string.
123:      * @param string $rev2  Second revision string.
124:      *
125:      * @return integer  1 if the first is greater, -1 if the second if greater,
126:      *                  and 0 if they are equal
127:      */
128:     public function cmp($rev1, $rev2)
129:     {
130:         return strcasecmp($rev1, $rev2);
131:     }
132: 
133:     /**
134:      * TODO
135:      */
136:     public function isFile($where)
137:     {
138:         return true;
139:     }
140: 
141:     /**
142:      * Create a range of revisions between two revision numbers.
143:      *
144:      * @param Horde_Vcs_File_Base $file  The desired file.
145:      * @param string $r1                 The initial revision.
146:      * @param string $r2                 The ending revision.
147:      *
148:      * @return array  The revision range, or empty if there is no straight
149:      *                line path between the revisions.
150:      */
151:     public function getRevisionRange(Horde_Vcs_File_Base $file, $r1, $r2)
152:     {
153:         return array();
154:     }
155: 
156:     /**
157:      * Obtain the differences between two revisions of a file.
158:      *
159:      * @param Horde_Vcs_File_Base $file  The desired file.
160:      * @param string $rev1               Original revision number to compare
161:      *                                   from.
162:      * @param string $rev2               New revision number to compare against.
163:      * @param array $opts                The following optional options:
164:      *                                   - 'human': (boolean) DEFAULT: false
165:      *                                   - 'num': (integer) DEFAULT: 3
166:      *                                   - 'type': (string) DEFAULT: 'unified'
167:      *                                   - 'ws': (boolean) DEFAULT: true
168:      *
169:      * @return string  The diff string.
170:      * @throws Horde_Vcs_Exception
171:      */
172:     public function diff(Horde_Vcs_File_Base $file, $rev1, $rev2,
173:                          $opts = array())
174:     {
175:         $opts = array_merge(array(
176:             'num' => 3,
177:             'type' => 'unified',
178:             'ws' => true
179:         ), $opts);
180: 
181:         if ($rev1) {
182:             $this->assertValidRevision($rev1);
183:         }
184: 
185:         $diff = $this->_diff($file, $rev1, $rev2, $opts);
186:         return empty($opts['human'])
187:             ? $diff
188:             : $this->_humanReadableDiff($diff);
189:     }
190: 
191:     /**
192:      * Obtain the differences between two revisions of a file.
193:      *
194:      * @param Horde_Vcs_File_Base $file  The desired file.
195:      * @param string $rev1               Original revision number to compare
196:      *                                   from.
197:      * @param string $rev2               New revision number to compare against.
198:      * @param array $opts                The following optional options:
199:      *                                   - 'num': (integer) DEFAULT: 3
200:      *                                   - 'type': (string) DEFAULT: 'unified'
201:      *                                   - 'ws': (boolean) DEFAULT: true
202:      *
203:      * @return string|boolean  False on failure, or a string containing the
204:      *                         diff on success.
205:      */
206:     protected function _diff(Horde_Vcs_File_Base $file, $rev1, $rev2, $opts)
207:     {
208:         return false;
209:     }
210: 
211:     /**
212:      * Obtain a tree containing information about the changes between
213:      * two revisions.
214:      *
215:      * @param array $raw  An array of lines of the raw unified diff,
216:      *                    normally obtained through Horde_Vcs_Diff::get().
217:      *
218:      * @return array  @TODO
219:      */
220:     protected function _humanReadableDiff($raw)
221:     {
222:         $ret = array();
223: 
224:         /* Hold the left and right columns of lines for change
225:          * blocks. */
226:         $cols = array(array(), array());
227:         $state = 'empty';
228: 
229:         /* Iterate through every line of the diff. */
230:         foreach ($raw as $line) {
231:             /* Look for a header which indicates the start of a diff
232:              * chunk. */
233:             if (preg_match('/^@@ \-([0-9]+).*\+([0-9]+).*@@(.*)/', $line, $regs)) {
234:                 /* Push any previous header information to the return
235:                  * stack. */
236:                 if (isset($data)) {
237:                     $ret[] = $data;
238:                 }
239:                 $data = array('type' => 'header', 'oldline' => $regs[1],
240:                               'newline' => $regs[2], 'contents'> array());
241:                 $data['function'] = isset($regs[3]) ? $regs[3] : '';
242:                 $state = 'dump';
243:             } elseif ($state != 'empty') {
244:                 /* We are in a chunk, so split out the action (+/-)
245:                  * and the line. */
246:                 preg_match('/^([\+\- ])(.*)/', $line, $regs);
247:                 if (count($regs) > 2) {
248:                     $action = $regs[1];
249:                     $content = $regs[2];
250:                 } else {
251:                     $action = ' ';
252:                     $content = '';
253:                 }
254: 
255:                 if ($action == '+') {
256:                     /* This is just an addition line. */
257:                     if ($state == 'dump' || $state == 'add') {
258:                         /* Start adding to the addition stack. */
259:                         $cols[0][] = $content;
260:                         $state = 'add';
261:                     } else {
262:                         /* This is inside a change block, so start
263:                          * accumulating lines. */
264:                         $state = 'change';
265:                         $cols[1][] = $content;
266:                     }
267:                 } elseif ($action == '-') {
268:                     /* This is a removal line. */
269:                     $state = 'remove';
270:                     $cols[0][] = $content;
271:                 } else {
272:                     /* An empty block with no action. */
273:                     switch ($state) {
274:                     case 'add':
275:                         $data['contents'][] = array('type' => 'add', 'lines' => $cols[0]);
276:                         break;
277: 
278:                     case 'remove':
279:                         /* We have some removal lines pending in our
280:                          * stack, so flush them. */
281:                         $data['contents'][] = array('type' => 'remove', 'lines' => $cols[0]);
282:                         break;
283: 
284:                     case 'change':
285:                         /* We have both remove and addition lines, so
286:                          * this is a change block. */
287:                         $data['contents'][] = array('type' => 'change', 'old' => $cols[0], 'new' => $cols[1]);
288:                         break;
289:                     }
290:                     $cols = array(array(), array());
291:                     $data['contents'][] = array('type' => 'empty', 'line' => $content);
292:                     $state = 'dump';
293:                 }
294:             }
295:         }
296: 
297:         /* Just flush any remaining entries in the columns stack. */
298:         switch ($state) {
299:         case 'add':
300:             $data['contents'][] = array('type' => 'add', 'lines' => $cols[0]);
301:             break;
302: 
303:         case 'remove':
304:             /* We have some removal lines pending in our stack, so
305:              * flush them. */
306:             $data['contents'][] = array('type' => 'remove', 'lines' => $cols[0]);
307:             break;
308: 
309:         case 'change':
310:             /* We have both remove and addition lines, so this is a
311:              * change block. */
312:             $data['contents'][] = array('type' => 'change', 'old' => $cols[0], 'new' => $cols[1]);
313:             break;
314:         }
315: 
316:         if (isset($data)) {
317:             $ret[] = $data;
318:         }
319: 
320:         return $ret;
321:     }
322: 
323:     /**
324:      * Return the list of available diff types.
325:      *
326:      * @return array  The list of available diff types for use with get().
327:      */
328:     public function availableDiffTypes()
329:     {
330:         return $this->_diffTypes;
331:     }
332: 
333:     /**
334:      * Returns the location of the specified binary.
335:      *
336:      * @param string $binary  An external program name.
337:      *
338:      * @return boolean|string  The location of the external program or false
339:      *                         if it wasn't specified.
340:      */
341:     public function getPath($binary)
342:     {
343:         if (isset($this->_paths[$binary])) {
344:             return $this->_paths[$binary];
345:         }
346: 
347:         return false;
348:     }
349: 
350:     /**
351:      * Parse the users file, if present in the sourceroot, and return
352:      * a hash containing the requisite information, keyed on the
353:      * username, and with the 'desc', 'name', and 'mail' values inside.
354:      *
355:      * @return array  User data.
356:      * @throws Horde_Vcs_Exception
357:      */
358:     public function getUsers($usersfile)
359:     {
360:         /* Check that we haven't already parsed users. */
361:         if (isset($this->_users[$usersfile])) {
362:             return $this->_users[$usersfile];
363:         }
364: 
365:         if (!@is_file($usersfile) ||
366:             !($fl = @fopen($usersfile, VC_WINDOWS ? 'rb' : 'r'))) {
367:             throw new Horde_Vcs_Exception('Invalid users file: ' . $usersfile);
368:         }
369: 
370:         /* Discard the first line, since it'll be the header info. */
371:         fgets($fl, 4096);
372: 
373:         /* Parse the rest of the lines into a hash, keyed on username. */
374:         $users = array();
375:         while ($line = fgets($fl, 4096)) {
376:             if (!preg_match('/^\s*$/', $line) &&
377:                 preg_match('/^(\w+)\s+(.+)\s+([\w\.\-\_]+@[\w\.\-\_]+)\s+(.*)$/', $line, $regs)) {
378:                 $users[$regs[1]] = array(
379:                     'name' => trim($regs[2]),
380:                     'mail' => trim($regs[3]),
381:                     'desc' => trim($regs[4])
382:                 );
383:             }
384:         }
385: 
386:         $this->_users[$usersfile] = $users;
387: 
388:         return $users;
389:     }
390: 
391:     /**
392:      * Returns a directory object.
393:      *
394:      * @param string $where        Path to the directory.
395:      * @param array $opts          Any additional options (depends on driver):
396:      *                             - 'showattic': (boolean) Parse any Attic/
397:      *                               sub-directory contents too.
398:      *                             - 'rev': (string) Generate directory list for
399:      *                               a certain branch or revision.
400:      *
401:      * @return Horde_Vcs_Directory_Base  A directory object.
402:      */
403:     public function getDirectory($where, $opts = array())
404:     {
405:         $class = 'Horde_Vcs_Directory_' . $this->_driver;
406:         return new $class($this, $where, $opts);
407:     }
408: 
409:     /**
410:      * Function which returns a file pointing to the head of the requested
411:      * revision of a file.
412:      *
413:      * @param string $fullname  Fully qualified pathname of the desired file
414:      *                          to checkout.
415:      * @param string $rev       Revision number to check out.
416:      *
417:      * @return resource  A stream pointer to the head of the checkout.
418:      */
419:     public function checkout($fullname, $rev)
420:     {
421:         return null;
422:     }
423: 
424:     /**
425:      * TODO
426:      *
427:      * $opts:
428:      * 'branch' - (string)
429:      */
430:     public function getFile($filename, $opts = array())
431:     {
432:         $class = 'Horde_Vcs_File_' . $this->_driver;
433: 
434:         ksort($opts);
435:         $cacheId = implode('|', array($class, $this->sourceroot, $filename, serialize($opts), $this->_cacheVersion));
436:         $fetchedFromCache = false;
437: 
438:         if (!empty($this->_cache)) {
439:             // TODO: Can't use filemtime() - Git bare repos contain no files
440:             if (file_exists($filename)) {
441:                 $ctime = time() - filemtime($filename);
442:             } else {
443:                 $ctime = 60;
444:             }
445:             if ($this->_cache->exists($cacheId, $ctime)) {
446:                 $ob = unserialize($this->_cache->get($cacheId, $ctime));
447:                 $fetchedFromCache = true;
448:             }
449:         }
450: 
451:         if (empty($ob) || !$ob) {
452:             $ob = new $class($filename, $opts);
453: 
454:         }
455:         $ob->setRepository($this);
456: 
457:         if (!empty($this->_cache) && !$fetchedFromCache) {
458:             $this->_cache->set($cacheId, serialize($ob));
459:         }
460: 
461:         return $ob;
462:     }
463: 
464:     /**
465:      * TODO
466:      *
467:      * @param array $opts  Options:
468:      * <pre>
469:      * 'file' - (string) TODO
470:      * 'range' - (array) TODO
471:      * </pre>
472:      *
473:      * @return Horde_Vcs_Patchset  Patchset object.
474:      */
475:     public function getPatchset($opts = array())
476:     {
477:         $class = 'Horde_Vcs_Patchset_' . $this->_driver;
478: 
479:         if (!is_array($opts)) { $opts = array(); }
480:         ksort($opts);
481:         $cacheId = implode('|', array($class, $this->sourceroot, serialize($opts), $this->_cacheVersion));
482: 
483:         if (!empty($this->_cache)) {
484:             if (isset($opts['file']) && file_exists($opts['file'])) {
485:                 $ctime = time() - filemtime($opts['file']);
486:             } else {
487:                 $ctime = 60;
488:             }
489: 
490:             if ($this->_cache->exists($cacheId, $ctime)) {
491:                 return unserialize($this->_cache->get($cacheId, $ctime));
492:             }
493:         }
494: 
495:         $ob = new $class($this, $opts);
496: 
497:         if (!empty($this->_cache)) {
498:             $this->_cache->set($cacheId, serialize($ob));
499:         }
500: 
501:         return $ob;
502:     }
503: 
504:     /**
505:      * TODO
506:      */
507:     public function annotate($fileob, $rev)
508:     {
509:         return array();
510:     }
511: 
512:     /**
513:      * Returns an abbreviated form of the revision, for display.
514:      *
515:      * @param string $rev  The revision string.
516:      *
517:      * @return string  The abbreviated string.
518:      */
519:     public function abbrev($rev)
520:     {
521:         return $rev;
522:     }
523: 
524:     /**
525:      * @TODO ?
526:      */
527:     public function getDefaultBranch()
528:     {
529:         return 'HEAD';
530:     }
531: }
532: 
API documentation generated by ApiGen