2 require_once 'MTrack/Repo.php';
5 class MTrack_SCM_Git_Repo extends MTrackRepo
7 protected $branches = null;
8 protected $tags = null;
11 public function getSCMMetaData() {
14 'tools' => array('git'),
18 function __construct($id = null) {
19 parent::__construct($id);
21 /* transparently handle bare vs. non bare repos */
22 $this->gitdir = $this->repopath;
23 if (is_dir("$this->repopath/.git")) {
24 $this->gitdir .= "/.git";
29 function getServerURL() {
30 $url = parent::getServerURL();
31 if ($url) return $url;
32 $url = MTrackConfig::get('repos', 'serverurl');
34 return "$url:" . $this->getBrowseRootName();
39 public function reconcileRepoSettings(MTrackSCM $r = null) {
44 if (!is_dir($r->repopath)) {
45 $userdata = MTrackAuth::getUserData(MTrackAuth::whoami());
46 $who = $userdata['email'];
47 putenv("GIT_AUTHOR_NAME=$who");
48 putenv("GIT_AUTHOR_EMAIL=$who");
51 $S = MTrackRepo::loadById($r->clonedfrom);
53 $stm = MTrackSCM::run('git', 'read',
54 array('clone', '--bare', $S->repopath, $r->repopath));
55 $out = stream_get_contents($stm);
57 throw new Exception("git init failed: $out");
61 /* a little peculiar, but bear with it.
62 * We need to have a bare repo so that git doesn't mess around
63 * trying to deal with a checkout in the repo dir.
64 * So we need to create two repos; one bare, one not bare.
65 * We populate the non-bare repo with a dummy file just to have
66 * something to commit, then push non-bare -> bare, and remove non-bare.
69 $stm = MTrackSCM::run('git', 'read',
70 array('init', '--bare', $r->repopath));
71 $out = stream_get_contents($stm);
73 throw new Exception("git init failed: $out");
76 $alt = "$r->repopath.MTRACKINIT";
78 $stm = MTrackSCM::run('git', 'read',
80 $out = stream_get_contents($stm);
82 throw new Exception("git init failed: $out");
88 file_put_contents("$alt/.gitignore", "#\n");
89 $stm = MTrackSCM::run('git', 'read',
90 array('add', '.gitignore'));
91 $out = stream_get_contents($stm);
93 throw new Exception("git add .gitignore failed: $out");
95 $stm = MTrackSCM::run('git', 'read',
96 array('commit', '-a', '-m', 'init'));
97 $out = stream_get_contents($stm);
99 throw new Exception("git commit failed: $out");
101 $stm = MTrackSCM::run('git', 'read',
102 array('push', $r->repopath, 'master'));
103 $out = stream_get_contents($stm);
105 throw new Exception("git push failed: $out");
108 system("rm -rf $alt");
111 $php = MTrackConfig::get('tools', 'php');
112 $hook = realpath(dirname(__FILE__) . '/../../bin/git-commit-hook');
113 $conffile = realpath(MTrackConfig::getLocation());
114 foreach (array('pre', 'post') as $step) {
117 exec $php $hook $step $conffile
120 $target = "$r->repopath/hooks/$step-receive";
121 if (file_put_contents("$target.mtrack", $script)) {
122 chmod("$target.mtrack", 0755);
123 rename("$target.mtrack", $target);
128 system("chmod -R 02777 $r->repopath");
136 public function getBranches()
138 if ($this->branches !== null) {
139 return $this->branches;
141 $this->branches = array();
142 $fp = $this->git('branch', '--no-color', '--verbose');
143 while ($line = fgets($fp)) {
144 // * master 61e7e7d oneliner
145 $line = substr($line, 2);
146 list($branch, $rev) = preg_split('/\s+/', $line);
147 $this->branches[$branch] = $rev;
150 return $this->branches;
153 public function getTags()
155 if ($this->tags !== null) {
158 $this->tags = array();
159 $fp = $this->git('tag');
160 while ($line = fgets($fp)) {
162 $this->tags[$line] = $line;
168 public function readdir($path, $object = null, $ident = null)
172 if ($object === null) {
176 $rev = $this->resolveRevision(null, $object, $ident);
179 $path = rtrim($path, '/') . '/';
182 $fp = $this->git('ls-tree', $rev, $path);
185 require_once 'MTrack/SCM/Git/File.php';
186 while ($line = fgets($fp)) {
187 // blob = file, tree = dir..
188 list($mode, $type, $hash, $name) = preg_split("/\s+/", $line);
189 //echo '<PRE>';echo $line ."\n</PRE>";
190 $res[] = new MTrack_SCM_Git_File($this, "$name", $rev, $type == 'tree', $hash);
195 public function file($path, $object = null, $ident = null)
197 if ($object == null) {
198 $branches = $this->getBranches();
199 if (isset($branches['master'])) {
207 $rev = $this->resolveRevision(null, $object, $ident);
208 require_once 'MTrack/SCM/Git/File.php';
209 return new MTrack_SCM_Git_File($this, $path, $rev);
214 * @param string path (can be empty - eg. '')
215 * @param {number|date} limit how many to fetch
216 * @param {string} object = eg. rev|tag|branch (use 'rev' here and ident=HASH to retrieve a speific revision
217 * @param {string} ident =
220 public function history($path, $limit = null, $object = null, $ident = null)
227 if ($object !== null) {
228 $rev = $this->resolveRevision(null, $object, $ident); // from scm...
235 if ($limit !== null) {
236 if (is_int($limit)) {
237 $args[] = "--max-count=$limit";
239 $args[] = "--since=$limit";
242 $args[] = "--no-color";
243 //$args[] = "--name-status";
245 $args[] = "--no-abbrev";
246 $args[] = "--numstat";
247 $args[] = "--date=rfc";
250 //echo '<PRE>';print_r($args);echo '</PRE>';
251 $path = ltrim($path, '/');
252 //print_R(array($args, '--' ,$path));
253 $fp = $this->git('log', $args, '--', $path);
259 if ($line === false) {
260 if ($commit !== null) {
261 $commits[] = $commit;
265 if (preg_match("/^commit/", $line)) {
266 if ($commit !== null) {
267 $commits[] = $commit;
274 require_once 'MTrack/SCM/Git/Event.php';
275 foreach ($commits as $commit) {
276 $res[] = MTrack_SCM_Git_Event::newFromCommit($commit, $this);
283 public function diff($path, $from = null, $to = null)
285 require_once 'MTrack/SCMFile.php';
287 if ($path instanceof MTrackSCMFile) {
288 if ($from === null) {
294 // if it's a file event.. we are even lucker..
295 if ($path instanceof MTrackSCMFileEvent) {
296 return $this->git('log', '--max-count=1', '--format=format:', '--patch', $from, '--', $path->name);
302 return $this->git('diff', "$from..$to", '--', $path);
304 return $this->git('diff', "$from^..$from", '--', $path);
307 public function getWorkingCopy()
309 require_once 'MTrack/SCM/Git/WorkingCopy.php';
310 return new MTrack_SCM_Git_WorkingCopy($this);
313 public function getRelatedChanges($revision) // pretty nasty.. could end up with 1000's of changes..
318 $fp = $this->git('rev-parse', "$revision^");
319 while (($line = fgets($fp)) !== false) {
320 $parents[] = trim($line);
323 // Ugh!: http://stackoverflow.com/questions/1761825/referencing-the-child-of-a-commit-in-git
324 $fp = $this->git('rev-list', '--all', '--parents');
325 while (($line = fgets($fp)) !== false) {
326 $hashes = preg_split("/\s+/", $line);
327 $kid = array_shift($hashes);
328 if (in_array($revision, $hashes)) {
333 return array($parents, $kids);
338 $args = func_get_args();
340 "--git-dir=$this->gitdir"
345 if ($this->gitdir != $this->repopath) {
346 // print_r(array($this->gitdir , $this->repopath));
348 //$a[] = "--work-tree=$this->repopath";
350 foreach ($args as $arg) {
351 if (is_array($arg)) {
352 $a = array_merge($a, $arg);
357 var_dump('git ' . join (' ' , $a));
359 return MTrackSCM::run('git', 'read', $a);