final move of files
[web.mtrack] / MTrack / SCM.php
1 <?php
2
3 require_once 'MTrack/Repo.php';
4
5 abstract class MTrackSCM {
6   static $repos = array();
7
8   public $repopath = '';
9   
10   static function factory(&$repopath) {
11     /* [ / owner type rest ] */
12     $bits = explode('/', $repopath, 4);
13   //  print_r($bits);
14     if (count($bits) < 3) {
15       throw new Exception("Invalid repo $repopath");
16     }
17     array_shift($bits);
18     list($owner, $type) = $bits;
19     $repo = "$owner/$type";
20
21     $r = MTrackRepo::loadByName($repo);
22     if (!$r) {
23       throw new Exception("invalid repo $repo");
24     }
25     $repopath = isset($bits[2]) ? $bits[2] : '';
26     $r->repopath = $repopath;
27     return $r;
28   }
29
30   /** Returns an array keyed by possible branch names.
31    * The data associated with the branches is implementation
32    * defined.
33    * If the SCM does not have a concept of first-class branch
34    * objects, this function returns null */
35   abstract public function getBranches();
36
37   /** Returns an array keyed by possible tag names.
38    * The data associated with the tags is implementation
39    * defined.
40    * If the SCM does not have a concept of first-class tag
41    * objects, this function returns null */
42   abstract public function getTags();
43
44   /** Enumerates the files/dirs that are present in the specified
45    * location of the repository that match the specified revision,
46    * branch or tag information.  If no revision, branch or tag is
47    * specified, then the appropriate default is assumed.
48    *
49    * The second and third parameters are optional; the second
50    * parameter is one of 'rev', 'branch', or 'tag', and if specifed
51    * the third parameter must be the corresponding revision, branch
52    * or tag identifier.
53    *
54    * The return value is an array of MTrackSCMFile objects present
55    * at that location/revision of the repository.
56    */
57   abstract public function readdir($path, $object = null, $ident = null);
58
59   /** Queries information on a specific file in the repository.
60    *
61    * Parameters are as for readdir() above.
62    *
63    * This function returns a single MTrackSCMFile for the location
64    * in question.
65    */
66   abstract public function file($path, $object = null, $ident = null);
67
68   /** Queries history for a particular location in the repo.
69    *
70    * Parameters are as for readdir() above, except that path can be
71    * left unspecified to query the history for the entire repo.
72    *
73    * The limit parameter limits the number of entries returned; it it is
74    * a number, it specifies the number of events, otherwise it is assumed
75    * to be a date in the past; only events since that date will be returned.
76    *
77    * Returns an array of MTrackSCMEvent objects.
78    */
79   abstract public function history($path, $limit = null, $object = null,
80     $ident = null);
81
82   /** Obtain the diff text representing a change to a file.
83    *
84    * You may optionally provide one or two revisions as context.
85    *
86    * If no revisions are passed in, then the change associated
87    * with the location will be assumed.
88    *
89    * If one revision is passed, then the change associated with
90    * that event will be assumed.
91    *
92    * If two revisions are passed, then the difference between
93    * the two events will be assumed.
94    */
95   abstract public function diff($path, $from = null, $to = null);
96
97   /** Determine the next and previous revisions for a given
98    * changeset.
99    *
100    * Returns an array: the 0th element is an array of prior revisions,
101    * and the 1st element is an array of successor revisions.
102    *
103    * There will usually be one prior and one successor revision for a
104    * given change, but some SCMs will return multiples in the case of
105    * merges.
106    */
107   abstract public function getRelatedChanges($revision);
108
109   /** Returns a working copy object for the repo
110    *
111    * The intended purpose is to support wiki page modifications, and
112    * as such, is not meant to be an especially efficient means to do so.
113    */
114   abstract public function getWorkingCopy();
115
116   /** Returns meta information about the SCM type; this is used in the
117    * UI and tooling to let the user know their options.
118    *
119    * Returns an array with the following keys:
120    * 'name' => 'Mercurial', // human displayable name
121    * 'tools' => array('hg'), // list of tools to find during setup
122    */
123   abstract public function getSCMMetaData();
124
125   /** Returns the default 'root' location in the repository.
126    * For SCMs that have a concept of branches, this is the empty string.
127    * For SCMs like SVN, this is the trunk dir */
128   public function getDefaultRoot() {
129     return '';
130   }
131
132   
133     
134   
135   /* takes an MTrackSCM as a parameter because in some bootstrapping
136    * cases, we're actually MTrackRepo and not the end-class.
137    * MTrackRepo calls the end-class method and passes itself in for
138    * context */
139   public function reconcileRepoSettings(MTrackSCM $r) {
140     throw new Exception(
141       "Creating/updating a repo of type $this->scmtype is not implemented");
142   }
143
144   static function makeBreadcrumbs($pi) {
145     if (!strlen($pi)) {
146       $pi = '/';
147     }
148     if ($pi == '/') {
149       $crumbs = array('');
150     } else {
151       $crumbs = explode('/', $pi);
152     }
153     return $crumbs;
154   }
155
156   static function makeDisplayName($data) {
157     $parent = '';
158     $name = '';
159     if (is_object($data)) {
160       $parent = $data->parent;
161       $name = $data->shortname;
162     } else if (is_array($data)) {
163       $parent = $data['parent'];
164       $name = $data['shortname'];
165     }
166     if ($parent) {
167       list($type, $owner) = explode(':', $parent);
168       return "$owner/$name";
169     }
170     return "default/$name";
171   }
172
173   public function getBrowseRootName() {
174     return self::makeDisplayName($this);
175   }
176
177   public function resolveRevision($rev, $object, $ident) {
178     if ($rev !== null) {
179       return $rev;
180     }
181     if ($object === null) {
182       return null;
183     }
184     switch ($object) {
185       case 'rev':
186         $rev = $ident;
187         break;
188       case 'branch':
189         $branches = $this->getBranches();
190         $rev = isset($branches[$ident]) ? $branches[$ident] : null;
191         break;
192       case 'tag':
193         $tags = $this->getTags();
194         $rev = isset($tags[$ident]) ? $tags[$ident] : null;
195         break;
196     }
197     if ($rev === null) {
198       throw new Exception(
199         "don't know which revision to use ($rev,$object,$ident)");
200     }
201     return $rev;
202   }
203
204     /**
205      * was run tool...
206      */
207
208     static function run($toolname, $mode, $args = null)
209     {
210         global $FORKS; //??? why?
211         
212         static $tools; // we cache the lookups... - we could use the config for this... (as per original..)
213                         // but that would only be needed or realy heavily loaded sites.
214         
215         require_once 'System.php';
216         $tool = isset($tools[$toolname]) ? $tools[$toolname] :  System::which($toolname);
217         $tools[$toolname] = $tool;
218         if (empty($tool)) {
219             throw new Exception("Could not find '$toolname'");
220         }
221          
222         $cmd = $tool;
223         $args = is_array($args) ? $args : array();
224         
225         foreach ($args as $arg) {
226             if (!is_array($arg)) {
227                 $cmd .= ' ' . escapeshellarg($arg);
228                 continue;
229             }
230             
231             foreach ($arg as $a) {
232                 $cmd .= ' ' . escapeshellarg($a);
233             }
234         }
235         
236         if (!isset($FORKS[$cmd])) {
237             $FORKS[$cmd] = 0;
238         }
239         $FORKS[$cmd]++;
240         
241         // debugging....
242         if (false) {
243             if (php_sapi_name() == 'cli') {
244                 echo $cmd, "\n";
245             } else {
246                 error_log($cmd);
247                 echo htmlentities($cmd) . "<br>\n";
248             }
249         }
250
251         switch ($mode) {
252             case 'read':   return popen($cmd, 'r');
253             case 'write':  return popen($cmd, 'w');
254             case 'string': return stream_get_contents(popen($cmd, 'r'));
255             case 'proc':
256               $pipedef = array(
257                 0 => array('pipe', 'r'),
258                 1 => array('pipe', 'w'),
259                 2 => array('pipe', 'w'),
260               );
261               $proc = proc_open($cmd, $pipedef, $pipes);
262               return array($proc, $pipes);
263         }
264     }  
265 }