1*b08c3d51SMarkus Hoffrogge<?php 2*b08c3d51SMarkus Hoffrogge 3*b08c3d51SMarkus Hoffrogge/* 4*b08c3d51SMarkus Hoffrogge * Git.php 5*b08c3d51SMarkus Hoffrogge * 6*b08c3d51SMarkus Hoffrogge * A PHP git library 7*b08c3d51SMarkus Hoffrogge * 8*b08c3d51SMarkus Hoffrogge * @package Git.php 9*b08c3d51SMarkus Hoffrogge * @version 0.1.4 10*b08c3d51SMarkus Hoffrogge * @author James Brumond 11*b08c3d51SMarkus Hoffrogge * @copyright Copyright 2013 James Brumond 12*b08c3d51SMarkus Hoffrogge * @repo http://github.com/kbjr/Git.php 13*b08c3d51SMarkus Hoffrogge */ 14*b08c3d51SMarkus Hoffrogge 15*b08c3d51SMarkus Hoffroggeif (__FILE__ == $_SERVER['SCRIPT_FILENAME']) die('Bad load order'); 16*b08c3d51SMarkus Hoffrogge 17*b08c3d51SMarkus Hoffrogge// ------------------------------------------------------------------------ 18*b08c3d51SMarkus Hoffrogge 19*b08c3d51SMarkus Hoffrogge/** 20*b08c3d51SMarkus Hoffrogge * Git Repository Interface Class 21*b08c3d51SMarkus Hoffrogge * 22*b08c3d51SMarkus Hoffrogge * This class enables the creating, reading, and manipulation 23*b08c3d51SMarkus Hoffrogge * of a git repository 24*b08c3d51SMarkus Hoffrogge * 25*b08c3d51SMarkus Hoffrogge * @class GitRepo 26*b08c3d51SMarkus Hoffrogge */ 27*b08c3d51SMarkus Hoffroggeclass GitRepo { 28*b08c3d51SMarkus Hoffrogge 29*b08c3d51SMarkus Hoffrogge // This regex will filter a probable password from any string containing a Git URL. 30*b08c3d51SMarkus Hoffrogge // Limitation: it will work for the first git URL occurrence in a string. 31*b08c3d51SMarkus Hoffrogge // Used https://regex101.com/ for evaluating! 32*b08c3d51SMarkus Hoffrogge const REGEX_GIT_URL_FILTER_PWD = "/^(.*)((http:)|(https:))([^:]+)(:[^@]*)?(.*)/im"; 33*b08c3d51SMarkus Hoffrogge const REGEX_GIT_URL_FILTER_PWD_REPLACE_PATTERN = "$1$2$5$7"; 34*b08c3d51SMarkus Hoffrogge 35*b08c3d51SMarkus Hoffrogge protected $repo_path = null; 36*b08c3d51SMarkus Hoffrogge protected $bare = false; 37*b08c3d51SMarkus Hoffrogge protected $envopts = array(); 38*b08c3d51SMarkus Hoffrogge // Fix for PHP <=7.3 compatibility: Type declarations for properties work since PHP >= 7.4 only. 39*b08c3d51SMarkus Hoffrogge // protected ?\action_plugin_gitbacked_editcommit $plugin = null; 40*b08c3d51SMarkus Hoffrogge protected $plugin = null; 41*b08c3d51SMarkus Hoffrogge 42*b08c3d51SMarkus Hoffrogge /** 43*b08c3d51SMarkus Hoffrogge * Create a new git repository 44*b08c3d51SMarkus Hoffrogge * 45*b08c3d51SMarkus Hoffrogge * Accepts a creation path, and, optionally, a source path 46*b08c3d51SMarkus Hoffrogge * 47*b08c3d51SMarkus Hoffrogge * @access public 48*b08c3d51SMarkus Hoffrogge * @param string repository path 49*b08c3d51SMarkus Hoffrogge * @param \action_plugin_gitbacked_editcommit plugin 50*b08c3d51SMarkus Hoffrogge * @param string directory to source 51*b08c3d51SMarkus Hoffrogge * @param string reference path 52*b08c3d51SMarkus Hoffrogge * @return GitRepo or null in case of an error 53*b08c3d51SMarkus Hoffrogge */ 54*b08c3d51SMarkus Hoffrogge public static function &create_new($repo_path, \action_plugin_gitbacked_editcommit $plugin = null, $source = null, $remote_source = false, $reference = null) { 55*b08c3d51SMarkus Hoffrogge if (is_dir($repo_path) && file_exists($repo_path."/.git") && is_dir($repo_path."/.git")) { 56*b08c3d51SMarkus Hoffrogge throw new Exception(self::handle_create_new_error($repo_path, $reference, '"'.$repo_path.'" is already a git repository', $plugin)); 57*b08c3d51SMarkus Hoffrogge } else { 58*b08c3d51SMarkus Hoffrogge $repo = new self($repo_path, $plugin, true, false); 59*b08c3d51SMarkus Hoffrogge if (is_string($source)) { 60*b08c3d51SMarkus Hoffrogge if ($remote_source) { 61*b08c3d51SMarkus Hoffrogge if (!is_dir($reference) || !is_dir($reference.'/.git')) { 62*b08c3d51SMarkus Hoffrogge throw new Exception(self::handle_create_new_error($repo_path, $reference, '"'.$reference.'" is not a git repository. Cannot use as reference.', $plugin)); 63*b08c3d51SMarkus Hoffrogge } else if (strlen($reference)) { 64*b08c3d51SMarkus Hoffrogge $reference = realpath($reference); 65*b08c3d51SMarkus Hoffrogge $reference = "--reference $reference"; 66*b08c3d51SMarkus Hoffrogge } 67*b08c3d51SMarkus Hoffrogge $repo->clone_remote($source, $reference); 68*b08c3d51SMarkus Hoffrogge } else { 69*b08c3d51SMarkus Hoffrogge $repo->clone_from($source); 70*b08c3d51SMarkus Hoffrogge } 71*b08c3d51SMarkus Hoffrogge } else { 72*b08c3d51SMarkus Hoffrogge $repo->run('init'); 73*b08c3d51SMarkus Hoffrogge } 74*b08c3d51SMarkus Hoffrogge return $repo; 75*b08c3d51SMarkus Hoffrogge } 76*b08c3d51SMarkus Hoffrogge } 77*b08c3d51SMarkus Hoffrogge 78*b08c3d51SMarkus Hoffrogge /** 79*b08c3d51SMarkus Hoffrogge * Constructor 80*b08c3d51SMarkus Hoffrogge * 81*b08c3d51SMarkus Hoffrogge * Accepts a repository path 82*b08c3d51SMarkus Hoffrogge * 83*b08c3d51SMarkus Hoffrogge * @access public 84*b08c3d51SMarkus Hoffrogge * @param string repository path 85*b08c3d51SMarkus Hoffrogge * @param \action_plugin_gitbacked_editcommit plugin 86*b08c3d51SMarkus Hoffrogge * @param bool create if not exists? 87*b08c3d51SMarkus Hoffrogge * @return void 88*b08c3d51SMarkus Hoffrogge */ 89*b08c3d51SMarkus Hoffrogge public function __construct($repo_path = null, \action_plugin_gitbacked_editcommit $plugin = null, $create_new = false, $_init = true) { 90*b08c3d51SMarkus Hoffrogge $this->plugin = $plugin; 91*b08c3d51SMarkus Hoffrogge if (is_string($repo_path)) { 92*b08c3d51SMarkus Hoffrogge $this->set_repo_path($repo_path, $create_new, $_init); 93*b08c3d51SMarkus Hoffrogge } 94*b08c3d51SMarkus Hoffrogge } 95*b08c3d51SMarkus Hoffrogge 96*b08c3d51SMarkus Hoffrogge /** 97*b08c3d51SMarkus Hoffrogge * Set the repository's path 98*b08c3d51SMarkus Hoffrogge * 99*b08c3d51SMarkus Hoffrogge * Accepts the repository path 100*b08c3d51SMarkus Hoffrogge * 101*b08c3d51SMarkus Hoffrogge * @access public 102*b08c3d51SMarkus Hoffrogge * @param string repository path 103*b08c3d51SMarkus Hoffrogge * @param bool create if not exists? 104*b08c3d51SMarkus Hoffrogge * @param bool initialize new Git repo if not exists? 105*b08c3d51SMarkus Hoffrogge * @return void 106*b08c3d51SMarkus Hoffrogge */ 107*b08c3d51SMarkus Hoffrogge public function set_repo_path($repo_path, $create_new = false, $_init = true) { 108*b08c3d51SMarkus Hoffrogge if (is_string($repo_path)) { 109*b08c3d51SMarkus Hoffrogge if ($new_path = realpath($repo_path)) { 110*b08c3d51SMarkus Hoffrogge $repo_path = $new_path; 111*b08c3d51SMarkus Hoffrogge if (is_dir($repo_path)) { 112*b08c3d51SMarkus Hoffrogge // Is this a work tree? 113*b08c3d51SMarkus Hoffrogge if (file_exists($repo_path."/.git") && is_dir($repo_path."/.git")) { 114*b08c3d51SMarkus Hoffrogge $this->repo_path = $repo_path; 115*b08c3d51SMarkus Hoffrogge $this->bare = false; 116*b08c3d51SMarkus Hoffrogge // Is this a bare repo? 117*b08c3d51SMarkus Hoffrogge } else if (is_file($repo_path."/config")) { 118*b08c3d51SMarkus Hoffrogge $parse_ini = parse_ini_file($repo_path."/config"); 119*b08c3d51SMarkus Hoffrogge if ($parse_ini['bare']) { 120*b08c3d51SMarkus Hoffrogge $this->repo_path = $repo_path; 121*b08c3d51SMarkus Hoffrogge $this->bare = true; 122*b08c3d51SMarkus Hoffrogge } 123*b08c3d51SMarkus Hoffrogge } else { 124*b08c3d51SMarkus Hoffrogge if ($create_new) { 125*b08c3d51SMarkus Hoffrogge $this->repo_path = $repo_path; 126*b08c3d51SMarkus Hoffrogge if ($_init) { 127*b08c3d51SMarkus Hoffrogge $this->run('init'); 128*b08c3d51SMarkus Hoffrogge } 129*b08c3d51SMarkus Hoffrogge } else { 130*b08c3d51SMarkus Hoffrogge throw new Exception($this->handle_repo_path_error($repo_path, '"'.$repo_path.'" is not a git repository')); 131*b08c3d51SMarkus Hoffrogge } 132*b08c3d51SMarkus Hoffrogge } 133*b08c3d51SMarkus Hoffrogge } else { 134*b08c3d51SMarkus Hoffrogge throw new Exception($this->handle_repo_path_error($repo_path, '"'.$repo_path.'" is not a directory')); 135*b08c3d51SMarkus Hoffrogge } 136*b08c3d51SMarkus Hoffrogge } else { 137*b08c3d51SMarkus Hoffrogge if ($create_new) { 138*b08c3d51SMarkus Hoffrogge if ($parent = realpath(dirname($repo_path))) { 139*b08c3d51SMarkus Hoffrogge mkdir($repo_path); 140*b08c3d51SMarkus Hoffrogge $this->repo_path = $repo_path; 141*b08c3d51SMarkus Hoffrogge if ($_init) $this->run('init'); 142*b08c3d51SMarkus Hoffrogge } else { 143*b08c3d51SMarkus Hoffrogge throw new Exception($this->handle_repo_path_error($repo_path, 'cannot create repository in non-existent directory')); 144*b08c3d51SMarkus Hoffrogge } 145*b08c3d51SMarkus Hoffrogge } else { 146*b08c3d51SMarkus Hoffrogge throw new Exception($this->handle_repo_path_error($repo_path, '"'.$repo_path.'" does not exist')); 147*b08c3d51SMarkus Hoffrogge } 148*b08c3d51SMarkus Hoffrogge } 149*b08c3d51SMarkus Hoffrogge } 150*b08c3d51SMarkus Hoffrogge } 151*b08c3d51SMarkus Hoffrogge 152*b08c3d51SMarkus Hoffrogge /** 153*b08c3d51SMarkus Hoffrogge * Get the path to the git repo directory (eg. the ".git" directory) 154*b08c3d51SMarkus Hoffrogge * 155*b08c3d51SMarkus Hoffrogge * @access public 156*b08c3d51SMarkus Hoffrogge * @return string 157*b08c3d51SMarkus Hoffrogge */ 158*b08c3d51SMarkus Hoffrogge public function git_directory_path() { 159*b08c3d51SMarkus Hoffrogge return ($this->bare) ? $this->repo_path : $this->repo_path."/.git"; 160*b08c3d51SMarkus Hoffrogge } 161*b08c3d51SMarkus Hoffrogge 162*b08c3d51SMarkus Hoffrogge /** 163*b08c3d51SMarkus Hoffrogge * Tests if git is installed 164*b08c3d51SMarkus Hoffrogge * 165*b08c3d51SMarkus Hoffrogge * @access public 166*b08c3d51SMarkus Hoffrogge * @return bool 167*b08c3d51SMarkus Hoffrogge */ 168*b08c3d51SMarkus Hoffrogge public function test_git() { 169*b08c3d51SMarkus Hoffrogge $descriptorspec = array( 170*b08c3d51SMarkus Hoffrogge 1 => array('pipe', 'w'), 171*b08c3d51SMarkus Hoffrogge 2 => array('pipe', 'w'), 172*b08c3d51SMarkus Hoffrogge ); 173*b08c3d51SMarkus Hoffrogge $pipes = array(); 174*b08c3d51SMarkus Hoffrogge $resource = proc_open(Git::get_bin(), $descriptorspec, $pipes); 175*b08c3d51SMarkus Hoffrogge 176*b08c3d51SMarkus Hoffrogge $stdout = stream_get_contents($pipes[1]); 177*b08c3d51SMarkus Hoffrogge $stderr = stream_get_contents($pipes[2]); 178*b08c3d51SMarkus Hoffrogge foreach ($pipes as $pipe) { 179*b08c3d51SMarkus Hoffrogge fclose($pipe); 180*b08c3d51SMarkus Hoffrogge } 181*b08c3d51SMarkus Hoffrogge 182*b08c3d51SMarkus Hoffrogge $status = trim(proc_close($resource)); 183*b08c3d51SMarkus Hoffrogge return ($status != 127); 184*b08c3d51SMarkus Hoffrogge } 185*b08c3d51SMarkus Hoffrogge 186*b08c3d51SMarkus Hoffrogge /** 187*b08c3d51SMarkus Hoffrogge * Run a command in the git repository 188*b08c3d51SMarkus Hoffrogge * 189*b08c3d51SMarkus Hoffrogge * Accepts a shell command to run 190*b08c3d51SMarkus Hoffrogge * 191*b08c3d51SMarkus Hoffrogge * @access protected 192*b08c3d51SMarkus Hoffrogge * @param string command to run 193*b08c3d51SMarkus Hoffrogge * @return string or null in case of an error 194*b08c3d51SMarkus Hoffrogge */ 195*b08c3d51SMarkus Hoffrogge protected function run_command($command) { 196*b08c3d51SMarkus Hoffrogge //dbglog("Git->run_command(command=[".$command."])"); 197*b08c3d51SMarkus Hoffrogge $descriptorspec = array( 198*b08c3d51SMarkus Hoffrogge 1 => array('pipe', 'w'), 199*b08c3d51SMarkus Hoffrogge 2 => array('pipe', 'w'), 200*b08c3d51SMarkus Hoffrogge ); 201*b08c3d51SMarkus Hoffrogge $pipes = array(); 202*b08c3d51SMarkus Hoffrogge $cwd = $this->repo_path; 203*b08c3d51SMarkus Hoffrogge //dbglog("GitBacked - cwd: [".$cwd."]"); 204*b08c3d51SMarkus Hoffrogge /* Provide any $this->envopts via putenv 205*b08c3d51SMarkus Hoffrogge * and call proc_open with env=null to inherit the rest 206*b08c3d51SMarkus Hoffrogge * of env variables from the original process of the system. 207*b08c3d51SMarkus Hoffrogge * Note: Variables set by putenv live for a 208*b08c3d51SMarkus Hoffrogge * single PHP request run only. These variables 209*b08c3d51SMarkus Hoffrogge * are visible "locally". They are NOT listed by getenv(), 210*b08c3d51SMarkus Hoffrogge * but they are visible to the process forked by proc_open(). 211*b08c3d51SMarkus Hoffrogge */ 212*b08c3d51SMarkus Hoffrogge foreach($this->envopts as $k => $v) { 213*b08c3d51SMarkus Hoffrogge putenv(sprintf("%s=%s",$k,$v)); 214*b08c3d51SMarkus Hoffrogge } 215*b08c3d51SMarkus Hoffrogge $resource = proc_open($command, $descriptorspec, $pipes, $cwd, null); 216*b08c3d51SMarkus Hoffrogge 217*b08c3d51SMarkus Hoffrogge $stdout = stream_get_contents($pipes[1]); 218*b08c3d51SMarkus Hoffrogge $stderr = stream_get_contents($pipes[2]); 219*b08c3d51SMarkus Hoffrogge foreach ($pipes as $pipe) { 220*b08c3d51SMarkus Hoffrogge fclose($pipe); 221*b08c3d51SMarkus Hoffrogge } 222*b08c3d51SMarkus Hoffrogge 223*b08c3d51SMarkus Hoffrogge $status = trim(proc_close($resource)); 224*b08c3d51SMarkus Hoffrogge //dbglog("GitBacked: run_command status: ".$status); 225*b08c3d51SMarkus Hoffrogge if ($status) { 226*b08c3d51SMarkus Hoffrogge //dbglog("GitBacked - stderr: [".$stderr."]"); 227*b08c3d51SMarkus Hoffrogge // Remove a probable password from the Git URL, if the URL is contained in the error message 228*b08c3d51SMarkus Hoffrogge $error_message = preg_replace($this::REGEX_GIT_URL_FILTER_PWD, $this::REGEX_GIT_URL_FILTER_PWD_REPLACE_PATTERN, $stderr); 229*b08c3d51SMarkus Hoffrogge //dbglog("GitBacked - error_message: [".$error_message."]"); 230*b08c3d51SMarkus Hoffrogge throw new Exception($this->handle_command_error($this->repo_path, $cwd, $command, $status, $error_message)); 231*b08c3d51SMarkus Hoffrogge } else { 232*b08c3d51SMarkus Hoffrogge $this->handle_command_success($this->repo_path, $cwd, $command); 233*b08c3d51SMarkus Hoffrogge } 234*b08c3d51SMarkus Hoffrogge 235*b08c3d51SMarkus Hoffrogge return $stdout; 236*b08c3d51SMarkus Hoffrogge } 237*b08c3d51SMarkus Hoffrogge 238*b08c3d51SMarkus Hoffrogge /** 239*b08c3d51SMarkus Hoffrogge * Run a git command in the git repository 240*b08c3d51SMarkus Hoffrogge * 241*b08c3d51SMarkus Hoffrogge * Accepts a git command to run 242*b08c3d51SMarkus Hoffrogge * 243*b08c3d51SMarkus Hoffrogge * @access public 244*b08c3d51SMarkus Hoffrogge * @param string command to run 245*b08c3d51SMarkus Hoffrogge * @return string 246*b08c3d51SMarkus Hoffrogge */ 247*b08c3d51SMarkus Hoffrogge public function run($command) { 248*b08c3d51SMarkus Hoffrogge return $this->run_command(Git::get_bin()." ".$command); 249*b08c3d51SMarkus Hoffrogge } 250*b08c3d51SMarkus Hoffrogge 251*b08c3d51SMarkus Hoffrogge /** 252*b08c3d51SMarkus Hoffrogge * Handles error on create_new 253*b08c3d51SMarkus Hoffrogge * 254*b08c3d51SMarkus Hoffrogge * @access protected 255*b08c3d51SMarkus Hoffrogge * @param string repository path 256*b08c3d51SMarkus Hoffrogge * @param string error message 257*b08c3d51SMarkus Hoffrogge * @return string error message 258*b08c3d51SMarkus Hoffrogge */ 259*b08c3d51SMarkus Hoffrogge protected static function handle_create_new_error($repo_path, $reference, $error_message, $plugin) { 260*b08c3d51SMarkus Hoffrogge if ($plugin instanceof \action_plugin_gitbacked_editcommit) { 261*b08c3d51SMarkus Hoffrogge $plugin->notify_create_new_error($repo_path, $reference, $error_message); 262*b08c3d51SMarkus Hoffrogge } 263*b08c3d51SMarkus Hoffrogge return $error_message; 264*b08c3d51SMarkus Hoffrogge } 265*b08c3d51SMarkus Hoffrogge 266*b08c3d51SMarkus Hoffrogge /** 267*b08c3d51SMarkus Hoffrogge * Handles error on setting the repo path 268*b08c3d51SMarkus Hoffrogge * 269*b08c3d51SMarkus Hoffrogge * @access protected 270*b08c3d51SMarkus Hoffrogge * @param string repository path 271*b08c3d51SMarkus Hoffrogge * @param string error message 272*b08c3d51SMarkus Hoffrogge * @return string error message 273*b08c3d51SMarkus Hoffrogge */ 274*b08c3d51SMarkus Hoffrogge protected function handle_repo_path_error($repo_path, $error_message) { 275*b08c3d51SMarkus Hoffrogge if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { 276*b08c3d51SMarkus Hoffrogge $this->plugin->notify_repo_path_error($repo_path, $error_message); 277*b08c3d51SMarkus Hoffrogge } 278*b08c3d51SMarkus Hoffrogge return $error_message; 279*b08c3d51SMarkus Hoffrogge } 280*b08c3d51SMarkus Hoffrogge 281*b08c3d51SMarkus Hoffrogge /** 282*b08c3d51SMarkus Hoffrogge * Handles error on git command 283*b08c3d51SMarkus Hoffrogge * 284*b08c3d51SMarkus Hoffrogge * @access protected 285*b08c3d51SMarkus Hoffrogge * @param string repository path 286*b08c3d51SMarkus Hoffrogge * @param string current working dir 287*b08c3d51SMarkus Hoffrogge * @param string command line 288*b08c3d51SMarkus Hoffrogge * @param int exit code of command (status) 289*b08c3d51SMarkus Hoffrogge * @param string error message 290*b08c3d51SMarkus Hoffrogge * @return string error message 291*b08c3d51SMarkus Hoffrogge */ 292*b08c3d51SMarkus Hoffrogge protected function handle_command_error($repo_path, $cwd, $command, $status, $error_message) { 293*b08c3d51SMarkus Hoffrogge if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { 294*b08c3d51SMarkus Hoffrogge $this->plugin->notify_command_error($repo_path, $cwd, $command, $status, $error_message); 295*b08c3d51SMarkus Hoffrogge } 296*b08c3d51SMarkus Hoffrogge return $error_message; 297*b08c3d51SMarkus Hoffrogge } 298*b08c3d51SMarkus Hoffrogge 299*b08c3d51SMarkus Hoffrogge /** 300*b08c3d51SMarkus Hoffrogge * Handles success on git command 301*b08c3d51SMarkus Hoffrogge * 302*b08c3d51SMarkus Hoffrogge * @access protected 303*b08c3d51SMarkus Hoffrogge * @param string repository path 304*b08c3d51SMarkus Hoffrogge * @param string current working dir 305*b08c3d51SMarkus Hoffrogge * @param string command line 306*b08c3d51SMarkus Hoffrogge * @return void 307*b08c3d51SMarkus Hoffrogge */ 308*b08c3d51SMarkus Hoffrogge protected function handle_command_success($repo_path, $cwd, $command) { 309*b08c3d51SMarkus Hoffrogge if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { 310*b08c3d51SMarkus Hoffrogge $this->plugin->notify_command_success($repo_path, $cwd, $command); 311*b08c3d51SMarkus Hoffrogge } 312*b08c3d51SMarkus Hoffrogge } 313*b08c3d51SMarkus Hoffrogge 314*b08c3d51SMarkus Hoffrogge /** 315*b08c3d51SMarkus Hoffrogge * Runs a 'git status' call 316*b08c3d51SMarkus Hoffrogge * 317*b08c3d51SMarkus Hoffrogge * Accept a convert to HTML bool 318*b08c3d51SMarkus Hoffrogge * 319*b08c3d51SMarkus Hoffrogge * @access public 320*b08c3d51SMarkus Hoffrogge * @param bool return string with <br /> 321*b08c3d51SMarkus Hoffrogge * @return string 322*b08c3d51SMarkus Hoffrogge */ 323*b08c3d51SMarkus Hoffrogge public function status($html = false) { 324*b08c3d51SMarkus Hoffrogge $msg = $this->run("status"); 325*b08c3d51SMarkus Hoffrogge if ($html == true) { 326*b08c3d51SMarkus Hoffrogge $msg = str_replace("\n", "<br />", $msg); 327*b08c3d51SMarkus Hoffrogge } 328*b08c3d51SMarkus Hoffrogge return $msg; 329*b08c3d51SMarkus Hoffrogge } 330*b08c3d51SMarkus Hoffrogge 331*b08c3d51SMarkus Hoffrogge /** 332*b08c3d51SMarkus Hoffrogge * Runs a `git add` call 333*b08c3d51SMarkus Hoffrogge * 334*b08c3d51SMarkus Hoffrogge * Accepts a list of files to add 335*b08c3d51SMarkus Hoffrogge * 336*b08c3d51SMarkus Hoffrogge * @access public 337*b08c3d51SMarkus Hoffrogge * @param mixed files to add 338*b08c3d51SMarkus Hoffrogge * @return string 339*b08c3d51SMarkus Hoffrogge */ 340*b08c3d51SMarkus Hoffrogge public function add($files = "*") { 341*b08c3d51SMarkus Hoffrogge if (is_array($files)) { 342*b08c3d51SMarkus Hoffrogge $files = '"'.implode('" "', $files).'"'; 343*b08c3d51SMarkus Hoffrogge } 344*b08c3d51SMarkus Hoffrogge return $this->run("add $files -v"); 345*b08c3d51SMarkus Hoffrogge } 346*b08c3d51SMarkus Hoffrogge 347*b08c3d51SMarkus Hoffrogge /** 348*b08c3d51SMarkus Hoffrogge * Runs a `git rm` call 349*b08c3d51SMarkus Hoffrogge * 350*b08c3d51SMarkus Hoffrogge * Accepts a list of files to remove 351*b08c3d51SMarkus Hoffrogge * 352*b08c3d51SMarkus Hoffrogge * @access public 353*b08c3d51SMarkus Hoffrogge * @param mixed files to remove 354*b08c3d51SMarkus Hoffrogge * @param Boolean use the --cached flag? 355*b08c3d51SMarkus Hoffrogge * @return string 356*b08c3d51SMarkus Hoffrogge */ 357*b08c3d51SMarkus Hoffrogge public function rm($files = "*", $cached = false) { 358*b08c3d51SMarkus Hoffrogge if (is_array($files)) { 359*b08c3d51SMarkus Hoffrogge $files = '"'.implode('" "', $files).'"'; 360*b08c3d51SMarkus Hoffrogge } 361*b08c3d51SMarkus Hoffrogge return $this->run("rm ".($cached ? '--cached ' : '').$files); 362*b08c3d51SMarkus Hoffrogge } 363*b08c3d51SMarkus Hoffrogge 364*b08c3d51SMarkus Hoffrogge 365*b08c3d51SMarkus Hoffrogge /** 366*b08c3d51SMarkus Hoffrogge * Runs a `git commit` call 367*b08c3d51SMarkus Hoffrogge * 368*b08c3d51SMarkus Hoffrogge * Accepts a commit message string 369*b08c3d51SMarkus Hoffrogge * 370*b08c3d51SMarkus Hoffrogge * @access public 371*b08c3d51SMarkus Hoffrogge * @param string commit message 372*b08c3d51SMarkus Hoffrogge * @param boolean should all files be committed automatically (-a flag) 373*b08c3d51SMarkus Hoffrogge * @return string 374*b08c3d51SMarkus Hoffrogge */ 375*b08c3d51SMarkus Hoffrogge public function commit($message = "", $commit_all = true) { 376*b08c3d51SMarkus Hoffrogge $flags = $commit_all ? '-av' : '-v'; 377*b08c3d51SMarkus Hoffrogge $msgfile = GitBackedUtil::createMessageFile($message); 378*b08c3d51SMarkus Hoffrogge try { 379*b08c3d51SMarkus Hoffrogge return $this->run("commit --allow-empty ".$flags." --file=".$msgfile); 380*b08c3d51SMarkus Hoffrogge } finally { 381*b08c3d51SMarkus Hoffrogge unlink($msgfile); 382*b08c3d51SMarkus Hoffrogge } 383*b08c3d51SMarkus Hoffrogge } 384*b08c3d51SMarkus Hoffrogge 385*b08c3d51SMarkus Hoffrogge /** 386*b08c3d51SMarkus Hoffrogge * Runs a `git clone` call to clone the current repository 387*b08c3d51SMarkus Hoffrogge * into a different directory 388*b08c3d51SMarkus Hoffrogge * 389*b08c3d51SMarkus Hoffrogge * Accepts a target directory 390*b08c3d51SMarkus Hoffrogge * 391*b08c3d51SMarkus Hoffrogge * @access public 392*b08c3d51SMarkus Hoffrogge * @param string target directory 393*b08c3d51SMarkus Hoffrogge * @return string 394*b08c3d51SMarkus Hoffrogge */ 395*b08c3d51SMarkus Hoffrogge public function clone_to($target) { 396*b08c3d51SMarkus Hoffrogge return $this->run("clone --local ".$this->repo_path." $target"); 397*b08c3d51SMarkus Hoffrogge } 398*b08c3d51SMarkus Hoffrogge 399*b08c3d51SMarkus Hoffrogge /** 400*b08c3d51SMarkus Hoffrogge * Runs a `git clone` call to clone a different repository 401*b08c3d51SMarkus Hoffrogge * into the current repository 402*b08c3d51SMarkus Hoffrogge * 403*b08c3d51SMarkus Hoffrogge * Accepts a source directory 404*b08c3d51SMarkus Hoffrogge * 405*b08c3d51SMarkus Hoffrogge * @access public 406*b08c3d51SMarkus Hoffrogge * @param string source directory 407*b08c3d51SMarkus Hoffrogge * @return string 408*b08c3d51SMarkus Hoffrogge */ 409*b08c3d51SMarkus Hoffrogge public function clone_from($source) { 410*b08c3d51SMarkus Hoffrogge return $this->run("clone --local $source ".$this->repo_path); 411*b08c3d51SMarkus Hoffrogge } 412*b08c3d51SMarkus Hoffrogge 413*b08c3d51SMarkus Hoffrogge /** 414*b08c3d51SMarkus Hoffrogge * Runs a `git clone` call to clone a remote repository 415*b08c3d51SMarkus Hoffrogge * into the current repository 416*b08c3d51SMarkus Hoffrogge * 417*b08c3d51SMarkus Hoffrogge * Accepts a source url 418*b08c3d51SMarkus Hoffrogge * 419*b08c3d51SMarkus Hoffrogge * @access public 420*b08c3d51SMarkus Hoffrogge * @param string source url 421*b08c3d51SMarkus Hoffrogge * @param string reference path 422*b08c3d51SMarkus Hoffrogge * @return string 423*b08c3d51SMarkus Hoffrogge */ 424*b08c3d51SMarkus Hoffrogge public function clone_remote($source, $reference) { 425*b08c3d51SMarkus Hoffrogge return $this->run("clone $reference $source ".$this->repo_path); 426*b08c3d51SMarkus Hoffrogge } 427*b08c3d51SMarkus Hoffrogge 428*b08c3d51SMarkus Hoffrogge /** 429*b08c3d51SMarkus Hoffrogge * Runs a `git clean` call 430*b08c3d51SMarkus Hoffrogge * 431*b08c3d51SMarkus Hoffrogge * Accepts a remove directories flag 432*b08c3d51SMarkus Hoffrogge * 433*b08c3d51SMarkus Hoffrogge * @access public 434*b08c3d51SMarkus Hoffrogge * @param bool delete directories? 435*b08c3d51SMarkus Hoffrogge * @param bool force clean? 436*b08c3d51SMarkus Hoffrogge * @return string 437*b08c3d51SMarkus Hoffrogge */ 438*b08c3d51SMarkus Hoffrogge public function clean($dirs = false, $force = false) { 439*b08c3d51SMarkus Hoffrogge return $this->run("clean".(($force) ? " -f" : "").(($dirs) ? " -d" : "")); 440*b08c3d51SMarkus Hoffrogge } 441*b08c3d51SMarkus Hoffrogge 442*b08c3d51SMarkus Hoffrogge /** 443*b08c3d51SMarkus Hoffrogge * Runs a `git branch` call 444*b08c3d51SMarkus Hoffrogge * 445*b08c3d51SMarkus Hoffrogge * Accepts a name for the branch 446*b08c3d51SMarkus Hoffrogge * 447*b08c3d51SMarkus Hoffrogge * @access public 448*b08c3d51SMarkus Hoffrogge * @param string branch name 449*b08c3d51SMarkus Hoffrogge * @return string 450*b08c3d51SMarkus Hoffrogge */ 451*b08c3d51SMarkus Hoffrogge public function create_branch($branch) { 452*b08c3d51SMarkus Hoffrogge return $this->run("branch $branch"); 453*b08c3d51SMarkus Hoffrogge } 454*b08c3d51SMarkus Hoffrogge 455*b08c3d51SMarkus Hoffrogge /** 456*b08c3d51SMarkus Hoffrogge * Runs a `git branch -[d|D]` call 457*b08c3d51SMarkus Hoffrogge * 458*b08c3d51SMarkus Hoffrogge * Accepts a name for the branch 459*b08c3d51SMarkus Hoffrogge * 460*b08c3d51SMarkus Hoffrogge * @access public 461*b08c3d51SMarkus Hoffrogge * @param string branch name 462*b08c3d51SMarkus Hoffrogge * @return string 463*b08c3d51SMarkus Hoffrogge */ 464*b08c3d51SMarkus Hoffrogge public function delete_branch($branch, $force = false) { 465*b08c3d51SMarkus Hoffrogge return $this->run("branch ".(($force) ? '-D' : '-d')." $branch"); 466*b08c3d51SMarkus Hoffrogge } 467*b08c3d51SMarkus Hoffrogge 468*b08c3d51SMarkus Hoffrogge /** 469*b08c3d51SMarkus Hoffrogge * Runs a `git branch` call 470*b08c3d51SMarkus Hoffrogge * 471*b08c3d51SMarkus Hoffrogge * @access public 472*b08c3d51SMarkus Hoffrogge * @param bool keep asterisk mark on active branch 473*b08c3d51SMarkus Hoffrogge * @return array 474*b08c3d51SMarkus Hoffrogge */ 475*b08c3d51SMarkus Hoffrogge public function list_branches($keep_asterisk = false) { 476*b08c3d51SMarkus Hoffrogge $branchArray = explode("\n", $this->run("branch")); 477*b08c3d51SMarkus Hoffrogge foreach($branchArray as $i => &$branch) { 478*b08c3d51SMarkus Hoffrogge $branch = trim($branch); 479*b08c3d51SMarkus Hoffrogge if (! $keep_asterisk) { 480*b08c3d51SMarkus Hoffrogge $branch = str_replace("* ", "", $branch); 481*b08c3d51SMarkus Hoffrogge } 482*b08c3d51SMarkus Hoffrogge if ($branch == "") { 483*b08c3d51SMarkus Hoffrogge unset($branchArray[$i]); 484*b08c3d51SMarkus Hoffrogge } 485*b08c3d51SMarkus Hoffrogge } 486*b08c3d51SMarkus Hoffrogge return $branchArray; 487*b08c3d51SMarkus Hoffrogge } 488*b08c3d51SMarkus Hoffrogge 489*b08c3d51SMarkus Hoffrogge /** 490*b08c3d51SMarkus Hoffrogge * Lists remote branches (using `git branch -r`). 491*b08c3d51SMarkus Hoffrogge * 492*b08c3d51SMarkus Hoffrogge * Also strips out the HEAD reference (e.g. "origin/HEAD -> origin/master"). 493*b08c3d51SMarkus Hoffrogge * 494*b08c3d51SMarkus Hoffrogge * @access public 495*b08c3d51SMarkus Hoffrogge * @return array 496*b08c3d51SMarkus Hoffrogge */ 497*b08c3d51SMarkus Hoffrogge public function list_remote_branches() { 498*b08c3d51SMarkus Hoffrogge $branchArray = explode("\n", $this->run("branch -r")); 499*b08c3d51SMarkus Hoffrogge foreach($branchArray as $i => &$branch) { 500*b08c3d51SMarkus Hoffrogge $branch = trim($branch); 501*b08c3d51SMarkus Hoffrogge if ($branch == "" || strpos($branch, 'HEAD -> ') !== false) { 502*b08c3d51SMarkus Hoffrogge unset($branchArray[$i]); 503*b08c3d51SMarkus Hoffrogge } 504*b08c3d51SMarkus Hoffrogge } 505*b08c3d51SMarkus Hoffrogge return $branchArray; 506*b08c3d51SMarkus Hoffrogge } 507*b08c3d51SMarkus Hoffrogge 508*b08c3d51SMarkus Hoffrogge /** 509*b08c3d51SMarkus Hoffrogge * Returns name of active branch 510*b08c3d51SMarkus Hoffrogge * 511*b08c3d51SMarkus Hoffrogge * @access public 512*b08c3d51SMarkus Hoffrogge * @param bool keep asterisk mark on branch name 513*b08c3d51SMarkus Hoffrogge * @return string 514*b08c3d51SMarkus Hoffrogge */ 515*b08c3d51SMarkus Hoffrogge public function active_branch($keep_asterisk = false) { 516*b08c3d51SMarkus Hoffrogge $branchArray = $this->list_branches(true); 517*b08c3d51SMarkus Hoffrogge $active_branch = preg_grep("/^\*/", $branchArray); 518*b08c3d51SMarkus Hoffrogge reset($active_branch); 519*b08c3d51SMarkus Hoffrogge if ($keep_asterisk) { 520*b08c3d51SMarkus Hoffrogge return current($active_branch); 521*b08c3d51SMarkus Hoffrogge } else { 522*b08c3d51SMarkus Hoffrogge return str_replace("* ", "", current($active_branch)); 523*b08c3d51SMarkus Hoffrogge } 524*b08c3d51SMarkus Hoffrogge } 525*b08c3d51SMarkus Hoffrogge 526*b08c3d51SMarkus Hoffrogge /** 527*b08c3d51SMarkus Hoffrogge * Runs a `git checkout` call 528*b08c3d51SMarkus Hoffrogge * 529*b08c3d51SMarkus Hoffrogge * Accepts a name for the branch 530*b08c3d51SMarkus Hoffrogge * 531*b08c3d51SMarkus Hoffrogge * @access public 532*b08c3d51SMarkus Hoffrogge * @param string branch name 533*b08c3d51SMarkus Hoffrogge * @return string 534*b08c3d51SMarkus Hoffrogge */ 535*b08c3d51SMarkus Hoffrogge public function checkout($branch) { 536*b08c3d51SMarkus Hoffrogge return $this->run("checkout $branch"); 537*b08c3d51SMarkus Hoffrogge } 538*b08c3d51SMarkus Hoffrogge 539*b08c3d51SMarkus Hoffrogge 540*b08c3d51SMarkus Hoffrogge /** 541*b08c3d51SMarkus Hoffrogge * Runs a `git merge` call 542*b08c3d51SMarkus Hoffrogge * 543*b08c3d51SMarkus Hoffrogge * Accepts a name for the branch to be merged 544*b08c3d51SMarkus Hoffrogge * 545*b08c3d51SMarkus Hoffrogge * @access public 546*b08c3d51SMarkus Hoffrogge * @param string $branch 547*b08c3d51SMarkus Hoffrogge * @return string 548*b08c3d51SMarkus Hoffrogge */ 549*b08c3d51SMarkus Hoffrogge public function merge($branch) { 550*b08c3d51SMarkus Hoffrogge return $this->run("merge $branch --no-ff"); 551*b08c3d51SMarkus Hoffrogge } 552*b08c3d51SMarkus Hoffrogge 553*b08c3d51SMarkus Hoffrogge 554*b08c3d51SMarkus Hoffrogge /** 555*b08c3d51SMarkus Hoffrogge * Runs a git fetch on the current branch 556*b08c3d51SMarkus Hoffrogge * 557*b08c3d51SMarkus Hoffrogge * @access public 558*b08c3d51SMarkus Hoffrogge * @return string 559*b08c3d51SMarkus Hoffrogge */ 560*b08c3d51SMarkus Hoffrogge public function fetch() { 561*b08c3d51SMarkus Hoffrogge return $this->run("fetch"); 562*b08c3d51SMarkus Hoffrogge } 563*b08c3d51SMarkus Hoffrogge 564*b08c3d51SMarkus Hoffrogge /** 565*b08c3d51SMarkus Hoffrogge * Add a new tag on the current position 566*b08c3d51SMarkus Hoffrogge * 567*b08c3d51SMarkus Hoffrogge * Accepts the name for the tag and the message 568*b08c3d51SMarkus Hoffrogge * 569*b08c3d51SMarkus Hoffrogge * @param string $tag 570*b08c3d51SMarkus Hoffrogge * @param string $message 571*b08c3d51SMarkus Hoffrogge * @return string 572*b08c3d51SMarkus Hoffrogge */ 573*b08c3d51SMarkus Hoffrogge public function add_tag($tag, $message = null) { 574*b08c3d51SMarkus Hoffrogge if ($message === null) { 575*b08c3d51SMarkus Hoffrogge $message = $tag; 576*b08c3d51SMarkus Hoffrogge } 577*b08c3d51SMarkus Hoffrogge $msgfile = GitBackedUtil::createMessageFile($message); 578*b08c3d51SMarkus Hoffrogge try { 579*b08c3d51SMarkus Hoffrogge return $this->run("tag -a $tag --file=".$msgfile); 580*b08c3d51SMarkus Hoffrogge } finally { 581*b08c3d51SMarkus Hoffrogge unlink($msgfile); 582*b08c3d51SMarkus Hoffrogge } 583*b08c3d51SMarkus Hoffrogge } 584*b08c3d51SMarkus Hoffrogge 585*b08c3d51SMarkus Hoffrogge /** 586*b08c3d51SMarkus Hoffrogge * List all the available repository tags. 587*b08c3d51SMarkus Hoffrogge * 588*b08c3d51SMarkus Hoffrogge * Optionally, accept a shell wildcard pattern and return only tags matching it. 589*b08c3d51SMarkus Hoffrogge * 590*b08c3d51SMarkus Hoffrogge * @access public 591*b08c3d51SMarkus Hoffrogge * @param string $pattern Shell wildcard pattern to match tags against. 592*b08c3d51SMarkus Hoffrogge * @return array Available repository tags. 593*b08c3d51SMarkus Hoffrogge */ 594*b08c3d51SMarkus Hoffrogge public function list_tags($pattern = null) { 595*b08c3d51SMarkus Hoffrogge $tagArray = explode("\n", $this->run("tag -l $pattern")); 596*b08c3d51SMarkus Hoffrogge foreach ($tagArray as $i => &$tag) { 597*b08c3d51SMarkus Hoffrogge $tag = trim($tag); 598*b08c3d51SMarkus Hoffrogge if ($tag == '') { 599*b08c3d51SMarkus Hoffrogge unset($tagArray[$i]); 600*b08c3d51SMarkus Hoffrogge } 601*b08c3d51SMarkus Hoffrogge } 602*b08c3d51SMarkus Hoffrogge 603*b08c3d51SMarkus Hoffrogge return $tagArray; 604*b08c3d51SMarkus Hoffrogge } 605*b08c3d51SMarkus Hoffrogge 606*b08c3d51SMarkus Hoffrogge /** 607*b08c3d51SMarkus Hoffrogge * Push specific branch to a remote 608*b08c3d51SMarkus Hoffrogge * 609*b08c3d51SMarkus Hoffrogge * Accepts the name of the remote and local branch 610*b08c3d51SMarkus Hoffrogge * 611*b08c3d51SMarkus Hoffrogge * @param string $remote 612*b08c3d51SMarkus Hoffrogge * @param string $branch 613*b08c3d51SMarkus Hoffrogge * @return string 614*b08c3d51SMarkus Hoffrogge */ 615*b08c3d51SMarkus Hoffrogge public function push($remote, $branch) { 616*b08c3d51SMarkus Hoffrogge return $this->run("push --tags $remote $branch"); 617*b08c3d51SMarkus Hoffrogge } 618*b08c3d51SMarkus Hoffrogge 619*b08c3d51SMarkus Hoffrogge /** 620*b08c3d51SMarkus Hoffrogge * Pull specific branch from remote 621*b08c3d51SMarkus Hoffrogge * 622*b08c3d51SMarkus Hoffrogge * Accepts the name of the remote and local branch 623*b08c3d51SMarkus Hoffrogge * 624*b08c3d51SMarkus Hoffrogge * @param string $remote 625*b08c3d51SMarkus Hoffrogge * @param string $branch 626*b08c3d51SMarkus Hoffrogge * @return string 627*b08c3d51SMarkus Hoffrogge */ 628*b08c3d51SMarkus Hoffrogge public function pull($remote, $branch) { 629*b08c3d51SMarkus Hoffrogge return $this->run("pull $remote $branch"); 630*b08c3d51SMarkus Hoffrogge } 631*b08c3d51SMarkus Hoffrogge 632*b08c3d51SMarkus Hoffrogge /** 633*b08c3d51SMarkus Hoffrogge * List log entries. 634*b08c3d51SMarkus Hoffrogge * 635*b08c3d51SMarkus Hoffrogge * @param strgin $format 636*b08c3d51SMarkus Hoffrogge * @return string 637*b08c3d51SMarkus Hoffrogge */ 638*b08c3d51SMarkus Hoffrogge public function log($format = null) { 639*b08c3d51SMarkus Hoffrogge if ($format === null) 640*b08c3d51SMarkus Hoffrogge return $this->run('log'); 641*b08c3d51SMarkus Hoffrogge else 642*b08c3d51SMarkus Hoffrogge return $this->run('log --pretty=format:"' . $format . '"'); 643*b08c3d51SMarkus Hoffrogge } 644*b08c3d51SMarkus Hoffrogge 645*b08c3d51SMarkus Hoffrogge /** 646*b08c3d51SMarkus Hoffrogge * Sets the project description. 647*b08c3d51SMarkus Hoffrogge * 648*b08c3d51SMarkus Hoffrogge * @param string $new 649*b08c3d51SMarkus Hoffrogge */ 650*b08c3d51SMarkus Hoffrogge public function set_description($new) { 651*b08c3d51SMarkus Hoffrogge $path = $this->git_directory_path(); 652*b08c3d51SMarkus Hoffrogge file_put_contents($path."/description", $new); 653*b08c3d51SMarkus Hoffrogge } 654*b08c3d51SMarkus Hoffrogge 655*b08c3d51SMarkus Hoffrogge /** 656*b08c3d51SMarkus Hoffrogge * Gets the project description. 657*b08c3d51SMarkus Hoffrogge * 658*b08c3d51SMarkus Hoffrogge * @return string 659*b08c3d51SMarkus Hoffrogge */ 660*b08c3d51SMarkus Hoffrogge public function get_description() { 661*b08c3d51SMarkus Hoffrogge $path = $this->git_directory_path(); 662*b08c3d51SMarkus Hoffrogge return file_get_contents($path."/description"); 663*b08c3d51SMarkus Hoffrogge } 664*b08c3d51SMarkus Hoffrogge 665*b08c3d51SMarkus Hoffrogge /** 666*b08c3d51SMarkus Hoffrogge * Sets custom environment options for calling Git 667*b08c3d51SMarkus Hoffrogge * 668*b08c3d51SMarkus Hoffrogge * @param string key 669*b08c3d51SMarkus Hoffrogge * @param string value 670*b08c3d51SMarkus Hoffrogge */ 671*b08c3d51SMarkus Hoffrogge public function setenv($key, $value) { 672*b08c3d51SMarkus Hoffrogge $this->envopts[$key] = $value; 673*b08c3d51SMarkus Hoffrogge } 674*b08c3d51SMarkus Hoffrogge 675*b08c3d51SMarkus Hoffrogge} 676*b08c3d51SMarkus Hoffrogge 677*b08c3d51SMarkus Hoffrogge/* End of file */ 678