= 7.4 only. // protected ?\action_plugin_gitbacked_editcommit $plugin = null; protected $plugin; /** * 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 &createNew( $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::handleCreateNewError( $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::handleCreateNewError( $repo_path, $reference, '"' . $reference . '" is not a git repository. Cannot use as reference.', $plugin )); } elseif (strlen($reference)) { $reference = realpath($reference); $reference = "--reference $reference"; } $repo->cloneRemote($source, $reference); } else { $repo->cloneFrom($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->setRepoPath($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 setRepoPath($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; } } elseif ($create_new) { $this->repo_path = $repo_path; if ($_init) { $this->run('init'); } } else { throw new \Exception($this->handleRepoPathError( $repo_path, '"' . $repo_path . '" is not a git repository' )); } } else { throw new \Exception($this->handleRepoPathError( $repo_path, '"' . $repo_path . '" is not a directory' )); } } elseif ($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->handleRepoPathError( $repo_path, 'cannot create repository in non-existent directory' )); } } else { throw new \Exception($this->handleRepoPathError( $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 gitDirectoryPath() { return ($this->bare) ? $this->repo_path : $this->repo_path . "/.git"; } /** * Tests if git is installed * * @access public * @return bool */ public function testGit() { $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $pipes = []; $resource = proc_open(Git::getBin(), $descriptorspec, $pipes); stream_get_contents($pipes[1]); 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 runCommand($command) { //dbglog("Git->runCommand(command=[".$command."])"); $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $pipes = []; $cwd = $this->repo_path; //dbglog("GitBacked - cwd: [".$cwd."]"); /* Provide any $this->envopts via putenv * and call proc_open with env=null to inherit the rest * of env variables from the original process of the system. * Note: Variables set by putenv live for a * single PHP request run only. These variables * are visible "locally". They are NOT listed by getenv(), * but they are visible to the process forked by proc_open(). */ foreach ($this->envopts as $k => $v) { putenv(sprintf("%s=%s", $k, $v)); } $resource = proc_open($command, $descriptorspec, $pipes, $cwd, null); $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: runCommand 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->handleCommandError( $this->repo_path, $cwd, $command, $status, $error_message )); } else { $this->handleCommandSuccess($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->runCommand(Git::getBin() . " " . $command); } /** * Handles error on create_new * * @access protected * @param string repository path * @param string error message * @return string error message */ protected static function handleCreateNewError($repo_path, $reference, $error_message, $plugin) { if ($plugin instanceof \action_plugin_gitbacked_editcommit) { $plugin->notifyCreateNewError($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 handleRepoPathError($repo_path, $error_message) { if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { $this->plugin->notifyRepoPathError($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 handleCommandError($repo_path, $cwd, $command, $status, $error_message) { if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { $this->plugin->notifyCommandError($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 handleCommandSuccess($repo_path, $cwd, $command) { if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { $this->plugin->notifyCommandSuccess($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 cloneTo($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 cloneFrom($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 cloneRemote($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 createBranch($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 deleteBranch($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 listBranches($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 listRemoteBranches() { $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 activeBranch($keep_asterisk = false) { $branchArray = $this->listBranches(true); $activeBranch = preg_grep("/^\*/", $branchArray); reset($activeBranch); if ($keep_asterisk) { return current($activeBranch); } else { return str_replace("* ", "", current($activeBranch)); } } /** * 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 addTag($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 listTags($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 setDescription($new) { $path = $this->gitDirectoryPath(); file_put_contents($path . "/description", $new); } /** * Gets the project description. * * @return string */ public function getDescription() { $path = $this->gitDirectoryPath(); 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 */