php8 fixes
[web.mtrack] / MTrack / SCM / Git / Repo.php
1 <?php
2 require_once 'MTrack/Repo.php';
3
4
5 class MTrack_SCM_Git_Repo extends MTrack_Repo 
6 {
7   protected $branches = null;
8   protected $tags = null;
9   public $gitdir = null;
10
11   public $debug = false;
12   
13   var $project_id;
14
15   public function getSCMMetaData() {
16     return array(
17       'name' => 'Git',
18       'tools' => array('git'),
19     );
20   }
21
22     function __construct($ar = null) {
23         
24         parent::__construct($ar);
25         if ($ar !== null) {
26           /* transparently handle bare vs. non bare repos */
27             $this->gitdir = $this->repopath;
28             if (is_dir("$this->repopath/.git")) {
29                 $this->gitdir .= "/.git";
30             }
31         }
32     }
33
34   function getServerURL() {
35     $url = parent::getServerURL();
36     if ($url) return $url;
37     $url = MTrackConfig::get('repos', 'serverurl');
38     if ($url) {
39       return "$url:" . $this->getBrowseRootName();
40     }
41     return null;
42   }
43
44   public function reconcileRepoSettings(MTrackSCM $r = null) {
45     if ($r == null) {
46       $r = $this;
47     }
48
49     if (!is_dir($r->repopath)) {
50       $userdata = MTrackAuth::getUserData(MTrackAuth::whoami());
51       $who = $userdata['email'];
52       putenv("GIT_AUTHOR_NAME=$who");
53       putenv("GIT_AUTHOR_EMAIL=$who");
54
55       if ($r->clonedfrom) {
56         $S = MTrack_Repo::loadById($r->clonedfrom);
57
58         $stm = MTrackSCM::run('git', 'read',
59             array('clone', '--bare', $S->repopath, $r->repopath));
60         $out = stream_get_contents($stm);
61         if (pclose($stm)) {
62           throw new Exception("git init failed: $out");
63         }
64
65       } else {
66         /* a little peculiar, but bear with it.
67          * We need to have a bare repo so that git doesn't mess around
68          * trying to deal with a checkout in the repo dir.
69          * So we need to create two repos; one bare, one not bare.
70          * We populate the non-bare repo with a dummy file just to have
71          * something to commit, then push non-bare -> bare, and remove non-bare.
72          */
73
74         $stm = MTrackSCM::run('git', 'read',
75             array('init', '--bare', $r->repopath));
76         $out = stream_get_contents($stm);
77         if (pclose($stm)) {
78           throw new Exception("git init failed: $out");
79         }
80
81         $alt = "$r->repopath.MTRACKINIT";
82
83         $stm = MTrackSCM::run('git', 'read',
84             array('init', $alt));
85         $out = stream_get_contents($stm);
86         if (pclose($stm)) {
87           throw new Exception("git init failed: $out");
88         }
89
90         $dir = getcwd();
91         chdir($alt);
92
93         file_put_contents("$alt/.gitignore", "#\n");
94         $stm = MTrackSCM::run('git', 'read',
95             array('add', '.gitignore'));
96         $out = stream_get_contents($stm);
97         if (pclose($stm)) {
98           throw new Exception("git add .gitignore failed: $out");
99         }
100         $stm = MTrackSCM::run('git', 'read',
101             array('commit', '-a', '-m', 'init'));
102         $out = stream_get_contents($stm);
103         if (pclose($stm)) {
104           throw new Exception("git commit failed: $out");
105         }
106         $stm = MTrackSCM::run('git', 'read',
107             array('push', $r->repopath, 'master'));
108         $out = stream_get_contents($stm);
109         if (pclose($stm)) {
110           throw new Exception("git push failed: $out");
111         }
112         chdir($dir);
113         system("rm -rf $alt");
114       }
115
116       $php = MTrackConfig::get('tools', 'php');
117       $hook = realpath(dirname(__FILE__) . '/../../bin/git-commit-hook');
118       $conffile = realpath(MTrackConfig::getLocation());
119       foreach (array('pre', 'post') as $step) {
120         $script = <<<HOOK
121 #!/bin/sh
122 exec $php $hook $step $conffile
123
124 HOOK;
125         $target = "$r->repopath/hooks/$step-receive";
126         if (file_put_contents("$target.mtrack", $script)) {
127           chmod("$target.mtrack", 0755);
128           rename("$target.mtrack", $target);
129         }
130       }
131     }
132
133     system("chmod -R 02777 $r->repopath");
134   }
135
136   function canFork() {
137     return true;
138   }
139
140
141   public function getBranches()
142   {
143     if ($this->branches !== null) {
144          return $this->branches;
145     }
146     $this->branches = array();
147     $fp = $this->git('branch', '--no-color', '--verbose');
148     while ($line = fgets($fp)) {
149       // * master 61e7e7d oneliner
150       $line = substr($line, 2);
151       list($branch, $rev) = preg_split('/\s+/', $line);
152       $this->branches[$branch] = $rev;
153     }
154     $fp = null;
155     return $this->branches;
156   }
157
158   public function getTags()
159   {
160     if ($this->tags !== null) {
161       return $this->tags;
162     }
163     $this->tags = array();
164     $fp = $this->git('tag');
165     while ($line = fgets($fp)) {
166       $line = trim($line);
167       $this->tags[$line] = $line;
168     }
169     $fp = null;
170     return $this->tags;
171   }
172
173   public function readdir($path, $object = null, $ident = null)
174   {
175     $res = array();
176
177     if ($object === null) {
178       $object = 'branch';
179       $ident = 'master';
180     }
181     $rev = $this->resolveRevision(null, $object, $ident);
182
183     if (strlen($path)) {
184       $path = rtrim($path, '/') . '/';
185     }
186
187     $fp = $this->git('ls-tree', $rev, $path);
188
189     $dirs = array();
190     require_once 'MTrack/SCM/Git/File.php';
191     while ($line = fgets($fp)) {
192         // blob = file, tree = dir..
193         list($mode, $type, $hash, $name) = preg_split("/\s+/", $line);
194         //echo '<PRE>';echo $line ."\n</PRE>";
195         $res[] = new MTrack_SCM_Git_File($this, "$name", $rev, $type == 'tree', $hash);
196     }
197     return $res;
198   }
199
200   public function file($path, $object = null, $ident = null)
201   {
202     if ($object == null) {
203       $branches = $this->getBranches();
204       if (isset($branches['master'])) {
205         $object = 'branch';
206         $ident = 'master';
207       } else {
208         // fresh/empty repo
209         return null;
210       }
211     }
212     $rev = $this->resolveRevision(null, $object, $ident);
213     require_once 'MTrack/SCM/Git/File.php';
214     return new MTrack_SCM_Git_File($this, $path, $rev);
215   }
216     
217     /**
218      * 
219      * @param  string path (can be empty - eg. '')
220      * @param  {number|date} limit  how many to fetch
221      *
222      *      -- prefered args:
223      *          
224      * @param  {string} object = eg. rev|tag|branch  (use 'rev' here and ident=HASH to retrieve a speific revision
225      * @param  {string} ident = 
226      *
227      * Example:
228      *   - fetch on revision?:  '.',1,'rev','xxxxxxxxxx'
229      *
230      *
231      * range... object ='rev' ident ='master..release'
232      * 
233      */
234     public function history($path, $limit = null, $object = null, $ident = null)
235     {
236         
237         
238         $res = array();
239         
240         $args = array();
241         if ($object !== null) {
242             $rev = $this->resolveRevision(null, $object, $ident); // from scm...
243             $args[] = "$rev";
244         } else {
245             $args[] = "master";
246         }
247      
248         if ($limit !== null) {
249             if (is_int($limit)) {
250                 $args[] = "--max-count=$limit";
251             } else if (is_array($limit) && isset($limit[0]) && isset($limit[1])) {
252                 
253                 $args[] = "--skip={$limit[0]} --max-count={$limit[1]}";
254                 
255                 
256             /// oh what a horible hack.. - bad api design here.
257             } else if (is_array($limit) ) {
258                 foreach($limit as $k=>$v) {
259                      
260                     $args[] = ($k == '-') ? $v : ('--'. $k .'='. $v);
261                     
262                 }
263                  
264             } else if (strpos($limit,'..') !== false ) {
265                  $args[] = $limit;
266             } else {   
267                 $args[] = "--since=$limit";
268             }
269         }
270         $args[] = "--no-color";
271         //$args[] = "--name-status";
272         $args[] = "--raw";
273         $args[] = "--no-abbrev";
274         $args[] = "--numstat";
275         $args[] = "--date=rfc";
276         $args[] = "--source"; // show the branch..
277         
278         
279         //echo '<PRE>';print_r($args);echo '</PRE>';
280         $path = ltrim($path, '/');
281         
282         
283         
284         //   print_R(array($args, '--' ,$path));exit;
285         $fp = $this->git('log', $args, '--', $path);
286
287         $commits = array();
288         $commit = null;
289         while (true) {
290           $line = fgets($fp);
291           if ($line === false) { //end of file..
292             if ($commit !== null) {
293               $commits[] = $commit;
294             }
295             break;
296           }
297           if (preg_match("/^commit/", $line)) {
298             if ($commit !== null) {
299               $commits[] = $commit;
300             }
301             $commit = $line;
302             continue;
303           }
304           $commit .= $line;
305         }
306         require_once 'MTrack/SCM/Git/Event.php';
307         foreach ($commits as $commit) {
308             $res[] = MTrack_SCM_Git_Event::newFromCommit($commit, $this);
309         }
310         $fp = null;
311         
312         return $res;
313     }
314
315     public function diff($path, $from = null, $to = null)
316     {
317         require_once 'MTrack/SCMFile.php';
318         
319         if ($path instanceof MTrackSCMFile) {
320             if ($from === null) {
321                 $from = $path->rev;
322             }
323             $path = $path->name;
324             
325         }
326         // if it's a file event.. we are even lucker..
327         if ($path instanceof MTrackSCMFileEvent) {
328             return $this->git('log', '--max-count=1', '--format=format:', '--patch', $from, '--', $path->name);
329             
330         }
331         
332         
333         if ($to !== null) {
334           return $this->git('diff', "$from..$to", '--', $path);
335         }
336         return $this->git('diff', "$from^..$from", '--', $path);
337       }
338
339     public function getWorkingCopy()
340     {
341         require_once 'MTrack/SCM/Git/WorkingCopy.php';
342         return new MTrack_SCM_Git_WorkingCopy($this);
343     }
344
345   public function getRelatedChanges($revision) // pretty nasty.. could end up with 1000's of changes..
346   {
347     $parents = array();
348     $kids = array();
349
350     $fp = $this->git('rev-parse', "$revision^");
351     while (($line = fgets($fp)) !== false) {
352       $parents[] = trim($line);
353     }
354
355     // Ugh!: http://stackoverflow.com/questions/1761825/referencing-the-child-of-a-commit-in-git
356     $fp = $this->git('rev-list', '--all', '--parents');
357     while (($line = fgets($fp)) !== false) {
358       $hashes = preg_split("/\s+/", $line);
359       $kid = array_shift($hashes);
360       if (in_array($revision, $hashes)) {
361         $kids[] = $kid;
362       }
363     }
364
365     return array($parents, $kids);
366   }
367
368     function git()
369     {
370            $args = func_get_args();
371           $a = array(
372             "--git-dir=$this->gitdir"
373           );
374           
375           
376           
377           if ($this->gitdir != $this->repopath) {
378           //    print_r(array($this->gitdir , $this->repopath));
379               
380             //$a[] = "--work-tree=$this->repopath";
381           }
382           foreach ($args as $arg) {
383               if (is_array($arg)) {
384                   $a = array_merge($a, $arg);
385                   continue;
386               }
387               $a[] = $arg;
388           }
389           if ($this->debug) { 
390               var_dump('git ' . join (' ' , $a));
391             //  die("oops");
392           }
393           //echo "git " . implode(" " , $a) . "\n";
394           return MTrackSCM::run('git', 'read', $a);
395     }
396   
397    
398     function commitLogToEvent($str) {
399         require_once 'MTrack/SCM/Git/Event.php';
400         return  MTrack_SCM_Git_Event::newFromCommit($str,$this);
401     }
402
403   
404 }
405
406