2 require_once 'MTrack/Repo.php';
5 class MTrack_SCM_Git_Repo extends MTrack_Repo
7 protected $branches = null;
8 protected $tags = null;
11 public $debug = false;
13 public function getSCMMetaData() {
16 'tools' => array('git'),
20 function __construct($ar = null) {
22 parent::__construct($ar);
24 /* transparently handle bare vs. non bare repos */
25 $this->gitdir = $this->repopath;
26 if (is_dir("$this->repopath/.git")) {
27 $this->gitdir .= "/.git";
32 function getServerURL() {
33 $url = parent::getServerURL();
34 if ($url) return $url;
35 $url = MTrackConfig::get('repos', 'serverurl');
37 return "$url:" . $this->getBrowseRootName();
42 public function reconcileRepoSettings(MTrackSCM $r = null) {
47 if (!is_dir($r->repopath)) {
48 $userdata = MTrackAuth::getUserData(MTrackAuth::whoami());
49 $who = $userdata['email'];
50 putenv("GIT_AUTHOR_NAME=$who");
51 putenv("GIT_AUTHOR_EMAIL=$who");
54 $S = MTrack_Repo::loadById($r->clonedfrom);
56 $stm = MTrackSCM::run('git', 'read',
57 array('clone', '--bare', $S->repopath, $r->repopath));
58 $out = stream_get_contents($stm);
60 throw new Exception("git init failed: $out");
64 /* a little peculiar, but bear with it.
65 * We need to have a bare repo so that git doesn't mess around
66 * trying to deal with a checkout in the repo dir.
67 * So we need to create two repos; one bare, one not bare.
68 * We populate the non-bare repo with a dummy file just to have
69 * something to commit, then push non-bare -> bare, and remove non-bare.
72 $stm = MTrackSCM::run('git', 'read',
73 array('init', '--bare', $r->repopath));
74 $out = stream_get_contents($stm);
76 throw new Exception("git init failed: $out");
79 $alt = "$r->repopath.MTRACKINIT";
81 $stm = MTrackSCM::run('git', 'read',
83 $out = stream_get_contents($stm);
85 throw new Exception("git init failed: $out");
91 file_put_contents("$alt/.gitignore", "#\n");
92 $stm = MTrackSCM::run('git', 'read',
93 array('add', '.gitignore'));
94 $out = stream_get_contents($stm);
96 throw new Exception("git add .gitignore failed: $out");
98 $stm = MTrackSCM::run('git', 'read',
99 array('commit', '-a', '-m', 'init'));
100 $out = stream_get_contents($stm);
102 throw new Exception("git commit failed: $out");
104 $stm = MTrackSCM::run('git', 'read',
105 array('push', $r->repopath, 'master'));
106 $out = stream_get_contents($stm);
108 throw new Exception("git push failed: $out");
111 system("rm -rf $alt");
114 $php = MTrackConfig::get('tools', 'php');
115 $hook = realpath(dirname(__FILE__) . '/../../bin/git-commit-hook');
116 $conffile = realpath(MTrackConfig::getLocation());
117 foreach (array('pre', 'post') as $step) {
120 exec $php $hook $step $conffile
123 $target = "$r->repopath/hooks/$step-receive";
124 if (file_put_contents("$target.mtrack", $script)) {
125 chmod("$target.mtrack", 0755);
126 rename("$target.mtrack", $target);
131 system("chmod -R 02777 $r->repopath");
139 public function getBranches()
141 if ($this->branches !== null) {
142 return $this->branches;
144 $this->branches = array();
145 $fp = $this->git('branch', '--no-color', '--verbose');
146 while ($line = fgets($fp)) {
147 // * master 61e7e7d oneliner
148 $line = substr($line, 2);
149 list($branch, $rev) = preg_split('/\s+/', $line);
150 $this->branches[$branch] = $rev;
153 return $this->branches;
156 public function getTags()
158 if ($this->tags !== null) {
161 $this->tags = array();
162 $fp = $this->git('tag');
163 while ($line = fgets($fp)) {
165 $this->tags[$line] = $line;
171 public function readdir($path, $object = null, $ident = null)
175 if ($object === null) {
179 $rev = $this->resolveRevision(null, $object, $ident);
182 $path = rtrim($path, '/') . '/';
185 $fp = $this->git('ls-tree', $rev, $path);
188 require_once 'MTrack/SCM/Git/File.php';
189 while ($line = fgets($fp)) {
190 // blob = file, tree = dir..
191 list($mode, $type, $hash, $name) = preg_split("/\s+/", $line);
192 //echo '<PRE>';echo $line ."\n</PRE>";
193 $res[] = new MTrack_SCM_Git_File($this, "$name", $rev, $type == 'tree', $hash);
198 public function file($path, $object = null, $ident = null)
200 if ($object == null) {
201 $branches = $this->getBranches();
202 if (isset($branches['master'])) {
210 $rev = $this->resolveRevision(null, $object, $ident);
211 require_once 'MTrack/SCM/Git/File.php';
212 return new MTrack_SCM_Git_File($this, $path, $rev);
217 * @param string path (can be empty - eg. '')
218 * @param {number|date} limit how many to fetch
222 * @param {string} object = eg. rev|tag|branch (use 'rev' here and ident=HASH to retrieve a speific revision
223 * @param {string} ident =
226 * - fetch on revision?: '.',1,'rev','xxxxxxxxxx'
229 * range... object ='rev' ident ='master..release'
232 public function history($path, $limit = null, $object = null, $ident = null)
239 if ($object !== null) {
240 $rev = $this->resolveRevision(null, $object, $ident); // from scm...
246 if ($limit !== null) {
247 if (is_int($limit)) {
248 $args[] = "--max-count=$limit";
249 } else if (is_array($limit) && isset($limit[0]) && isset($limit[1])) {
251 $args[] = "--skip={$limit[0]} --max-count={$limit[1]}";
254 /// oh what a horible hack.. - bad api design here.
255 } else if (is_array($limit) ) {
256 foreach($limit as $k=>$v) {
258 $args[] = ($k == '-') ? $v : ('--'. $k .'='. $v);
262 } else if (strpos($limit,'..') !== false ) {
265 $args[] = "--since=$limit";
268 $args[] = "--no-color";
269 //$args[] = "--name-status";
271 $args[] = "--no-abbrev";
272 $args[] = "--numstat";
273 $args[] = "--date=rfc";
274 $args[] = "--source"; // show the branch..
277 //echo '<PRE>';print_r($args);echo '</PRE>';
278 $path = ltrim($path, '/');
282 // print_R(array($args, '--' ,$path));exit;
283 $fp = $this->git('log', $args, '--', $path);
289 if ($line === false) { //end of file..
290 if ($commit !== null) {
291 $commits[] = $commit;
295 if (preg_match("/^commit/", $line)) {
296 if ($commit !== null) {
297 $commits[] = $commit;
304 require_once 'MTrack/SCM/Git/Event.php';
305 foreach ($commits as $commit) {
306 $res[] = MTrack_SCM_Git_Event::newFromCommit($commit, $this);
313 public function diff($path, $from = null, $to = null)
315 require_once 'MTrack/SCMFile.php';
317 if ($path instanceof MTrackSCMFile) {
318 if ($from === null) {
324 // if it's a file event.. we are even lucker..
325 if ($path instanceof MTrackSCMFileEvent) {
326 return $this->git('log', '--max-count=1', '--format=format:', '--patch', $from, '--', $path->name);
332 return $this->git('diff', "$from..$to", '--', $path);
334 return $this->git('diff', "$from^..$from", '--', $path);
337 public function getWorkingCopy()
339 require_once 'MTrack/SCM/Git/WorkingCopy.php';
340 return new MTrack_SCM_Git_WorkingCopy($this);
343 public function getRelatedChanges($revision) // pretty nasty.. could end up with 1000's of changes..
348 $fp = $this->git('rev-parse', "$revision^");
349 while (($line = fgets($fp)) !== false) {
350 $parents[] = trim($line);
353 // Ugh!: http://stackoverflow.com/questions/1761825/referencing-the-child-of-a-commit-in-git
354 $fp = $this->git('rev-list', '--all', '--parents');
355 while (($line = fgets($fp)) !== false) {
356 $hashes = preg_split("/\s+/", $line);
357 $kid = array_shift($hashes);
358 if (in_array($revision, $hashes)) {
363 return array($parents, $kids);
368 $args = func_get_args();
370 "--git-dir=$this->gitdir"
375 if ($this->gitdir != $this->repopath) {
376 // print_r(array($this->gitdir , $this->repopath));
378 //$a[] = "--work-tree=$this->repopath";
380 foreach ($args as $arg) {
381 if (is_array($arg)) {
382 $a = array_merge($a, $arg);
388 var_dump('git ' . join (' ' , $a));
391 //echo "git " . implode(" " , $a) . "\n";
392 return MTrackSCM::run('git', 'read', $a);
396 function commitLogToEvent($str) {
397 require_once 'MTrack/SCM/Git/Event.php';
398 return MTrack_SCM_Git_Event::newFromCommit($str,$this);