clone_remote($source, $reference); } else { $repo->clone_from($source); } } else { $repo->run('init'); } return $repo; } } /** * Constructor. * * Accepts a repository path * * @param string repository path * @param bool create if not exists? * * @return void */ public function __construct($repo_path = null, $create_new = false, $_init = true) { if (is_string($repo_path)) { $this->set_repo_path($repo_path, $create_new, $_init); } } /** * Set the repository's path. * * Accepts the repository path * * @param string repository path * @param bool create if not exists? * @param bool initialize new Git repo if not exists? * * @return void */ public function set_repo_path($repo_path, $create_new = false, $_init = true) { if (is_string($repo_path)) { if ($new_path = realpath($repo_path)) { $repo_path = $new_path; if (is_dir($repo_path)) { // Is this a work tree? if (file_exists($repo_path.'/.git') && is_dir($repo_path.'/.git')) { $this->repo_path = $repo_path; $this->bare = false; // Is this a bare repo? } elseif (is_file($repo_path.'/config')) { $parse_ini = parse_ini_file($repo_path.'/config'); if ($parse_ini['bare']) { $this->repo_path = $repo_path; $this->bare = true; } } else { if ($create_new) { $this->repo_path = $repo_path; if ($_init) { $this->run('init'); } } else { throw new Exception('"'.$repo_path.'" is not a git repository'); } } } else { throw new Exception('"'.$repo_path.'" is not a directory'); } } else { if ($create_new) { if ($parent = realpath(dirname($repo_path))) { mkdir($repo_path); $this->repo_path = $repo_path; if ($_init) { $this->run('init'); } } else { throw new Exception('cannot create repository in non-existent directory'); } } else { throw new Exception('"'.$repo_path.'" does not exist'); } } } } /** * Get the path to the git repo directory (eg. the ".git" directory). * * @return string */ public function git_directory_path() { return ($this->bare) ? $this->repo_path : $this->repo_path.'/.git'; } /** * Tests if git is installed. * * @return bool */ public function test_git() { $descriptorspec = [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ]; $pipes = []; $resource = proc_open(Git::get_bin(), $descriptorspec, $pipes); $stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]); foreach ($pipes as $pipe) { fclose($pipe); } $status = trim(proc_close($resource)); return $status != 127; } /** * Run a command in the git repository. * * Accepts a shell command to run * * @param string command to run * * @return string */ protected function run_command($command) { $descriptorspec = [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ]; $pipes = []; /* Depending on the value of variables_order, $_ENV may be empty. * In that case, we have to explicitly set the new variables with * putenv, and call proc_open with env=null to inherit the reset * of the system. * * This is kind of crappy because we cannot easily restore just those * variables afterwards. * * If $_ENV is not empty, then we can just copy it and be done with it. */ if (count($_ENV) === 0) { $env = null; foreach ($this->envopts as $k => $v) { putenv(sprintf('%s=%s', $k, $v)); } } else { $env = array_merge($_ENV, $this->envopts); } $cwd = $this->repo_path; $resource = proc_open($command, $descriptorspec, $pipes, $cwd, $env); $stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]); foreach ($pipes as $pipe) { fclose($pipe); } $status = trim(proc_close($resource)); if ($status) { throw new Exception($stderr); } return $stdout; } /** * Run a git command in the git repository. * * Accepts a git command to run * * @param string command to run * * @return string */ public function run($command) { return $this->run_command(Git::get_bin().' '.$command); } /** * Runs a 'git status' call. * * Accept a convert to HTML bool * * @param bool return string with
* * @return string */ public function status($html = false) { $msg = $this->run('status'); if ($html == true) { $msg = str_replace("\n", '
', $msg); } return $msg; } /** * Runs a `git add` call. * * Accepts a list of files to add * * @param mixed files to add * * @return string */ public function add($files = '*') { if (is_array($files)) { $files = '"'.implode('" "', $files).'"'; } return $this->run("add $files -v"); } /** * Runs a `git rm` call. * * Accepts a list of files to remove * * @param mixed files to remove * @param bool use the --cached flag? * * @return string */ public function rm($files = '*', $cached = false) { if (is_array($files)) { $files = '"'.implode('" "', $files).'"'; } return $this->run('rm '.($cached ? '--cached ' : '').$files); } /** * Runs a `git commit` call. * * Accepts a commit message string * * @param string commit message * @param bool should all files be committed automatically (-a flag) * * @return string */ public function commit($message = '', $commit_all = true) { $flags = $commit_all ? '-av' : '-v'; return $this->run('commit '.$flags.' -m '.escapeshellarg($message)); } /** * Runs a `git clone` call to clone the current repository * into a different directory. * * Accepts a target directory * * @param string target directory * * @return string */ public function clone_to($target) { return $this->run('clone --local '.$this->repo_path." $target"); } /** * Runs a `git clone` call to clone a different repository * into the current repository. * * Accepts a source directory * * @param string source directory * * @return string */ public function clone_from($source) { return $this->run("clone --local $source ".$this->repo_path); } /** * Runs a `git clone` call to clone a remote repository * into the current repository. * * Accepts a source url * * @param string source url * @param string reference path * * @return string */ public function clone_remote($source, $reference) { return $this->run("clone $reference $source ".$this->repo_path); } /** * Runs a `git clean` call. * * Accepts a remove directories flag * * @param bool delete directories? * @param bool force clean? * * @return string */ public function clean($dirs = false, $force = false) { return $this->run('clean'.(($force) ? ' -f' : '').(($dirs) ? ' -d' : '')); } /** * Runs a `git branch` call. * * Accepts a name for the branch * * @param string branch name * * @return string */ public function create_branch($branch) { return $this->run("branch $branch"); } /** * Runs a `git branch -[d|D]` call. * * Accepts a name for the branch * * @param string branch name * * @return string */ public function delete_branch($branch, $force = false) { return $this->run('branch '.(($force) ? '-D' : '-d')." $branch"); } /** * Runs a `git branch` call. * * @param bool keep asterisk mark on active branch * * @return array */ public function list_branches($keep_asterisk = false) { $branchArray = explode("\n", $this->run('branch')); foreach ($branchArray as $i => &$branch) { $branch = trim($branch); if (!$keep_asterisk) { $branch = str_replace('* ', '', $branch); } if ($branch == '') { unset($branchArray[$i]); } } return $branchArray; } /** * Lists remote branches (using `git branch -r`). * * Also strips out the HEAD reference (e.g. "origin/HEAD -> origin/master"). * * @return array */ public function list_remote_branches() { $branchArray = explode("\n", $this->run('branch -r')); foreach ($branchArray as $i => &$branch) { $branch = trim($branch); if ($branch == '' || strpos($branch, 'HEAD -> ') !== false) { unset($branchArray[$i]); } } return $branchArray; } /** * Returns name of active branch. * * @param bool keep asterisk mark on branch name * * @return string */ public function active_branch($keep_asterisk = false) { $branchArray = $this->list_branches(true); $active_branch = preg_grep("/^\*/", $branchArray); reset($active_branch); if ($keep_asterisk) { return current($active_branch); } else { return str_replace('* ', '', current($active_branch)); } } /** * Runs a `git checkout` call. * * Accepts a name for the branch * * @param string branch name * * @return string */ public function checkout($branch) { return $this->run("checkout $branch"); } /** * Runs a `git merge` call. * * Accepts a name for the branch to be merged * * @param string $branch * * @return string */ public function merge($branch) { return $this->run("merge $branch --no-ff"); } /** * Runs a git fetch on the current branch. * * @return string */ public function fetch() { return $this->run('fetch'); } /** * Add a new tag on the current position. * * Accepts the name for the tag and the message * * @param string $tag * @param string $message * * @return string */ public function add_tag($tag, $message = null) { if ($message === null) { $message = $tag; } return $this->run("tag -a $tag -m ".escapeshellarg($message)); } /** * List all the available repository tags. * * Optionally, accept a shell wildcard pattern and return only tags matching it. * * @param string $pattern Shell wildcard pattern to match tags against. * * @return array Available repository tags. */ public function list_tags($pattern = null) { $tagArray = explode("\n", $this->run("tag -l $pattern")); foreach ($tagArray as $i => &$tag) { $tag = trim($tag); if ($tag == '') { unset($tagArray[$i]); } } return $tagArray; } /** * Push specific branch to a remote. * * Accepts the name of the remote and local branch * * @param string $remote * @param string $branch * * @return string */ public function push($remote, $branch) { return $this->run("push --tags $remote $branch"); } /** * Pull specific branch from remote. * * Accepts the name of the remote and local branch * * @param string $remote * @param string $branch * * @return string */ public function pull($remote, $branch) { return $this->run("pull $remote $branch"); } /** * List log entries. * * @param strgin $format * * @return string */ public function log($format = null) { if ($format === null) { return $this->run('log'); } else { return $this->run('log --pretty=format:"'.$format.'"'); } } /** * Sets the project description. * * @param string $new */ public function set_description($new) { $path = $this->git_directory_path(); file_put_contents($path.'/description', $new); } /** * Gets the project description. * * @return string */ public function get_description() { $path = $this->git_directory_path(); return file_get_contents($path.'/description'); } /** * Sets custom environment options for calling Git. * * @param string key * @param string value */ public function setenv($key, $value) { $this->envopts[$key] = $value; } } /* End of file */