'Git', 'tools' => array('git'), ); } function __construct($ar = null) { parent::__construct($ar); if ($ar !== null) { /* transparently handle bare vs. non bare repos */ $this->gitdir = $this->repopath; if (is_dir("$this->repopath/.git")) { $this->gitdir .= "/.git"; } } } function getServerURL() { $url = parent::getServerURL(); if ($url) return $url; $url = MTrackConfig::get('repos', 'serverurl'); if ($url) { return "$url:" . $this->getBrowseRootName(); } return null; } public function reconcileRepoSettings(MTrackSCM $r = null) { if ($r == null) { $r = $this; } if (!is_dir($r->repopath)) { $userdata = MTrackAuth::getUserData(MTrackAuth::whoami()); $who = $userdata['email']; putenv("GIT_AUTHOR_NAME=$who"); putenv("GIT_AUTHOR_EMAIL=$who"); if ($r->clonedfrom) { $S = MTrack_Repo::loadById($r->clonedfrom); $stm = MTrackSCM::run('git', 'read', array('clone', '--bare', $S->repopath, $r->repopath)); $out = stream_get_contents($stm); if (pclose($stm)) { throw new Exception("git init failed: $out"); } } else { /* a little peculiar, but bear with it. * We need to have a bare repo so that git doesn't mess around * trying to deal with a checkout in the repo dir. * So we need to create two repos; one bare, one not bare. * We populate the non-bare repo with a dummy file just to have * something to commit, then push non-bare -> bare, and remove non-bare. */ $stm = MTrackSCM::run('git', 'read', array('init', '--bare', $r->repopath)); $out = stream_get_contents($stm); if (pclose($stm)) { throw new Exception("git init failed: $out"); } $alt = "$r->repopath.MTRACKINIT"; $stm = MTrackSCM::run('git', 'read', array('init', $alt)); $out = stream_get_contents($stm); if (pclose($stm)) { throw new Exception("git init failed: $out"); } $dir = getcwd(); chdir($alt); file_put_contents("$alt/.gitignore", "#\n"); $stm = MTrackSCM::run('git', 'read', array('add', '.gitignore')); $out = stream_get_contents($stm); if (pclose($stm)) { throw new Exception("git add .gitignore failed: $out"); } $stm = MTrackSCM::run('git', 'read', array('commit', '-a', '-m', 'init')); $out = stream_get_contents($stm); if (pclose($stm)) { throw new Exception("git commit failed: $out"); } $stm = MTrackSCM::run('git', 'read', array('push', $r->repopath, 'master')); $out = stream_get_contents($stm); if (pclose($stm)) { throw new Exception("git push failed: $out"); } chdir($dir); system("rm -rf $alt"); } $php = MTrackConfig::get('tools', 'php'); $hook = realpath(dirname(__FILE__) . '/../../bin/git-commit-hook'); $conffile = realpath(MTrackConfig::getLocation()); foreach (array('pre', 'post') as $step) { $script = <<repopath"); } function canFork() { return true; } public function getBranches() { if ($this->branches !== null) { return $this->branches; } $this->branches = array(); $fp = $this->git('branch', '--no-color', '--verbose'); while ($line = fgets($fp)) { // * master 61e7e7d oneliner $line = substr($line, 2); list($branch, $rev) = preg_split('/\s+/', $line); $this->branches[$branch] = $rev; } $fp = null; return $this->branches; } public function getTags() { if ($this->tags !== null) { return $this->tags; } $this->tags = array(); $fp = $this->git('tag'); while ($line = fgets($fp)) { $line = trim($line); $this->tags[$line] = $line; } $fp = null; return $this->tags; } public function readdir($path, $object = null, $ident = null) { $res = array(); if ($object === null) { $object = 'branch'; $ident = 'master'; } $rev = $this->resolveRevision(null, $object, $ident); if (strlen($path)) { $path = rtrim($path, '/') . '/'; } $fp = $this->git('ls-tree', $rev, $path); $dirs = array(); require_once 'MTrack/SCM/Git/File.php'; while ($line = fgets($fp)) { // blob = file, tree = dir.. list($mode, $type, $hash, $name) = preg_split("/\s+/", $line); //echo '
';echo $line ."\n
"; $res[] = new MTrack_SCM_Git_File($this, "$name", $rev, $type == 'tree', $hash); } return $res; } public function file($path, $object = null, $ident = null) { if ($object == null) { $branches = $this->getBranches(); if (isset($branches['master'])) { $object = 'branch'; $ident = 'master'; } else { // fresh/empty repo return null; } } $rev = $this->resolveRevision(null, $object, $ident); require_once 'MTrack/SCM/Git/File.php'; return new MTrack_SCM_Git_File($this, $path, $rev); } /** * * @param string path (can be empty - eg. '') * @param {number|date} limit how many to fetch * * -- prefered args: * * @param {string} object = eg. rev|tag|branch (use 'rev' here and ident=HASH to retrieve a speific revision * @param {string} ident = * * Example: * - fetch on revision?: '.',1,'rev','xxxxxxxxxx' * * * range... object ='rev' ident ='master..release' * */ public function history($path, $limit = null, $object = null, $ident = null) { $res = array(); $args = array(); if ($object !== null) { $rev = $this->resolveRevision(null, $object, $ident); // from scm... $args[] = "$rev"; } else { $args[] = "master"; } if ($limit !== null) { if (is_int($limit)) { $args[] = "--max-count=$limit"; } else if (is_array($limit) && isset($limit[0]) && isset($limit[1])) { $args[] = "--skip={$limit[0]} --max-count={$limit[1]}"; /// oh what a horible hack.. - bad api design here. } else if (is_array($limit) ) { foreach($limit as $k=>$v) { $args[] = ($k == '-') ? $v : ('--'. $k .'='. $v); } } else if (strpos($limit,'..') !== false ) { $args[] = $limit; } else { $args[] = "--since=$limit"; } } $args[] = "--no-color"; //$args[] = "--name-status"; $args[] = "--raw"; $args[] = "--no-abbrev"; $args[] = "--numstat"; $args[] = "--date=rfc"; $args[] = "--source"; // show the branch.. //echo '
';print_r($args);echo '
'; $path = ltrim($path, '/'); // print_R(array($args, '--' ,$path));exit; $fp = $this->git('log', $args, '--', $path); $commits = array(); $commit = null; while (true) { $line = fgets($fp); if ($line === false) { //end of file.. if ($commit !== null) { $commits[] = $commit; } break; } if (preg_match("/^commit/", $line)) { if ($commit !== null) { $commits[] = $commit; } $commit = $line; continue; } $commit .= $line; } require_once 'MTrack/SCM/Git/Event.php'; foreach ($commits as $commit) { $res[] = MTrack_SCM_Git_Event::newFromCommit($commit, $this); } $fp = null; return $res; } public function diff($path, $from = null, $to = null) { require_once 'MTrack/SCMFile.php'; if ($path instanceof MTrackSCMFile) { if ($from === null) { $from = $path->rev; } $path = $path->name; } // if it's a file event.. we are even lucker.. if ($path instanceof MTrackSCMFileEvent) { return $this->git('log', '--max-count=1', '--format=format:', '--patch', $from, '--', $path->name); } if ($to !== null) { return $this->git('diff', "$from..$to", '--', $path); } return $this->git('diff', "$from^..$from", '--', $path); } public function getWorkingCopy() { require_once 'MTrack/SCM/Git/WorkingCopy.php'; return new MTrack_SCM_Git_WorkingCopy($this); } public function getRelatedChanges($revision) // pretty nasty.. could end up with 1000's of changes.. { $parents = array(); $kids = array(); $fp = $this->git('rev-parse', "$revision^"); while (($line = fgets($fp)) !== false) { $parents[] = trim($line); } // Ugh!: http://stackoverflow.com/questions/1761825/referencing-the-child-of-a-commit-in-git $fp = $this->git('rev-list', '--all', '--parents'); while (($line = fgets($fp)) !== false) { $hashes = preg_split("/\s+/", $line); $kid = array_shift($hashes); if (in_array($revision, $hashes)) { $kids[] = $kid; } } return array($parents, $kids); } function git() { $args = func_get_args(); $a = array( "--git-dir=$this->gitdir" ); if ($this->gitdir != $this->repopath) { // print_r(array($this->gitdir , $this->repopath)); //$a[] = "--work-tree=$this->repopath"; } foreach ($args as $arg) { if (is_array($arg)) { $a = array_merge($a, $arg); continue; } $a[] = $arg; } if ($this->debug) { var_dump('git ' . join (' ' , $a)); // die("oops"); } //echo "git " . implode(" " , $a) . "\n"; return MTrackSCM::run('git', 'read', $a); } function commitLogToEvent($str) { require_once 'MTrack/SCM/Git/Event.php'; return MTrack_SCM_Git_Event::newFromCommit($str,$this); } }