= 7.4 only. // protected ?\action_plugin_gitbacked_editcommit $plugin = null; protected $plugin = null; /** * Create a new git repository * * Accepts a creation path, and, optionally, a source path * * @access public * @param string repository path * @param \action_plugin_gitbacked_editcommit plugin * @param string directory to source * @param string reference path * @return GitRepo or null in case of an error */ public static function &create_new($repo_path, \action_plugin_gitbacked_editcommit $plugin = null, $source = null, $remote_source = false, $reference = null) { if (is_dir($repo_path) && file_exists($repo_path."/.git") && is_dir($repo_path."/.git")) { throw new Exception(self::handle_create_new_error($repo_path, $reference, '"'.$repo_path.'" is already a git repository', $plugin)); } else { $repo = new self($repo_path, $plugin, true, false); if (is_string($source)) { if ($remote_source) { if (!is_dir($reference) || !is_dir($reference.'/.git')) { throw new Exception(self::handle_create_new_error($repo_path, $reference, '"'.$reference.'" is not a git repository. Cannot use as reference.', $plugin)); } else if (strlen($reference)) { $reference = realpath($reference); $reference = "--reference $reference"; } $repo->clone_remote($source, $reference); } else { $repo->clone_from($source); } } else { $repo->run('init'); } return $repo; } } /** * Constructor * * Accepts a repository path * * @access public * @param string repository path * @param \action_plugin_gitbacked_editcommit plugin * @param bool create if not exists? * @return void */ public function __construct($repo_path = null, \action_plugin_gitbacked_editcommit $plugin = null, $create_new = false, $_init = true) { $this->plugin = $plugin; if (is_string($repo_path)) { $this->set_repo_path($repo_path, $create_new, $_init); } } /** * Set the repository's path * * Accepts the repository path * * @access public * @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? } else if (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($this->handle_repo_path_error($repo_path, '"'.$repo_path.'" is not a git repository')); } } } else { throw new Exception($this->handle_repo_path_error($repo_path, '"'.$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($this->handle_repo_path_error($repo_path, 'cannot create repository in non-existent directory')); } } else { throw new Exception($this->handle_repo_path_error($repo_path, '"'.$repo_path.'" does not exist')); } } } } /** * Get the path to the git repo directory (eg. the ".git" directory) * * @access public * @return string */ public function git_directory_path() { return ($this->bare) ? $this->repo_path : $this->repo_path."/.git"; } /** * Tests if git is installed * * @access public * @return bool */ public function test_git() { $descriptorspec = array( 1 => array('pipe', 'w'), 2 => array('pipe', 'w'), ); $pipes = array(); $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 * * @access protected * @param string command to run * @return string or null in case of an error */ protected function run_command($command) { //dbglog("Git->run_command(command=[".$command."])"); $descriptorspec = array( 1 => array('pipe', 'w'), 2 => array('pipe', 'w'), ); $pipes = array(); /* 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; //dbglog("GitBacked - cwd: [".$cwd."]"); $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)); //dbglog("GitBacked: run_command status: ".$status); if ($status) { //dbglog("GitBacked - stderr: [".$stderr."]"); // Remove a probable password from the Git URL, if the URL is contained in the error message $error_message = preg_replace($this::REGEX_GIT_URL_FILTER_PWD, $this::REGEX_GIT_URL_FILTER_PWD_REPLACE_PATTERN, $stderr); //dbglog("GitBacked - error_message: [".$error_message."]"); throw new Exception($this->handle_command_error($this->repo_path, $cwd, $command, $status, $error_message)); } else { $this->handle_command_success($this->repo_path, $cwd, $command); } return $stdout; } /** * Run a git command in the git repository * * Accepts a git command to run * * @access public * @param string command to run * @return string */ public function run($command) { return $this->run_command(Git::get_bin()." ".$command); } /** * Handles error on create_new * * @access protected * @param string repository path * @param string error message * @return string error message */ protected static function handle_create_new_error($repo_path, $reference, $error_message, $plugin) { if ($plugin instanceof \action_plugin_gitbacked_editcommit) { $plugin->notify_create_new_error($repo_path, $reference, $error_message); } return $error_message; } /** * Handles error on setting the repo path * * @access protected * @param string repository path * @param string error message * @return string error message */ protected function handle_repo_path_error($repo_path, $error_message) { if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { $this->plugin->notify_repo_path_error($repo_path, $error_message); } return $error_message; } /** * Handles error on git command * * @access protected * @param string repository path * @param string current working dir * @param string command line * @param int exit code of command (status) * @param string error message * @return string error message */ protected function handle_command_error($repo_path, $cwd, $command, $status, $error_message) { if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { $this->plugin->notify_command_error($repo_path, $cwd, $command, $status, $error_message); } return $error_message; } /** * Handles success on git command * * @access protected * @param string repository path * @param string current working dir * @param string command line * @return void */ protected function handle_command_success($repo_path, $cwd, $command) { if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { $this->plugin->notify_command_success($repo_path, $cwd, $command); } } /** * Runs a 'git status' call * * Accept a convert to HTML bool * * @access public * @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 * * @access public * @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 * * @access public * @param mixed files to remove * @param Boolean 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 * * @access public * @param string commit message * @param boolean should all files be committed automatically (-a flag) * @return string */ public function commit($message = "", $commit_all = true) { $flags = $commit_all ? '-av' : '-v'; $msgfile = GitBackedUtil::createMessageFile($message); try { return $this->run("commit --allow-empty ".$flags." --file=".$msgfile); } finally { unlink($msgfile); } } /** * Runs a `git clone` call to clone the current repository * into a different directory * * Accepts a target directory * * @access public * @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 * * @access public * @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 * * @access public * @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 * * @access public * @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 * * @access public * @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 * * @access public * @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 * * @access public * @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"). * * @access public * @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 * * @access public * @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 * * @access public * @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 * * @access public * @param string $branch * @return string */ public function merge($branch) { return $this->run("merge $branch --no-ff"); } /** * Runs a git fetch on the current branch * * @access public * @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; } $msgfile = GitBackedUtil::createMessageFile($message); try { return $this->run("tag -a $tag --file=".$msgfile); } finally { unlink($msgfile); } } /** * List all the available repository tags. * * Optionally, accept a shell wildcard pattern and return only tags matching it. * * @access public * @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 */