1b08c3d51SMarkus Hoffrogge<?php 2b08c3d51SMarkus Hoffrogge 32762023dSMarkus Hoffroggenamespace woolfg\dokuwiki\plugin\gitbacked; 4b08c3d51SMarkus Hoffrogge 52762023dSMarkus Hoffrogge// phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols 6b08c3d51SMarkus Hoffroggeif (__FILE__ == $_SERVER['SCRIPT_FILENAME']) die('Bad load order'); 7b08c3d51SMarkus Hoffrogge 8b08c3d51SMarkus Hoffrogge/** 9b08c3d51SMarkus Hoffrogge * Git Repository Interface Class 10b08c3d51SMarkus Hoffrogge * 11b08c3d51SMarkus Hoffrogge * This class enables the creating, reading, and manipulation 12b08c3d51SMarkus Hoffrogge * of a git repository 13b08c3d51SMarkus Hoffrogge * 14b08c3d51SMarkus Hoffrogge * @class GitRepo 15b08c3d51SMarkus Hoffrogge */ 162762023dSMarkus Hoffroggeclass GitRepo 172762023dSMarkus Hoffrogge{ 18b08c3d51SMarkus Hoffrogge // This regex will filter a probable password from any string containing a Git URL. 19b08c3d51SMarkus Hoffrogge // Limitation: it will work for the first git URL occurrence in a string. 20b08c3d51SMarkus Hoffrogge // Used https://regex101.com/ for evaluating! 212762023dSMarkus Hoffrogge public const REGEX_GIT_URL_FILTER_PWD = "/^(.*)((http:)|(https:))([^:]+)(:[^@]*)?(.*)/im"; 222762023dSMarkus Hoffrogge public const REGEX_GIT_URL_FILTER_PWD_REPLACE_PATTERN = "$1$2$5$7"; 23b08c3d51SMarkus Hoffrogge 24*c365e7dbSmhoffrog protected $repo_path; 25b08c3d51SMarkus Hoffrogge protected $bare = false; 26*c365e7dbSmhoffrog protected $envopts = []; 27b08c3d51SMarkus Hoffrogge // Fix for PHP <=7.3 compatibility: Type declarations for properties work since PHP >= 7.4 only. 28b08c3d51SMarkus Hoffrogge // protected ?\action_plugin_gitbacked_editcommit $plugin = null; 29*c365e7dbSmhoffrog protected $plugin; 30b08c3d51SMarkus Hoffrogge 31b08c3d51SMarkus Hoffrogge /** 32b08c3d51SMarkus Hoffrogge * Create a new git repository 33b08c3d51SMarkus Hoffrogge * 34b08c3d51SMarkus Hoffrogge * Accepts a creation path, and, optionally, a source path 35b08c3d51SMarkus Hoffrogge * 36b08c3d51SMarkus Hoffrogge * @access public 37b08c3d51SMarkus Hoffrogge * @param string repository path 38b08c3d51SMarkus Hoffrogge * @param \action_plugin_gitbacked_editcommit plugin 39b08c3d51SMarkus Hoffrogge * @param string directory to source 40b08c3d51SMarkus Hoffrogge * @param string reference path 41b08c3d51SMarkus Hoffrogge * @return GitRepo or null in case of an error 42b08c3d51SMarkus Hoffrogge */ 432762023dSMarkus Hoffrogge public static function &createNew( 442762023dSMarkus Hoffrogge $repo_path, 452762023dSMarkus Hoffrogge \action_plugin_gitbacked_editcommit $plugin = null, 462762023dSMarkus Hoffrogge $source = null, 472762023dSMarkus Hoffrogge $remote_source = false, 482762023dSMarkus Hoffrogge $reference = null 492762023dSMarkus Hoffrogge ) { 50b08c3d51SMarkus Hoffrogge if (is_dir($repo_path) && file_exists($repo_path . "/.git") && is_dir($repo_path . "/.git")) { 512762023dSMarkus Hoffrogge throw new \Exception(self::handleCreateNewError( 522762023dSMarkus Hoffrogge $repo_path, 532762023dSMarkus Hoffrogge $reference, 542762023dSMarkus Hoffrogge '"' . $repo_path . '" is already a git repository', 552762023dSMarkus Hoffrogge $plugin 562762023dSMarkus Hoffrogge )); 57b08c3d51SMarkus Hoffrogge } else { 58b08c3d51SMarkus Hoffrogge $repo = new self($repo_path, $plugin, true, false); 59b08c3d51SMarkus Hoffrogge if (is_string($source)) { 60b08c3d51SMarkus Hoffrogge if ($remote_source) { 61b08c3d51SMarkus Hoffrogge if (!is_dir($reference) || !is_dir($reference . '/.git')) { 622762023dSMarkus Hoffrogge throw new \Exception(self::handleCreateNewError( 632762023dSMarkus Hoffrogge $repo_path, 642762023dSMarkus Hoffrogge $reference, 652762023dSMarkus Hoffrogge '"' . $reference . '" is not a git repository. Cannot use as reference.', 662762023dSMarkus Hoffrogge $plugin 672762023dSMarkus Hoffrogge )); 68b08c3d51SMarkus Hoffrogge } elseif (strlen($reference)) { 69b08c3d51SMarkus Hoffrogge $reference = realpath($reference); 70b08c3d51SMarkus Hoffrogge $reference = "--reference $reference"; 71b08c3d51SMarkus Hoffrogge } 722762023dSMarkus Hoffrogge $repo->cloneRemote($source, $reference); 73b08c3d51SMarkus Hoffrogge } else { 742762023dSMarkus Hoffrogge $repo->cloneFrom($source); 75b08c3d51SMarkus Hoffrogge } 76b08c3d51SMarkus Hoffrogge } else { 77b08c3d51SMarkus Hoffrogge $repo->run('init'); 78b08c3d51SMarkus Hoffrogge } 79b08c3d51SMarkus Hoffrogge return $repo; 80b08c3d51SMarkus Hoffrogge } 81b08c3d51SMarkus Hoffrogge } 82b08c3d51SMarkus Hoffrogge 83b08c3d51SMarkus Hoffrogge /** 84b08c3d51SMarkus Hoffrogge * Constructor 85b08c3d51SMarkus Hoffrogge * 86b08c3d51SMarkus Hoffrogge * Accepts a repository path 87b08c3d51SMarkus Hoffrogge * 88b08c3d51SMarkus Hoffrogge * @access public 89b08c3d51SMarkus Hoffrogge * @param string repository path 90b08c3d51SMarkus Hoffrogge * @param \action_plugin_gitbacked_editcommit plugin 91b08c3d51SMarkus Hoffrogge * @param bool create if not exists? 92b08c3d51SMarkus Hoffrogge * @return void 93b08c3d51SMarkus Hoffrogge */ 942762023dSMarkus Hoffrogge public function __construct( 952762023dSMarkus Hoffrogge $repo_path = null, 962762023dSMarkus Hoffrogge \action_plugin_gitbacked_editcommit $plugin = null, 972762023dSMarkus Hoffrogge $create_new = false, 982762023dSMarkus Hoffrogge $_init = true 992762023dSMarkus Hoffrogge ) { 100b08c3d51SMarkus Hoffrogge $this->plugin = $plugin; 101b08c3d51SMarkus Hoffrogge if (is_string($repo_path)) { 1022762023dSMarkus Hoffrogge $this->setRepoPath($repo_path, $create_new, $_init); 103b08c3d51SMarkus Hoffrogge } 104b08c3d51SMarkus Hoffrogge } 105b08c3d51SMarkus Hoffrogge 106b08c3d51SMarkus Hoffrogge /** 107b08c3d51SMarkus Hoffrogge * Set the repository's path 108b08c3d51SMarkus Hoffrogge * 109b08c3d51SMarkus Hoffrogge * Accepts the repository path 110b08c3d51SMarkus Hoffrogge * 111b08c3d51SMarkus Hoffrogge * @access public 112b08c3d51SMarkus Hoffrogge * @param string repository path 113b08c3d51SMarkus Hoffrogge * @param bool create if not exists? 114b08c3d51SMarkus Hoffrogge * @param bool initialize new Git repo if not exists? 115b08c3d51SMarkus Hoffrogge * @return void 116b08c3d51SMarkus Hoffrogge */ 1172762023dSMarkus Hoffrogge public function setRepoPath($repo_path, $create_new = false, $_init = true) 1182762023dSMarkus Hoffrogge { 119b08c3d51SMarkus Hoffrogge if (is_string($repo_path)) { 120b08c3d51SMarkus Hoffrogge if ($new_path = realpath($repo_path)) { 121b08c3d51SMarkus Hoffrogge $repo_path = $new_path; 122b08c3d51SMarkus Hoffrogge if (is_dir($repo_path)) { 123b08c3d51SMarkus Hoffrogge // Is this a work tree? 124b08c3d51SMarkus Hoffrogge if (file_exists($repo_path . "/.git") && is_dir($repo_path . "/.git")) { 125b08c3d51SMarkus Hoffrogge $this->repo_path = $repo_path; 126b08c3d51SMarkus Hoffrogge $this->bare = false; 127b08c3d51SMarkus Hoffrogge // Is this a bare repo? 128b08c3d51SMarkus Hoffrogge } elseif (is_file($repo_path . "/config")) { 129b08c3d51SMarkus Hoffrogge $parse_ini = parse_ini_file($repo_path . "/config"); 130b08c3d51SMarkus Hoffrogge if ($parse_ini['bare']) { 131b08c3d51SMarkus Hoffrogge $this->repo_path = $repo_path; 132b08c3d51SMarkus Hoffrogge $this->bare = true; 133b08c3d51SMarkus Hoffrogge } 134*c365e7dbSmhoffrog } elseif ($create_new) { 135b08c3d51SMarkus Hoffrogge $this->repo_path = $repo_path; 136b08c3d51SMarkus Hoffrogge if ($_init) { 137b08c3d51SMarkus Hoffrogge $this->run('init'); 138b08c3d51SMarkus Hoffrogge } 139b08c3d51SMarkus Hoffrogge } else { 1402762023dSMarkus Hoffrogge throw new \Exception($this->handleRepoPathError( 1412762023dSMarkus Hoffrogge $repo_path, 1422762023dSMarkus Hoffrogge '"' . $repo_path . '" is not a git repository' 1432762023dSMarkus Hoffrogge )); 144b08c3d51SMarkus Hoffrogge } 145b08c3d51SMarkus Hoffrogge } else { 1462762023dSMarkus Hoffrogge throw new \Exception($this->handleRepoPathError( 1472762023dSMarkus Hoffrogge $repo_path, 1482762023dSMarkus Hoffrogge '"' . $repo_path . '" is not a directory' 1492762023dSMarkus Hoffrogge )); 150b08c3d51SMarkus Hoffrogge } 151*c365e7dbSmhoffrog } elseif ($create_new) { 152b08c3d51SMarkus Hoffrogge if ($parent = realpath(dirname($repo_path))) { 153b08c3d51SMarkus Hoffrogge mkdir($repo_path); 154b08c3d51SMarkus Hoffrogge $this->repo_path = $repo_path; 155b08c3d51SMarkus Hoffrogge if ($_init) $this->run('init'); 156b08c3d51SMarkus Hoffrogge } else { 1572762023dSMarkus Hoffrogge throw new \Exception($this->handleRepoPathError( 1582762023dSMarkus Hoffrogge $repo_path, 1592762023dSMarkus Hoffrogge 'cannot create repository in non-existent directory' 1602762023dSMarkus Hoffrogge )); 161b08c3d51SMarkus Hoffrogge } 162b08c3d51SMarkus Hoffrogge } else { 1632762023dSMarkus Hoffrogge throw new \Exception($this->handleRepoPathError( 1642762023dSMarkus Hoffrogge $repo_path, 1652762023dSMarkus Hoffrogge '"' . $repo_path . '" does not exist' 1662762023dSMarkus Hoffrogge )); 167b08c3d51SMarkus Hoffrogge } 168b08c3d51SMarkus Hoffrogge } 169b08c3d51SMarkus Hoffrogge } 170b08c3d51SMarkus Hoffrogge 171b08c3d51SMarkus Hoffrogge /** 172b08c3d51SMarkus Hoffrogge * Get the path to the git repo directory (eg. the ".git" directory) 173b08c3d51SMarkus Hoffrogge * 174b08c3d51SMarkus Hoffrogge * @access public 175b08c3d51SMarkus Hoffrogge * @return string 176b08c3d51SMarkus Hoffrogge */ 1772762023dSMarkus Hoffrogge public function gitDirectoryPath() 1782762023dSMarkus Hoffrogge { 179b08c3d51SMarkus Hoffrogge return ($this->bare) ? $this->repo_path : $this->repo_path . "/.git"; 180b08c3d51SMarkus Hoffrogge } 181b08c3d51SMarkus Hoffrogge 182b08c3d51SMarkus Hoffrogge /** 183b08c3d51SMarkus Hoffrogge * Tests if git is installed 184b08c3d51SMarkus Hoffrogge * 185b08c3d51SMarkus Hoffrogge * @access public 186b08c3d51SMarkus Hoffrogge * @return bool 187b08c3d51SMarkus Hoffrogge */ 1882762023dSMarkus Hoffrogge public function testGit() 1892762023dSMarkus Hoffrogge { 190*c365e7dbSmhoffrog $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; 191*c365e7dbSmhoffrog $pipes = []; 1922762023dSMarkus Hoffrogge $resource = proc_open(Git::getBin(), $descriptorspec, $pipes); 193b08c3d51SMarkus Hoffrogge 194*c365e7dbSmhoffrog stream_get_contents($pipes[1]); 195*c365e7dbSmhoffrog stream_get_contents($pipes[2]); 196b08c3d51SMarkus Hoffrogge foreach ($pipes as $pipe) { 197b08c3d51SMarkus Hoffrogge fclose($pipe); 198b08c3d51SMarkus Hoffrogge } 199b08c3d51SMarkus Hoffrogge 200b08c3d51SMarkus Hoffrogge $status = trim(proc_close($resource)); 201b08c3d51SMarkus Hoffrogge return ($status != 127); 202b08c3d51SMarkus Hoffrogge } 203b08c3d51SMarkus Hoffrogge 204b08c3d51SMarkus Hoffrogge /** 205b08c3d51SMarkus Hoffrogge * Run a command in the git repository 206b08c3d51SMarkus Hoffrogge * 207b08c3d51SMarkus Hoffrogge * Accepts a shell command to run 208b08c3d51SMarkus Hoffrogge * 209b08c3d51SMarkus Hoffrogge * @access protected 210b08c3d51SMarkus Hoffrogge * @param string command to run 211b08c3d51SMarkus Hoffrogge * @return string or null in case of an error 212b08c3d51SMarkus Hoffrogge */ 2132762023dSMarkus Hoffrogge protected function runCommand($command) 2142762023dSMarkus Hoffrogge { 2152762023dSMarkus Hoffrogge //dbglog("Git->runCommand(command=[".$command."])"); 216*c365e7dbSmhoffrog $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; 217*c365e7dbSmhoffrog $pipes = []; 218b08c3d51SMarkus Hoffrogge $cwd = $this->repo_path; 219b08c3d51SMarkus Hoffrogge //dbglog("GitBacked - cwd: [".$cwd."]"); 220b08c3d51SMarkus Hoffrogge /* Provide any $this->envopts via putenv 221b08c3d51SMarkus Hoffrogge * and call proc_open with env=null to inherit the rest 222b08c3d51SMarkus Hoffrogge * of env variables from the original process of the system. 223b08c3d51SMarkus Hoffrogge * Note: Variables set by putenv live for a 224b08c3d51SMarkus Hoffrogge * single PHP request run only. These variables 225b08c3d51SMarkus Hoffrogge * are visible "locally". They are NOT listed by getenv(), 226b08c3d51SMarkus Hoffrogge * but they are visible to the process forked by proc_open(). 227b08c3d51SMarkus Hoffrogge */ 228b08c3d51SMarkus Hoffrogge foreach ($this->envopts as $k => $v) { 229b08c3d51SMarkus Hoffrogge putenv(sprintf("%s=%s", $k, $v)); 230b08c3d51SMarkus Hoffrogge } 231b08c3d51SMarkus Hoffrogge $resource = proc_open($command, $descriptorspec, $pipes, $cwd, null); 232b08c3d51SMarkus Hoffrogge 233b08c3d51SMarkus Hoffrogge $stdout = stream_get_contents($pipes[1]); 234b08c3d51SMarkus Hoffrogge $stderr = stream_get_contents($pipes[2]); 235b08c3d51SMarkus Hoffrogge foreach ($pipes as $pipe) { 236b08c3d51SMarkus Hoffrogge fclose($pipe); 237b08c3d51SMarkus Hoffrogge } 238b08c3d51SMarkus Hoffrogge 239b08c3d51SMarkus Hoffrogge $status = trim(proc_close($resource)); 2402762023dSMarkus Hoffrogge //dbglog("GitBacked: runCommand status: ".$status); 241b08c3d51SMarkus Hoffrogge if ($status) { 242b08c3d51SMarkus Hoffrogge //dbglog("GitBacked - stderr: [".$stderr."]"); 243b08c3d51SMarkus Hoffrogge // Remove a probable password from the Git URL, if the URL is contained in the error message 2442762023dSMarkus Hoffrogge $error_message = preg_replace( 2452762023dSMarkus Hoffrogge $this::REGEX_GIT_URL_FILTER_PWD, 2462762023dSMarkus Hoffrogge $this::REGEX_GIT_URL_FILTER_PWD_REPLACE_PATTERN, 2472762023dSMarkus Hoffrogge $stderr 2482762023dSMarkus Hoffrogge ); 249b08c3d51SMarkus Hoffrogge //dbglog("GitBacked - error_message: [".$error_message."]"); 2502762023dSMarkus Hoffrogge throw new \Exception($this->handleCommandError( 2512762023dSMarkus Hoffrogge $this->repo_path, 2522762023dSMarkus Hoffrogge $cwd, 2532762023dSMarkus Hoffrogge $command, 2542762023dSMarkus Hoffrogge $status, 2552762023dSMarkus Hoffrogge $error_message 2562762023dSMarkus Hoffrogge )); 257b08c3d51SMarkus Hoffrogge } else { 2582762023dSMarkus Hoffrogge $this->handleCommandSuccess($this->repo_path, $cwd, $command); 259b08c3d51SMarkus Hoffrogge } 260b08c3d51SMarkus Hoffrogge 261b08c3d51SMarkus Hoffrogge return $stdout; 262b08c3d51SMarkus Hoffrogge } 263b08c3d51SMarkus Hoffrogge 264b08c3d51SMarkus Hoffrogge /** 265b08c3d51SMarkus Hoffrogge * Run a git command in the git repository 266b08c3d51SMarkus Hoffrogge * 267b08c3d51SMarkus Hoffrogge * Accepts a git command to run 268b08c3d51SMarkus Hoffrogge * 269b08c3d51SMarkus Hoffrogge * @access public 270b08c3d51SMarkus Hoffrogge * @param string command to run 271b08c3d51SMarkus Hoffrogge * @return string 272b08c3d51SMarkus Hoffrogge */ 2732762023dSMarkus Hoffrogge public function run($command) 2742762023dSMarkus Hoffrogge { 2752762023dSMarkus Hoffrogge return $this->runCommand(Git::getBin() . " " . $command); 276b08c3d51SMarkus Hoffrogge } 277b08c3d51SMarkus Hoffrogge 278b08c3d51SMarkus Hoffrogge /** 279b08c3d51SMarkus Hoffrogge * Handles error on create_new 280b08c3d51SMarkus Hoffrogge * 281b08c3d51SMarkus Hoffrogge * @access protected 282b08c3d51SMarkus Hoffrogge * @param string repository path 283b08c3d51SMarkus Hoffrogge * @param string error message 284b08c3d51SMarkus Hoffrogge * @return string error message 285b08c3d51SMarkus Hoffrogge */ 2862762023dSMarkus Hoffrogge protected static function handleCreateNewError($repo_path, $reference, $error_message, $plugin) 2872762023dSMarkus Hoffrogge { 288b08c3d51SMarkus Hoffrogge if ($plugin instanceof \action_plugin_gitbacked_editcommit) { 2892762023dSMarkus Hoffrogge $plugin->notifyCreateNewError($repo_path, $reference, $error_message); 290b08c3d51SMarkus Hoffrogge } 291b08c3d51SMarkus Hoffrogge return $error_message; 292b08c3d51SMarkus Hoffrogge } 293b08c3d51SMarkus Hoffrogge 294b08c3d51SMarkus Hoffrogge /** 295b08c3d51SMarkus Hoffrogge * Handles error on setting the repo path 296b08c3d51SMarkus Hoffrogge * 297b08c3d51SMarkus Hoffrogge * @access protected 298b08c3d51SMarkus Hoffrogge * @param string repository path 299b08c3d51SMarkus Hoffrogge * @param string error message 300b08c3d51SMarkus Hoffrogge * @return string error message 301b08c3d51SMarkus Hoffrogge */ 3022762023dSMarkus Hoffrogge protected function handleRepoPathError($repo_path, $error_message) 3032762023dSMarkus Hoffrogge { 304b08c3d51SMarkus Hoffrogge if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { 3052762023dSMarkus Hoffrogge $this->plugin->notifyRepoPathError($repo_path, $error_message); 306b08c3d51SMarkus Hoffrogge } 307b08c3d51SMarkus Hoffrogge return $error_message; 308b08c3d51SMarkus Hoffrogge } 309b08c3d51SMarkus Hoffrogge 310b08c3d51SMarkus Hoffrogge /** 311b08c3d51SMarkus Hoffrogge * Handles error on git command 312b08c3d51SMarkus Hoffrogge * 313b08c3d51SMarkus Hoffrogge * @access protected 314b08c3d51SMarkus Hoffrogge * @param string repository path 315b08c3d51SMarkus Hoffrogge * @param string current working dir 316b08c3d51SMarkus Hoffrogge * @param string command line 317b08c3d51SMarkus Hoffrogge * @param int exit code of command (status) 318b08c3d51SMarkus Hoffrogge * @param string error message 319b08c3d51SMarkus Hoffrogge * @return string error message 320b08c3d51SMarkus Hoffrogge */ 3212762023dSMarkus Hoffrogge protected function handleCommandError($repo_path, $cwd, $command, $status, $error_message) 3222762023dSMarkus Hoffrogge { 323b08c3d51SMarkus Hoffrogge if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { 3242762023dSMarkus Hoffrogge $this->plugin->notifyCommandError($repo_path, $cwd, $command, $status, $error_message); 325b08c3d51SMarkus Hoffrogge } 326b08c3d51SMarkus Hoffrogge return $error_message; 327b08c3d51SMarkus Hoffrogge } 328b08c3d51SMarkus Hoffrogge 329b08c3d51SMarkus Hoffrogge /** 330b08c3d51SMarkus Hoffrogge * Handles success on git command 331b08c3d51SMarkus Hoffrogge * 332b08c3d51SMarkus Hoffrogge * @access protected 333b08c3d51SMarkus Hoffrogge * @param string repository path 334b08c3d51SMarkus Hoffrogge * @param string current working dir 335b08c3d51SMarkus Hoffrogge * @param string command line 336b08c3d51SMarkus Hoffrogge * @return void 337b08c3d51SMarkus Hoffrogge */ 3382762023dSMarkus Hoffrogge protected function handleCommandSuccess($repo_path, $cwd, $command) 3392762023dSMarkus Hoffrogge { 340b08c3d51SMarkus Hoffrogge if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { 3412762023dSMarkus Hoffrogge $this->plugin->notifyCommandSuccess($repo_path, $cwd, $command); 342b08c3d51SMarkus Hoffrogge } 343b08c3d51SMarkus Hoffrogge } 344b08c3d51SMarkus Hoffrogge 345b08c3d51SMarkus Hoffrogge /** 346b08c3d51SMarkus Hoffrogge * Runs a 'git status' call 347b08c3d51SMarkus Hoffrogge * 348b08c3d51SMarkus Hoffrogge * Accept a convert to HTML bool 349b08c3d51SMarkus Hoffrogge * 350b08c3d51SMarkus Hoffrogge * @access public 351b08c3d51SMarkus Hoffrogge * @param bool return string with <br /> 352b08c3d51SMarkus Hoffrogge * @return string 353b08c3d51SMarkus Hoffrogge */ 3542762023dSMarkus Hoffrogge public function status($html = false) 3552762023dSMarkus Hoffrogge { 356b08c3d51SMarkus Hoffrogge $msg = $this->run("status"); 357b08c3d51SMarkus Hoffrogge if ($html == true) { 358b08c3d51SMarkus Hoffrogge $msg = str_replace("\n", "<br />", $msg); 359b08c3d51SMarkus Hoffrogge } 360b08c3d51SMarkus Hoffrogge return $msg; 361b08c3d51SMarkus Hoffrogge } 362b08c3d51SMarkus Hoffrogge 363b08c3d51SMarkus Hoffrogge /** 364b08c3d51SMarkus Hoffrogge * Runs a `git add` call 365b08c3d51SMarkus Hoffrogge * 366b08c3d51SMarkus Hoffrogge * Accepts a list of files to add 367b08c3d51SMarkus Hoffrogge * 368b08c3d51SMarkus Hoffrogge * @access public 369b08c3d51SMarkus Hoffrogge * @param mixed files to add 370b08c3d51SMarkus Hoffrogge * @return string 371b08c3d51SMarkus Hoffrogge */ 3722762023dSMarkus Hoffrogge public function add($files = "*") 3732762023dSMarkus Hoffrogge { 374b08c3d51SMarkus Hoffrogge if (is_array($files)) { 375b08c3d51SMarkus Hoffrogge $files = '"' . implode('" "', $files) . '"'; 376b08c3d51SMarkus Hoffrogge } 377b08c3d51SMarkus Hoffrogge return $this->run("add $files -v"); 378b08c3d51SMarkus Hoffrogge } 379b08c3d51SMarkus Hoffrogge 380b08c3d51SMarkus Hoffrogge /** 381b08c3d51SMarkus Hoffrogge * Runs a `git rm` call 382b08c3d51SMarkus Hoffrogge * 383b08c3d51SMarkus Hoffrogge * Accepts a list of files to remove 384b08c3d51SMarkus Hoffrogge * 385b08c3d51SMarkus Hoffrogge * @access public 386b08c3d51SMarkus Hoffrogge * @param mixed files to remove 387b08c3d51SMarkus Hoffrogge * @param Boolean use the --cached flag? 388b08c3d51SMarkus Hoffrogge * @return string 389b08c3d51SMarkus Hoffrogge */ 3902762023dSMarkus Hoffrogge public function rm($files = "*", $cached = false) 3912762023dSMarkus Hoffrogge { 392b08c3d51SMarkus Hoffrogge if (is_array($files)) { 393b08c3d51SMarkus Hoffrogge $files = '"' . implode('" "', $files) . '"'; 394b08c3d51SMarkus Hoffrogge } 395b08c3d51SMarkus Hoffrogge return $this->run("rm " . ($cached ? '--cached ' : '') . $files); 396b08c3d51SMarkus Hoffrogge } 397b08c3d51SMarkus Hoffrogge 398b08c3d51SMarkus Hoffrogge 399b08c3d51SMarkus Hoffrogge /** 400b08c3d51SMarkus Hoffrogge * Runs a `git commit` call 401b08c3d51SMarkus Hoffrogge * 402b08c3d51SMarkus Hoffrogge * Accepts a commit message string 403b08c3d51SMarkus Hoffrogge * 404b08c3d51SMarkus Hoffrogge * @access public 405b08c3d51SMarkus Hoffrogge * @param string commit message 406b08c3d51SMarkus Hoffrogge * @param boolean should all files be committed automatically (-a flag) 407b08c3d51SMarkus Hoffrogge * @return string 408b08c3d51SMarkus Hoffrogge */ 4092762023dSMarkus Hoffrogge public function commit($message = "", $commit_all = true) 4102762023dSMarkus Hoffrogge { 411b08c3d51SMarkus Hoffrogge $flags = $commit_all ? '-av' : '-v'; 412b08c3d51SMarkus Hoffrogge $msgfile = GitBackedUtil::createMessageFile($message); 413b08c3d51SMarkus Hoffrogge try { 414b08c3d51SMarkus Hoffrogge return $this->run("commit --allow-empty " . $flags . " --file=" . $msgfile); 415b08c3d51SMarkus Hoffrogge } finally { 416b08c3d51SMarkus Hoffrogge unlink($msgfile); 417b08c3d51SMarkus Hoffrogge } 418b08c3d51SMarkus Hoffrogge } 419b08c3d51SMarkus Hoffrogge 420b08c3d51SMarkus Hoffrogge /** 421b08c3d51SMarkus Hoffrogge * Runs a `git clone` call to clone the current repository 422b08c3d51SMarkus Hoffrogge * into a different directory 423b08c3d51SMarkus Hoffrogge * 424b08c3d51SMarkus Hoffrogge * Accepts a target directory 425b08c3d51SMarkus Hoffrogge * 426b08c3d51SMarkus Hoffrogge * @access public 427b08c3d51SMarkus Hoffrogge * @param string target directory 428b08c3d51SMarkus Hoffrogge * @return string 429b08c3d51SMarkus Hoffrogge */ 4302762023dSMarkus Hoffrogge public function cloneTo($target) 4312762023dSMarkus Hoffrogge { 432b08c3d51SMarkus Hoffrogge return $this->run("clone --local " . $this->repo_path . " $target"); 433b08c3d51SMarkus Hoffrogge } 434b08c3d51SMarkus Hoffrogge 435b08c3d51SMarkus Hoffrogge /** 436b08c3d51SMarkus Hoffrogge * Runs a `git clone` call to clone a different repository 437b08c3d51SMarkus Hoffrogge * into the current repository 438b08c3d51SMarkus Hoffrogge * 439b08c3d51SMarkus Hoffrogge * Accepts a source directory 440b08c3d51SMarkus Hoffrogge * 441b08c3d51SMarkus Hoffrogge * @access public 442b08c3d51SMarkus Hoffrogge * @param string source directory 443b08c3d51SMarkus Hoffrogge * @return string 444b08c3d51SMarkus Hoffrogge */ 4452762023dSMarkus Hoffrogge public function cloneFrom($source) 4462762023dSMarkus Hoffrogge { 447b08c3d51SMarkus Hoffrogge return $this->run("clone --local $source " . $this->repo_path); 448b08c3d51SMarkus Hoffrogge } 449b08c3d51SMarkus Hoffrogge 450b08c3d51SMarkus Hoffrogge /** 451b08c3d51SMarkus Hoffrogge * Runs a `git clone` call to clone a remote repository 452b08c3d51SMarkus Hoffrogge * into the current repository 453b08c3d51SMarkus Hoffrogge * 454b08c3d51SMarkus Hoffrogge * Accepts a source url 455b08c3d51SMarkus Hoffrogge * 456b08c3d51SMarkus Hoffrogge * @access public 457b08c3d51SMarkus Hoffrogge * @param string source url 458b08c3d51SMarkus Hoffrogge * @param string reference path 459b08c3d51SMarkus Hoffrogge * @return string 460b08c3d51SMarkus Hoffrogge */ 4612762023dSMarkus Hoffrogge public function cloneRemote($source, $reference) 4622762023dSMarkus Hoffrogge { 463b08c3d51SMarkus Hoffrogge return $this->run("clone $reference $source " . $this->repo_path); 464b08c3d51SMarkus Hoffrogge } 465b08c3d51SMarkus Hoffrogge 466b08c3d51SMarkus Hoffrogge /** 467b08c3d51SMarkus Hoffrogge * Runs a `git clean` call 468b08c3d51SMarkus Hoffrogge * 469b08c3d51SMarkus Hoffrogge * Accepts a remove directories flag 470b08c3d51SMarkus Hoffrogge * 471b08c3d51SMarkus Hoffrogge * @access public 472b08c3d51SMarkus Hoffrogge * @param bool delete directories? 473b08c3d51SMarkus Hoffrogge * @param bool force clean? 474b08c3d51SMarkus Hoffrogge * @return string 475b08c3d51SMarkus Hoffrogge */ 4762762023dSMarkus Hoffrogge public function clean($dirs = false, $force = false) 4772762023dSMarkus Hoffrogge { 478b08c3d51SMarkus Hoffrogge return $this->run("clean" . (($force) ? " -f" : "") . (($dirs) ? " -d" : "")); 479b08c3d51SMarkus Hoffrogge } 480b08c3d51SMarkus Hoffrogge 481b08c3d51SMarkus Hoffrogge /** 482b08c3d51SMarkus Hoffrogge * Runs a `git branch` call 483b08c3d51SMarkus Hoffrogge * 484b08c3d51SMarkus Hoffrogge * Accepts a name for the branch 485b08c3d51SMarkus Hoffrogge * 486b08c3d51SMarkus Hoffrogge * @access public 487b08c3d51SMarkus Hoffrogge * @param string branch name 488b08c3d51SMarkus Hoffrogge * @return string 489b08c3d51SMarkus Hoffrogge */ 4902762023dSMarkus Hoffrogge public function createBranch($branch) 4912762023dSMarkus Hoffrogge { 492b08c3d51SMarkus Hoffrogge return $this->run("branch $branch"); 493b08c3d51SMarkus Hoffrogge } 494b08c3d51SMarkus Hoffrogge 495b08c3d51SMarkus Hoffrogge /** 496b08c3d51SMarkus Hoffrogge * Runs a `git branch -[d|D]` call 497b08c3d51SMarkus Hoffrogge * 498b08c3d51SMarkus Hoffrogge * Accepts a name for the branch 499b08c3d51SMarkus Hoffrogge * 500b08c3d51SMarkus Hoffrogge * @access public 501b08c3d51SMarkus Hoffrogge * @param string branch name 502b08c3d51SMarkus Hoffrogge * @return string 503b08c3d51SMarkus Hoffrogge */ 5042762023dSMarkus Hoffrogge public function deleteBranch($branch, $force = false) 5052762023dSMarkus Hoffrogge { 506b08c3d51SMarkus Hoffrogge return $this->run("branch " . (($force) ? '-D' : '-d') . " $branch"); 507b08c3d51SMarkus Hoffrogge } 508b08c3d51SMarkus Hoffrogge 509b08c3d51SMarkus Hoffrogge /** 510b08c3d51SMarkus Hoffrogge * Runs a `git branch` call 511b08c3d51SMarkus Hoffrogge * 512b08c3d51SMarkus Hoffrogge * @access public 513b08c3d51SMarkus Hoffrogge * @param bool keep asterisk mark on active branch 514b08c3d51SMarkus Hoffrogge * @return array 515b08c3d51SMarkus Hoffrogge */ 5162762023dSMarkus Hoffrogge public function listBranches($keep_asterisk = false) 5172762023dSMarkus Hoffrogge { 518b08c3d51SMarkus Hoffrogge $branchArray = explode("\n", $this->run("branch")); 519b08c3d51SMarkus Hoffrogge foreach ($branchArray as $i => &$branch) { 520b08c3d51SMarkus Hoffrogge $branch = trim($branch); 521b08c3d51SMarkus Hoffrogge if (! $keep_asterisk) { 522b08c3d51SMarkus Hoffrogge $branch = str_replace("* ", "", $branch); 523b08c3d51SMarkus Hoffrogge } 524b08c3d51SMarkus Hoffrogge if ($branch == "") { 525b08c3d51SMarkus Hoffrogge unset($branchArray[$i]); 526b08c3d51SMarkus Hoffrogge } 527b08c3d51SMarkus Hoffrogge } 528b08c3d51SMarkus Hoffrogge return $branchArray; 529b08c3d51SMarkus Hoffrogge } 530b08c3d51SMarkus Hoffrogge 531b08c3d51SMarkus Hoffrogge /** 532b08c3d51SMarkus Hoffrogge * Lists remote branches (using `git branch -r`). 533b08c3d51SMarkus Hoffrogge * 534b08c3d51SMarkus Hoffrogge * Also strips out the HEAD reference (e.g. "origin/HEAD -> origin/master"). 535b08c3d51SMarkus Hoffrogge * 536b08c3d51SMarkus Hoffrogge * @access public 537b08c3d51SMarkus Hoffrogge * @return array 538b08c3d51SMarkus Hoffrogge */ 5392762023dSMarkus Hoffrogge public function listRemoteBranches() 5402762023dSMarkus Hoffrogge { 541b08c3d51SMarkus Hoffrogge $branchArray = explode("\n", $this->run("branch -r")); 542b08c3d51SMarkus Hoffrogge foreach ($branchArray as $i => &$branch) { 543b08c3d51SMarkus Hoffrogge $branch = trim($branch); 544b08c3d51SMarkus Hoffrogge if ($branch == "" || strpos($branch, 'HEAD -> ') !== false) { 545b08c3d51SMarkus Hoffrogge unset($branchArray[$i]); 546b08c3d51SMarkus Hoffrogge } 547b08c3d51SMarkus Hoffrogge } 548b08c3d51SMarkus Hoffrogge return $branchArray; 549b08c3d51SMarkus Hoffrogge } 550b08c3d51SMarkus Hoffrogge 551b08c3d51SMarkus Hoffrogge /** 552b08c3d51SMarkus Hoffrogge * Returns name of active branch 553b08c3d51SMarkus Hoffrogge * 554b08c3d51SMarkus Hoffrogge * @access public 555b08c3d51SMarkus Hoffrogge * @param bool keep asterisk mark on branch name 556b08c3d51SMarkus Hoffrogge * @return string 557b08c3d51SMarkus Hoffrogge */ 5582762023dSMarkus Hoffrogge public function activeBranch($keep_asterisk = false) 5592762023dSMarkus Hoffrogge { 5602762023dSMarkus Hoffrogge $branchArray = $this->listBranches(true); 5612762023dSMarkus Hoffrogge $activeBranch = preg_grep("/^\*/", $branchArray); 5622762023dSMarkus Hoffrogge reset($activeBranch); 563b08c3d51SMarkus Hoffrogge if ($keep_asterisk) { 5642762023dSMarkus Hoffrogge return current($activeBranch); 565b08c3d51SMarkus Hoffrogge } else { 5662762023dSMarkus Hoffrogge return str_replace("* ", "", current($activeBranch)); 567b08c3d51SMarkus Hoffrogge } 568b08c3d51SMarkus Hoffrogge } 569b08c3d51SMarkus Hoffrogge 570b08c3d51SMarkus Hoffrogge /** 571b08c3d51SMarkus Hoffrogge * Runs a `git checkout` call 572b08c3d51SMarkus Hoffrogge * 573b08c3d51SMarkus Hoffrogge * Accepts a name for the branch 574b08c3d51SMarkus Hoffrogge * 575b08c3d51SMarkus Hoffrogge * @access public 576b08c3d51SMarkus Hoffrogge * @param string branch name 577b08c3d51SMarkus Hoffrogge * @return string 578b08c3d51SMarkus Hoffrogge */ 5792762023dSMarkus Hoffrogge public function checkout($branch) 5802762023dSMarkus Hoffrogge { 581b08c3d51SMarkus Hoffrogge return $this->run("checkout $branch"); 582b08c3d51SMarkus Hoffrogge } 583b08c3d51SMarkus Hoffrogge 584b08c3d51SMarkus Hoffrogge 585b08c3d51SMarkus Hoffrogge /** 586b08c3d51SMarkus Hoffrogge * Runs a `git merge` call 587b08c3d51SMarkus Hoffrogge * 588b08c3d51SMarkus Hoffrogge * Accepts a name for the branch to be merged 589b08c3d51SMarkus Hoffrogge * 590b08c3d51SMarkus Hoffrogge * @access public 591b08c3d51SMarkus Hoffrogge * @param string $branch 592b08c3d51SMarkus Hoffrogge * @return string 593b08c3d51SMarkus Hoffrogge */ 5942762023dSMarkus Hoffrogge public function merge($branch) 5952762023dSMarkus Hoffrogge { 596b08c3d51SMarkus Hoffrogge return $this->run("merge $branch --no-ff"); 597b08c3d51SMarkus Hoffrogge } 598b08c3d51SMarkus Hoffrogge 599b08c3d51SMarkus Hoffrogge 600b08c3d51SMarkus Hoffrogge /** 601b08c3d51SMarkus Hoffrogge * Runs a git fetch on the current branch 602b08c3d51SMarkus Hoffrogge * 603b08c3d51SMarkus Hoffrogge * @access public 604b08c3d51SMarkus Hoffrogge * @return string 605b08c3d51SMarkus Hoffrogge */ 6062762023dSMarkus Hoffrogge public function fetch() 6072762023dSMarkus Hoffrogge { 608b08c3d51SMarkus Hoffrogge return $this->run("fetch"); 609b08c3d51SMarkus Hoffrogge } 610b08c3d51SMarkus Hoffrogge 611b08c3d51SMarkus Hoffrogge /** 612b08c3d51SMarkus Hoffrogge * Add a new tag on the current position 613b08c3d51SMarkus Hoffrogge * 614b08c3d51SMarkus Hoffrogge * Accepts the name for the tag and the message 615b08c3d51SMarkus Hoffrogge * 616b08c3d51SMarkus Hoffrogge * @param string $tag 617b08c3d51SMarkus Hoffrogge * @param string $message 618b08c3d51SMarkus Hoffrogge * @return string 619b08c3d51SMarkus Hoffrogge */ 6202762023dSMarkus Hoffrogge public function addTag($tag, $message = null) 6212762023dSMarkus Hoffrogge { 622b08c3d51SMarkus Hoffrogge if ($message === null) { 623b08c3d51SMarkus Hoffrogge $message = $tag; 624b08c3d51SMarkus Hoffrogge } 625b08c3d51SMarkus Hoffrogge $msgfile = GitBackedUtil::createMessageFile($message); 626b08c3d51SMarkus Hoffrogge try { 627b08c3d51SMarkus Hoffrogge return $this->run("tag -a $tag --file=" . $msgfile); 628b08c3d51SMarkus Hoffrogge } finally { 629b08c3d51SMarkus Hoffrogge unlink($msgfile); 630b08c3d51SMarkus Hoffrogge } 631b08c3d51SMarkus Hoffrogge } 632b08c3d51SMarkus Hoffrogge 633b08c3d51SMarkus Hoffrogge /** 634b08c3d51SMarkus Hoffrogge * List all the available repository tags. 635b08c3d51SMarkus Hoffrogge * 636b08c3d51SMarkus Hoffrogge * Optionally, accept a shell wildcard pattern and return only tags matching it. 637b08c3d51SMarkus Hoffrogge * 638b08c3d51SMarkus Hoffrogge * @access public 639b08c3d51SMarkus Hoffrogge * @param string $pattern Shell wildcard pattern to match tags against. 640b08c3d51SMarkus Hoffrogge * @return array Available repository tags. 641b08c3d51SMarkus Hoffrogge */ 6422762023dSMarkus Hoffrogge public function listTags($pattern = null) 6432762023dSMarkus Hoffrogge { 644b08c3d51SMarkus Hoffrogge $tagArray = explode("\n", $this->run("tag -l $pattern")); 645b08c3d51SMarkus Hoffrogge foreach ($tagArray as $i => &$tag) { 646b08c3d51SMarkus Hoffrogge $tag = trim($tag); 647b08c3d51SMarkus Hoffrogge if ($tag == '') { 648b08c3d51SMarkus Hoffrogge unset($tagArray[$i]); 649b08c3d51SMarkus Hoffrogge } 650b08c3d51SMarkus Hoffrogge } 651b08c3d51SMarkus Hoffrogge 652b08c3d51SMarkus Hoffrogge return $tagArray; 653b08c3d51SMarkus Hoffrogge } 654b08c3d51SMarkus Hoffrogge 655b08c3d51SMarkus Hoffrogge /** 656b08c3d51SMarkus Hoffrogge * Push specific branch to a remote 657b08c3d51SMarkus Hoffrogge * 658b08c3d51SMarkus Hoffrogge * Accepts the name of the remote and local branch 659b08c3d51SMarkus Hoffrogge * 660b08c3d51SMarkus Hoffrogge * @param string $remote 661b08c3d51SMarkus Hoffrogge * @param string $branch 662b08c3d51SMarkus Hoffrogge * @return string 663b08c3d51SMarkus Hoffrogge */ 6642762023dSMarkus Hoffrogge public function push($remote, $branch) 6652762023dSMarkus Hoffrogge { 666b08c3d51SMarkus Hoffrogge return $this->run("push --tags $remote $branch"); 667b08c3d51SMarkus Hoffrogge } 668b08c3d51SMarkus Hoffrogge 669b08c3d51SMarkus Hoffrogge /** 670b08c3d51SMarkus Hoffrogge * Pull specific branch from remote 671b08c3d51SMarkus Hoffrogge * 672b08c3d51SMarkus Hoffrogge * Accepts the name of the remote and local branch 673b08c3d51SMarkus Hoffrogge * 674b08c3d51SMarkus Hoffrogge * @param string $remote 675b08c3d51SMarkus Hoffrogge * @param string $branch 676b08c3d51SMarkus Hoffrogge * @return string 677b08c3d51SMarkus Hoffrogge */ 6782762023dSMarkus Hoffrogge public function pull($remote, $branch) 6792762023dSMarkus Hoffrogge { 680b08c3d51SMarkus Hoffrogge return $this->run("pull $remote $branch"); 681b08c3d51SMarkus Hoffrogge } 682b08c3d51SMarkus Hoffrogge 683b08c3d51SMarkus Hoffrogge /** 684b08c3d51SMarkus Hoffrogge * List log entries. 685b08c3d51SMarkus Hoffrogge * 686b08c3d51SMarkus Hoffrogge * @param strgin $format 687b08c3d51SMarkus Hoffrogge * @return string 688b08c3d51SMarkus Hoffrogge */ 6892762023dSMarkus Hoffrogge public function log($format = null) 6902762023dSMarkus Hoffrogge { 6912762023dSMarkus Hoffrogge if ($format === null) { 692b08c3d51SMarkus Hoffrogge return $this->run('log'); 6932762023dSMarkus Hoffrogge } else { 694b08c3d51SMarkus Hoffrogge return $this->run('log --pretty=format:"' . $format . '"'); 695b08c3d51SMarkus Hoffrogge } 6962762023dSMarkus Hoffrogge } 697b08c3d51SMarkus Hoffrogge 698b08c3d51SMarkus Hoffrogge /** 699b08c3d51SMarkus Hoffrogge * Sets the project description. 700b08c3d51SMarkus Hoffrogge * 701b08c3d51SMarkus Hoffrogge * @param string $new 702b08c3d51SMarkus Hoffrogge */ 7032762023dSMarkus Hoffrogge public function setDescription($new) 7042762023dSMarkus Hoffrogge { 7052762023dSMarkus Hoffrogge $path = $this->gitDirectoryPath(); 706b08c3d51SMarkus Hoffrogge file_put_contents($path . "/description", $new); 707b08c3d51SMarkus Hoffrogge } 708b08c3d51SMarkus Hoffrogge 709b08c3d51SMarkus Hoffrogge /** 710b08c3d51SMarkus Hoffrogge * Gets the project description. 711b08c3d51SMarkus Hoffrogge * 712b08c3d51SMarkus Hoffrogge * @return string 713b08c3d51SMarkus Hoffrogge */ 7142762023dSMarkus Hoffrogge public function getDescription() 7152762023dSMarkus Hoffrogge { 7162762023dSMarkus Hoffrogge $path = $this->gitDirectoryPath(); 717b08c3d51SMarkus Hoffrogge return file_get_contents($path . "/description"); 718b08c3d51SMarkus Hoffrogge } 719b08c3d51SMarkus Hoffrogge 720b08c3d51SMarkus Hoffrogge /** 721b08c3d51SMarkus Hoffrogge * Sets custom environment options for calling Git 722b08c3d51SMarkus Hoffrogge * 723b08c3d51SMarkus Hoffrogge * @param string key 724b08c3d51SMarkus Hoffrogge * @param string value 725b08c3d51SMarkus Hoffrogge */ 7262762023dSMarkus Hoffrogge public function setenv($key, $value) 7272762023dSMarkus Hoffrogge { 728b08c3d51SMarkus Hoffrogge $this->envopts[$key] = $value; 729b08c3d51SMarkus Hoffrogge } 730b08c3d51SMarkus Hoffrogge} 731b08c3d51SMarkus Hoffrogge 732b08c3d51SMarkus Hoffrogge/* End of file */ 733