1b08c3d51SMarkus Hoffrogge<?php 2b08c3d51SMarkus Hoffrogge 3*2762023dSMarkus Hoffroggenamespace woolfg\dokuwiki\plugin\gitbacked; 4b08c3d51SMarkus Hoffrogge 5*2762023dSMarkus 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 */ 16*2762023dSMarkus Hoffroggeclass GitRepo 17*2762023dSMarkus 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! 21*2762023dSMarkus Hoffrogge public const REGEX_GIT_URL_FILTER_PWD = "/^(.*)((http:)|(https:))([^:]+)(:[^@]*)?(.*)/im"; 22*2762023dSMarkus Hoffrogge public const REGEX_GIT_URL_FILTER_PWD_REPLACE_PATTERN = "$1$2$5$7"; 23b08c3d51SMarkus Hoffrogge 24b08c3d51SMarkus Hoffrogge protected $repo_path = null; 25b08c3d51SMarkus Hoffrogge protected $bare = false; 26b08c3d51SMarkus Hoffrogge protected $envopts = array(); 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; 29b08c3d51SMarkus Hoffrogge protected $plugin = null; 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 */ 43*2762023dSMarkus Hoffrogge public static function &createNew( 44*2762023dSMarkus Hoffrogge $repo_path, 45*2762023dSMarkus Hoffrogge \action_plugin_gitbacked_editcommit $plugin = null, 46*2762023dSMarkus Hoffrogge $source = null, 47*2762023dSMarkus Hoffrogge $remote_source = false, 48*2762023dSMarkus Hoffrogge $reference = null 49*2762023dSMarkus Hoffrogge ) { 50b08c3d51SMarkus Hoffrogge if (is_dir($repo_path) && file_exists($repo_path . "/.git") && is_dir($repo_path . "/.git")) { 51*2762023dSMarkus Hoffrogge throw new \Exception(self::handleCreateNewError( 52*2762023dSMarkus Hoffrogge $repo_path, 53*2762023dSMarkus Hoffrogge $reference, 54*2762023dSMarkus Hoffrogge '"' . $repo_path . '" is already a git repository', 55*2762023dSMarkus Hoffrogge $plugin 56*2762023dSMarkus 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')) { 62*2762023dSMarkus Hoffrogge throw new \Exception(self::handleCreateNewError( 63*2762023dSMarkus Hoffrogge $repo_path, 64*2762023dSMarkus Hoffrogge $reference, 65*2762023dSMarkus Hoffrogge '"' . $reference . '" is not a git repository. Cannot use as reference.', 66*2762023dSMarkus Hoffrogge $plugin 67*2762023dSMarkus Hoffrogge )); 68b08c3d51SMarkus Hoffrogge } elseif (strlen($reference)) { 69b08c3d51SMarkus Hoffrogge $reference = realpath($reference); 70b08c3d51SMarkus Hoffrogge $reference = "--reference $reference"; 71b08c3d51SMarkus Hoffrogge } 72*2762023dSMarkus Hoffrogge $repo->cloneRemote($source, $reference); 73b08c3d51SMarkus Hoffrogge } else { 74*2762023dSMarkus 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 */ 94*2762023dSMarkus Hoffrogge public function __construct( 95*2762023dSMarkus Hoffrogge $repo_path = null, 96*2762023dSMarkus Hoffrogge \action_plugin_gitbacked_editcommit $plugin = null, 97*2762023dSMarkus Hoffrogge $create_new = false, 98*2762023dSMarkus Hoffrogge $_init = true 99*2762023dSMarkus Hoffrogge ) { 100b08c3d51SMarkus Hoffrogge $this->plugin = $plugin; 101b08c3d51SMarkus Hoffrogge if (is_string($repo_path)) { 102*2762023dSMarkus 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 */ 117*2762023dSMarkus Hoffrogge public function setRepoPath($repo_path, $create_new = false, $_init = true) 118*2762023dSMarkus 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 } 134b08c3d51SMarkus Hoffrogge } else { 135b08c3d51SMarkus Hoffrogge if ($create_new) { 136b08c3d51SMarkus Hoffrogge $this->repo_path = $repo_path; 137b08c3d51SMarkus Hoffrogge if ($_init) { 138b08c3d51SMarkus Hoffrogge $this->run('init'); 139b08c3d51SMarkus Hoffrogge } 140b08c3d51SMarkus Hoffrogge } else { 141*2762023dSMarkus Hoffrogge throw new \Exception($this->handleRepoPathError( 142*2762023dSMarkus Hoffrogge $repo_path, 143*2762023dSMarkus Hoffrogge '"' . $repo_path . '" is not a git repository' 144*2762023dSMarkus Hoffrogge )); 145b08c3d51SMarkus Hoffrogge } 146b08c3d51SMarkus Hoffrogge } 147b08c3d51SMarkus Hoffrogge } else { 148*2762023dSMarkus Hoffrogge throw new \Exception($this->handleRepoPathError( 149*2762023dSMarkus Hoffrogge $repo_path, 150*2762023dSMarkus Hoffrogge '"' . $repo_path . '" is not a directory' 151*2762023dSMarkus Hoffrogge )); 152b08c3d51SMarkus Hoffrogge } 153b08c3d51SMarkus Hoffrogge } else { 154b08c3d51SMarkus Hoffrogge if ($create_new) { 155b08c3d51SMarkus Hoffrogge if ($parent = realpath(dirname($repo_path))) { 156b08c3d51SMarkus Hoffrogge mkdir($repo_path); 157b08c3d51SMarkus Hoffrogge $this->repo_path = $repo_path; 158b08c3d51SMarkus Hoffrogge if ($_init) $this->run('init'); 159b08c3d51SMarkus Hoffrogge } else { 160*2762023dSMarkus Hoffrogge throw new \Exception($this->handleRepoPathError( 161*2762023dSMarkus Hoffrogge $repo_path, 162*2762023dSMarkus Hoffrogge 'cannot create repository in non-existent directory' 163*2762023dSMarkus Hoffrogge )); 164b08c3d51SMarkus Hoffrogge } 165b08c3d51SMarkus Hoffrogge } else { 166*2762023dSMarkus Hoffrogge throw new \Exception($this->handleRepoPathError( 167*2762023dSMarkus Hoffrogge $repo_path, 168*2762023dSMarkus Hoffrogge '"' . $repo_path . '" does not exist' 169*2762023dSMarkus Hoffrogge )); 170b08c3d51SMarkus Hoffrogge } 171b08c3d51SMarkus Hoffrogge } 172b08c3d51SMarkus Hoffrogge } 173b08c3d51SMarkus Hoffrogge } 174b08c3d51SMarkus Hoffrogge 175b08c3d51SMarkus Hoffrogge /** 176b08c3d51SMarkus Hoffrogge * Get the path to the git repo directory (eg. the ".git" directory) 177b08c3d51SMarkus Hoffrogge * 178b08c3d51SMarkus Hoffrogge * @access public 179b08c3d51SMarkus Hoffrogge * @return string 180b08c3d51SMarkus Hoffrogge */ 181*2762023dSMarkus Hoffrogge public function gitDirectoryPath() 182*2762023dSMarkus Hoffrogge { 183b08c3d51SMarkus Hoffrogge return ($this->bare) ? $this->repo_path : $this->repo_path . "/.git"; 184b08c3d51SMarkus Hoffrogge } 185b08c3d51SMarkus Hoffrogge 186b08c3d51SMarkus Hoffrogge /** 187b08c3d51SMarkus Hoffrogge * Tests if git is installed 188b08c3d51SMarkus Hoffrogge * 189b08c3d51SMarkus Hoffrogge * @access public 190b08c3d51SMarkus Hoffrogge * @return bool 191b08c3d51SMarkus Hoffrogge */ 192*2762023dSMarkus Hoffrogge public function testGit() 193*2762023dSMarkus Hoffrogge { 194b08c3d51SMarkus Hoffrogge $descriptorspec = array( 195b08c3d51SMarkus Hoffrogge 1 => array('pipe', 'w'), 196b08c3d51SMarkus Hoffrogge 2 => array('pipe', 'w'), 197b08c3d51SMarkus Hoffrogge ); 198b08c3d51SMarkus Hoffrogge $pipes = array(); 199*2762023dSMarkus Hoffrogge $resource = proc_open(Git::getBin(), $descriptorspec, $pipes); 200b08c3d51SMarkus Hoffrogge 201b08c3d51SMarkus Hoffrogge $stdout = stream_get_contents($pipes[1]); 202b08c3d51SMarkus Hoffrogge $stderr = stream_get_contents($pipes[2]); 203b08c3d51SMarkus Hoffrogge foreach ($pipes as $pipe) { 204b08c3d51SMarkus Hoffrogge fclose($pipe); 205b08c3d51SMarkus Hoffrogge } 206b08c3d51SMarkus Hoffrogge 207b08c3d51SMarkus Hoffrogge $status = trim(proc_close($resource)); 208b08c3d51SMarkus Hoffrogge return ($status != 127); 209b08c3d51SMarkus Hoffrogge } 210b08c3d51SMarkus Hoffrogge 211b08c3d51SMarkus Hoffrogge /** 212b08c3d51SMarkus Hoffrogge * Run a command in the git repository 213b08c3d51SMarkus Hoffrogge * 214b08c3d51SMarkus Hoffrogge * Accepts a shell command to run 215b08c3d51SMarkus Hoffrogge * 216b08c3d51SMarkus Hoffrogge * @access protected 217b08c3d51SMarkus Hoffrogge * @param string command to run 218b08c3d51SMarkus Hoffrogge * @return string or null in case of an error 219b08c3d51SMarkus Hoffrogge */ 220*2762023dSMarkus Hoffrogge protected function runCommand($command) 221*2762023dSMarkus Hoffrogge { 222*2762023dSMarkus Hoffrogge //dbglog("Git->runCommand(command=[".$command."])"); 223b08c3d51SMarkus Hoffrogge $descriptorspec = array( 224b08c3d51SMarkus Hoffrogge 1 => array('pipe', 'w'), 225b08c3d51SMarkus Hoffrogge 2 => array('pipe', 'w'), 226b08c3d51SMarkus Hoffrogge ); 227b08c3d51SMarkus Hoffrogge $pipes = array(); 228b08c3d51SMarkus Hoffrogge $cwd = $this->repo_path; 229b08c3d51SMarkus Hoffrogge //dbglog("GitBacked - cwd: [".$cwd."]"); 230b08c3d51SMarkus Hoffrogge /* Provide any $this->envopts via putenv 231b08c3d51SMarkus Hoffrogge * and call proc_open with env=null to inherit the rest 232b08c3d51SMarkus Hoffrogge * of env variables from the original process of the system. 233b08c3d51SMarkus Hoffrogge * Note: Variables set by putenv live for a 234b08c3d51SMarkus Hoffrogge * single PHP request run only. These variables 235b08c3d51SMarkus Hoffrogge * are visible "locally". They are NOT listed by getenv(), 236b08c3d51SMarkus Hoffrogge * but they are visible to the process forked by proc_open(). 237b08c3d51SMarkus Hoffrogge */ 238b08c3d51SMarkus Hoffrogge foreach ($this->envopts as $k => $v) { 239b08c3d51SMarkus Hoffrogge putenv(sprintf("%s=%s", $k, $v)); 240b08c3d51SMarkus Hoffrogge } 241b08c3d51SMarkus Hoffrogge $resource = proc_open($command, $descriptorspec, $pipes, $cwd, null); 242b08c3d51SMarkus Hoffrogge 243b08c3d51SMarkus Hoffrogge $stdout = stream_get_contents($pipes[1]); 244b08c3d51SMarkus Hoffrogge $stderr = stream_get_contents($pipes[2]); 245b08c3d51SMarkus Hoffrogge foreach ($pipes as $pipe) { 246b08c3d51SMarkus Hoffrogge fclose($pipe); 247b08c3d51SMarkus Hoffrogge } 248b08c3d51SMarkus Hoffrogge 249b08c3d51SMarkus Hoffrogge $status = trim(proc_close($resource)); 250*2762023dSMarkus Hoffrogge //dbglog("GitBacked: runCommand status: ".$status); 251b08c3d51SMarkus Hoffrogge if ($status) { 252b08c3d51SMarkus Hoffrogge //dbglog("GitBacked - stderr: [".$stderr."]"); 253b08c3d51SMarkus Hoffrogge // Remove a probable password from the Git URL, if the URL is contained in the error message 254*2762023dSMarkus Hoffrogge $error_message = preg_replace( 255*2762023dSMarkus Hoffrogge $this::REGEX_GIT_URL_FILTER_PWD, 256*2762023dSMarkus Hoffrogge $this::REGEX_GIT_URL_FILTER_PWD_REPLACE_PATTERN, 257*2762023dSMarkus Hoffrogge $stderr 258*2762023dSMarkus Hoffrogge ); 259b08c3d51SMarkus Hoffrogge //dbglog("GitBacked - error_message: [".$error_message."]"); 260*2762023dSMarkus Hoffrogge throw new \Exception($this->handleCommandError( 261*2762023dSMarkus Hoffrogge $this->repo_path, 262*2762023dSMarkus Hoffrogge $cwd, 263*2762023dSMarkus Hoffrogge $command, 264*2762023dSMarkus Hoffrogge $status, 265*2762023dSMarkus Hoffrogge $error_message 266*2762023dSMarkus Hoffrogge )); 267b08c3d51SMarkus Hoffrogge } else { 268*2762023dSMarkus Hoffrogge $this->handleCommandSuccess($this->repo_path, $cwd, $command); 269b08c3d51SMarkus Hoffrogge } 270b08c3d51SMarkus Hoffrogge 271b08c3d51SMarkus Hoffrogge return $stdout; 272b08c3d51SMarkus Hoffrogge } 273b08c3d51SMarkus Hoffrogge 274b08c3d51SMarkus Hoffrogge /** 275b08c3d51SMarkus Hoffrogge * Run a git command in the git repository 276b08c3d51SMarkus Hoffrogge * 277b08c3d51SMarkus Hoffrogge * Accepts a git command to run 278b08c3d51SMarkus Hoffrogge * 279b08c3d51SMarkus Hoffrogge * @access public 280b08c3d51SMarkus Hoffrogge * @param string command to run 281b08c3d51SMarkus Hoffrogge * @return string 282b08c3d51SMarkus Hoffrogge */ 283*2762023dSMarkus Hoffrogge public function run($command) 284*2762023dSMarkus Hoffrogge { 285*2762023dSMarkus Hoffrogge return $this->runCommand(Git::getBin() . " " . $command); 286b08c3d51SMarkus Hoffrogge } 287b08c3d51SMarkus Hoffrogge 288b08c3d51SMarkus Hoffrogge /** 289b08c3d51SMarkus Hoffrogge * Handles error on create_new 290b08c3d51SMarkus Hoffrogge * 291b08c3d51SMarkus Hoffrogge * @access protected 292b08c3d51SMarkus Hoffrogge * @param string repository path 293b08c3d51SMarkus Hoffrogge * @param string error message 294b08c3d51SMarkus Hoffrogge * @return string error message 295b08c3d51SMarkus Hoffrogge */ 296*2762023dSMarkus Hoffrogge protected static function handleCreateNewError($repo_path, $reference, $error_message, $plugin) 297*2762023dSMarkus Hoffrogge { 298b08c3d51SMarkus Hoffrogge if ($plugin instanceof \action_plugin_gitbacked_editcommit) { 299*2762023dSMarkus Hoffrogge $plugin->notifyCreateNewError($repo_path, $reference, $error_message); 300b08c3d51SMarkus Hoffrogge } 301b08c3d51SMarkus Hoffrogge return $error_message; 302b08c3d51SMarkus Hoffrogge } 303b08c3d51SMarkus Hoffrogge 304b08c3d51SMarkus Hoffrogge /** 305b08c3d51SMarkus Hoffrogge * Handles error on setting the repo path 306b08c3d51SMarkus Hoffrogge * 307b08c3d51SMarkus Hoffrogge * @access protected 308b08c3d51SMarkus Hoffrogge * @param string repository path 309b08c3d51SMarkus Hoffrogge * @param string error message 310b08c3d51SMarkus Hoffrogge * @return string error message 311b08c3d51SMarkus Hoffrogge */ 312*2762023dSMarkus Hoffrogge protected function handleRepoPathError($repo_path, $error_message) 313*2762023dSMarkus Hoffrogge { 314b08c3d51SMarkus Hoffrogge if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { 315*2762023dSMarkus Hoffrogge $this->plugin->notifyRepoPathError($repo_path, $error_message); 316b08c3d51SMarkus Hoffrogge } 317b08c3d51SMarkus Hoffrogge return $error_message; 318b08c3d51SMarkus Hoffrogge } 319b08c3d51SMarkus Hoffrogge 320b08c3d51SMarkus Hoffrogge /** 321b08c3d51SMarkus Hoffrogge * Handles error on git command 322b08c3d51SMarkus Hoffrogge * 323b08c3d51SMarkus Hoffrogge * @access protected 324b08c3d51SMarkus Hoffrogge * @param string repository path 325b08c3d51SMarkus Hoffrogge * @param string current working dir 326b08c3d51SMarkus Hoffrogge * @param string command line 327b08c3d51SMarkus Hoffrogge * @param int exit code of command (status) 328b08c3d51SMarkus Hoffrogge * @param string error message 329b08c3d51SMarkus Hoffrogge * @return string error message 330b08c3d51SMarkus Hoffrogge */ 331*2762023dSMarkus Hoffrogge protected function handleCommandError($repo_path, $cwd, $command, $status, $error_message) 332*2762023dSMarkus Hoffrogge { 333b08c3d51SMarkus Hoffrogge if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { 334*2762023dSMarkus Hoffrogge $this->plugin->notifyCommandError($repo_path, $cwd, $command, $status, $error_message); 335b08c3d51SMarkus Hoffrogge } 336b08c3d51SMarkus Hoffrogge return $error_message; 337b08c3d51SMarkus Hoffrogge } 338b08c3d51SMarkus Hoffrogge 339b08c3d51SMarkus Hoffrogge /** 340b08c3d51SMarkus Hoffrogge * Handles success on git command 341b08c3d51SMarkus Hoffrogge * 342b08c3d51SMarkus Hoffrogge * @access protected 343b08c3d51SMarkus Hoffrogge * @param string repository path 344b08c3d51SMarkus Hoffrogge * @param string current working dir 345b08c3d51SMarkus Hoffrogge * @param string command line 346b08c3d51SMarkus Hoffrogge * @return void 347b08c3d51SMarkus Hoffrogge */ 348*2762023dSMarkus Hoffrogge protected function handleCommandSuccess($repo_path, $cwd, $command) 349*2762023dSMarkus Hoffrogge { 350b08c3d51SMarkus Hoffrogge if ($this->plugin instanceof \action_plugin_gitbacked_editcommit) { 351*2762023dSMarkus Hoffrogge $this->plugin->notifyCommandSuccess($repo_path, $cwd, $command); 352b08c3d51SMarkus Hoffrogge } 353b08c3d51SMarkus Hoffrogge } 354b08c3d51SMarkus Hoffrogge 355b08c3d51SMarkus Hoffrogge /** 356b08c3d51SMarkus Hoffrogge * Runs a 'git status' call 357b08c3d51SMarkus Hoffrogge * 358b08c3d51SMarkus Hoffrogge * Accept a convert to HTML bool 359b08c3d51SMarkus Hoffrogge * 360b08c3d51SMarkus Hoffrogge * @access public 361b08c3d51SMarkus Hoffrogge * @param bool return string with <br /> 362b08c3d51SMarkus Hoffrogge * @return string 363b08c3d51SMarkus Hoffrogge */ 364*2762023dSMarkus Hoffrogge public function status($html = false) 365*2762023dSMarkus Hoffrogge { 366b08c3d51SMarkus Hoffrogge $msg = $this->run("status"); 367b08c3d51SMarkus Hoffrogge if ($html == true) { 368b08c3d51SMarkus Hoffrogge $msg = str_replace("\n", "<br />", $msg); 369b08c3d51SMarkus Hoffrogge } 370b08c3d51SMarkus Hoffrogge return $msg; 371b08c3d51SMarkus Hoffrogge } 372b08c3d51SMarkus Hoffrogge 373b08c3d51SMarkus Hoffrogge /** 374b08c3d51SMarkus Hoffrogge * Runs a `git add` call 375b08c3d51SMarkus Hoffrogge * 376b08c3d51SMarkus Hoffrogge * Accepts a list of files to add 377b08c3d51SMarkus Hoffrogge * 378b08c3d51SMarkus Hoffrogge * @access public 379b08c3d51SMarkus Hoffrogge * @param mixed files to add 380b08c3d51SMarkus Hoffrogge * @return string 381b08c3d51SMarkus Hoffrogge */ 382*2762023dSMarkus Hoffrogge public function add($files = "*") 383*2762023dSMarkus Hoffrogge { 384b08c3d51SMarkus Hoffrogge if (is_array($files)) { 385b08c3d51SMarkus Hoffrogge $files = '"' . implode('" "', $files) . '"'; 386b08c3d51SMarkus Hoffrogge } 387b08c3d51SMarkus Hoffrogge return $this->run("add $files -v"); 388b08c3d51SMarkus Hoffrogge } 389b08c3d51SMarkus Hoffrogge 390b08c3d51SMarkus Hoffrogge /** 391b08c3d51SMarkus Hoffrogge * Runs a `git rm` call 392b08c3d51SMarkus Hoffrogge * 393b08c3d51SMarkus Hoffrogge * Accepts a list of files to remove 394b08c3d51SMarkus Hoffrogge * 395b08c3d51SMarkus Hoffrogge * @access public 396b08c3d51SMarkus Hoffrogge * @param mixed files to remove 397b08c3d51SMarkus Hoffrogge * @param Boolean use the --cached flag? 398b08c3d51SMarkus Hoffrogge * @return string 399b08c3d51SMarkus Hoffrogge */ 400*2762023dSMarkus Hoffrogge public function rm($files = "*", $cached = false) 401*2762023dSMarkus Hoffrogge { 402b08c3d51SMarkus Hoffrogge if (is_array($files)) { 403b08c3d51SMarkus Hoffrogge $files = '"' . implode('" "', $files) . '"'; 404b08c3d51SMarkus Hoffrogge } 405b08c3d51SMarkus Hoffrogge return $this->run("rm " . ($cached ? '--cached ' : '') . $files); 406b08c3d51SMarkus Hoffrogge } 407b08c3d51SMarkus Hoffrogge 408b08c3d51SMarkus Hoffrogge 409b08c3d51SMarkus Hoffrogge /** 410b08c3d51SMarkus Hoffrogge * Runs a `git commit` call 411b08c3d51SMarkus Hoffrogge * 412b08c3d51SMarkus Hoffrogge * Accepts a commit message string 413b08c3d51SMarkus Hoffrogge * 414b08c3d51SMarkus Hoffrogge * @access public 415b08c3d51SMarkus Hoffrogge * @param string commit message 416b08c3d51SMarkus Hoffrogge * @param boolean should all files be committed automatically (-a flag) 417b08c3d51SMarkus Hoffrogge * @return string 418b08c3d51SMarkus Hoffrogge */ 419*2762023dSMarkus Hoffrogge public function commit($message = "", $commit_all = true) 420*2762023dSMarkus Hoffrogge { 421b08c3d51SMarkus Hoffrogge $flags = $commit_all ? '-av' : '-v'; 422b08c3d51SMarkus Hoffrogge $msgfile = GitBackedUtil::createMessageFile($message); 423b08c3d51SMarkus Hoffrogge try { 424b08c3d51SMarkus Hoffrogge return $this->run("commit --allow-empty " . $flags . " --file=" . $msgfile); 425b08c3d51SMarkus Hoffrogge } finally { 426b08c3d51SMarkus Hoffrogge unlink($msgfile); 427b08c3d51SMarkus Hoffrogge } 428b08c3d51SMarkus Hoffrogge } 429b08c3d51SMarkus Hoffrogge 430b08c3d51SMarkus Hoffrogge /** 431b08c3d51SMarkus Hoffrogge * Runs a `git clone` call to clone the current repository 432b08c3d51SMarkus Hoffrogge * into a different directory 433b08c3d51SMarkus Hoffrogge * 434b08c3d51SMarkus Hoffrogge * Accepts a target directory 435b08c3d51SMarkus Hoffrogge * 436b08c3d51SMarkus Hoffrogge * @access public 437b08c3d51SMarkus Hoffrogge * @param string target directory 438b08c3d51SMarkus Hoffrogge * @return string 439b08c3d51SMarkus Hoffrogge */ 440*2762023dSMarkus Hoffrogge public function cloneTo($target) 441*2762023dSMarkus Hoffrogge { 442b08c3d51SMarkus Hoffrogge return $this->run("clone --local " . $this->repo_path . " $target"); 443b08c3d51SMarkus Hoffrogge } 444b08c3d51SMarkus Hoffrogge 445b08c3d51SMarkus Hoffrogge /** 446b08c3d51SMarkus Hoffrogge * Runs a `git clone` call to clone a different repository 447b08c3d51SMarkus Hoffrogge * into the current repository 448b08c3d51SMarkus Hoffrogge * 449b08c3d51SMarkus Hoffrogge * Accepts a source directory 450b08c3d51SMarkus Hoffrogge * 451b08c3d51SMarkus Hoffrogge * @access public 452b08c3d51SMarkus Hoffrogge * @param string source directory 453b08c3d51SMarkus Hoffrogge * @return string 454b08c3d51SMarkus Hoffrogge */ 455*2762023dSMarkus Hoffrogge public function cloneFrom($source) 456*2762023dSMarkus Hoffrogge { 457b08c3d51SMarkus Hoffrogge return $this->run("clone --local $source " . $this->repo_path); 458b08c3d51SMarkus Hoffrogge } 459b08c3d51SMarkus Hoffrogge 460b08c3d51SMarkus Hoffrogge /** 461b08c3d51SMarkus Hoffrogge * Runs a `git clone` call to clone a remote repository 462b08c3d51SMarkus Hoffrogge * into the current repository 463b08c3d51SMarkus Hoffrogge * 464b08c3d51SMarkus Hoffrogge * Accepts a source url 465b08c3d51SMarkus Hoffrogge * 466b08c3d51SMarkus Hoffrogge * @access public 467b08c3d51SMarkus Hoffrogge * @param string source url 468b08c3d51SMarkus Hoffrogge * @param string reference path 469b08c3d51SMarkus Hoffrogge * @return string 470b08c3d51SMarkus Hoffrogge */ 471*2762023dSMarkus Hoffrogge public function cloneRemote($source, $reference) 472*2762023dSMarkus Hoffrogge { 473b08c3d51SMarkus Hoffrogge return $this->run("clone $reference $source " . $this->repo_path); 474b08c3d51SMarkus Hoffrogge } 475b08c3d51SMarkus Hoffrogge 476b08c3d51SMarkus Hoffrogge /** 477b08c3d51SMarkus Hoffrogge * Runs a `git clean` call 478b08c3d51SMarkus Hoffrogge * 479b08c3d51SMarkus Hoffrogge * Accepts a remove directories flag 480b08c3d51SMarkus Hoffrogge * 481b08c3d51SMarkus Hoffrogge * @access public 482b08c3d51SMarkus Hoffrogge * @param bool delete directories? 483b08c3d51SMarkus Hoffrogge * @param bool force clean? 484b08c3d51SMarkus Hoffrogge * @return string 485b08c3d51SMarkus Hoffrogge */ 486*2762023dSMarkus Hoffrogge public function clean($dirs = false, $force = false) 487*2762023dSMarkus Hoffrogge { 488b08c3d51SMarkus Hoffrogge return $this->run("clean" . (($force) ? " -f" : "") . (($dirs) ? " -d" : "")); 489b08c3d51SMarkus Hoffrogge } 490b08c3d51SMarkus Hoffrogge 491b08c3d51SMarkus Hoffrogge /** 492b08c3d51SMarkus Hoffrogge * Runs a `git branch` call 493b08c3d51SMarkus Hoffrogge * 494b08c3d51SMarkus Hoffrogge * Accepts a name for the branch 495b08c3d51SMarkus Hoffrogge * 496b08c3d51SMarkus Hoffrogge * @access public 497b08c3d51SMarkus Hoffrogge * @param string branch name 498b08c3d51SMarkus Hoffrogge * @return string 499b08c3d51SMarkus Hoffrogge */ 500*2762023dSMarkus Hoffrogge public function createBranch($branch) 501*2762023dSMarkus Hoffrogge { 502b08c3d51SMarkus Hoffrogge return $this->run("branch $branch"); 503b08c3d51SMarkus Hoffrogge } 504b08c3d51SMarkus Hoffrogge 505b08c3d51SMarkus Hoffrogge /** 506b08c3d51SMarkus Hoffrogge * Runs a `git branch -[d|D]` call 507b08c3d51SMarkus Hoffrogge * 508b08c3d51SMarkus Hoffrogge * Accepts a name for the branch 509b08c3d51SMarkus Hoffrogge * 510b08c3d51SMarkus Hoffrogge * @access public 511b08c3d51SMarkus Hoffrogge * @param string branch name 512b08c3d51SMarkus Hoffrogge * @return string 513b08c3d51SMarkus Hoffrogge */ 514*2762023dSMarkus Hoffrogge public function deleteBranch($branch, $force = false) 515*2762023dSMarkus Hoffrogge { 516b08c3d51SMarkus Hoffrogge return $this->run("branch " . (($force) ? '-D' : '-d') . " $branch"); 517b08c3d51SMarkus Hoffrogge } 518b08c3d51SMarkus Hoffrogge 519b08c3d51SMarkus Hoffrogge /** 520b08c3d51SMarkus Hoffrogge * Runs a `git branch` call 521b08c3d51SMarkus Hoffrogge * 522b08c3d51SMarkus Hoffrogge * @access public 523b08c3d51SMarkus Hoffrogge * @param bool keep asterisk mark on active branch 524b08c3d51SMarkus Hoffrogge * @return array 525b08c3d51SMarkus Hoffrogge */ 526*2762023dSMarkus Hoffrogge public function listBranches($keep_asterisk = false) 527*2762023dSMarkus Hoffrogge { 528b08c3d51SMarkus Hoffrogge $branchArray = explode("\n", $this->run("branch")); 529b08c3d51SMarkus Hoffrogge foreach ($branchArray as $i => &$branch) { 530b08c3d51SMarkus Hoffrogge $branch = trim($branch); 531b08c3d51SMarkus Hoffrogge if (! $keep_asterisk) { 532b08c3d51SMarkus Hoffrogge $branch = str_replace("* ", "", $branch); 533b08c3d51SMarkus Hoffrogge } 534b08c3d51SMarkus Hoffrogge if ($branch == "") { 535b08c3d51SMarkus Hoffrogge unset($branchArray[$i]); 536b08c3d51SMarkus Hoffrogge } 537b08c3d51SMarkus Hoffrogge } 538b08c3d51SMarkus Hoffrogge return $branchArray; 539b08c3d51SMarkus Hoffrogge } 540b08c3d51SMarkus Hoffrogge 541b08c3d51SMarkus Hoffrogge /** 542b08c3d51SMarkus Hoffrogge * Lists remote branches (using `git branch -r`). 543b08c3d51SMarkus Hoffrogge * 544b08c3d51SMarkus Hoffrogge * Also strips out the HEAD reference (e.g. "origin/HEAD -> origin/master"). 545b08c3d51SMarkus Hoffrogge * 546b08c3d51SMarkus Hoffrogge * @access public 547b08c3d51SMarkus Hoffrogge * @return array 548b08c3d51SMarkus Hoffrogge */ 549*2762023dSMarkus Hoffrogge public function listRemoteBranches() 550*2762023dSMarkus Hoffrogge { 551b08c3d51SMarkus Hoffrogge $branchArray = explode("\n", $this->run("branch -r")); 552b08c3d51SMarkus Hoffrogge foreach ($branchArray as $i => &$branch) { 553b08c3d51SMarkus Hoffrogge $branch = trim($branch); 554b08c3d51SMarkus Hoffrogge if ($branch == "" || strpos($branch, 'HEAD -> ') !== false) { 555b08c3d51SMarkus Hoffrogge unset($branchArray[$i]); 556b08c3d51SMarkus Hoffrogge } 557b08c3d51SMarkus Hoffrogge } 558b08c3d51SMarkus Hoffrogge return $branchArray; 559b08c3d51SMarkus Hoffrogge } 560b08c3d51SMarkus Hoffrogge 561b08c3d51SMarkus Hoffrogge /** 562b08c3d51SMarkus Hoffrogge * Returns name of active branch 563b08c3d51SMarkus Hoffrogge * 564b08c3d51SMarkus Hoffrogge * @access public 565b08c3d51SMarkus Hoffrogge * @param bool keep asterisk mark on branch name 566b08c3d51SMarkus Hoffrogge * @return string 567b08c3d51SMarkus Hoffrogge */ 568*2762023dSMarkus Hoffrogge public function activeBranch($keep_asterisk = false) 569*2762023dSMarkus Hoffrogge { 570*2762023dSMarkus Hoffrogge $branchArray = $this->listBranches(true); 571*2762023dSMarkus Hoffrogge $activeBranch = preg_grep("/^\*/", $branchArray); 572*2762023dSMarkus Hoffrogge reset($activeBranch); 573b08c3d51SMarkus Hoffrogge if ($keep_asterisk) { 574*2762023dSMarkus Hoffrogge return current($activeBranch); 575b08c3d51SMarkus Hoffrogge } else { 576*2762023dSMarkus Hoffrogge return str_replace("* ", "", current($activeBranch)); 577b08c3d51SMarkus Hoffrogge } 578b08c3d51SMarkus Hoffrogge } 579b08c3d51SMarkus Hoffrogge 580b08c3d51SMarkus Hoffrogge /** 581b08c3d51SMarkus Hoffrogge * Runs a `git checkout` call 582b08c3d51SMarkus Hoffrogge * 583b08c3d51SMarkus Hoffrogge * Accepts a name for the branch 584b08c3d51SMarkus Hoffrogge * 585b08c3d51SMarkus Hoffrogge * @access public 586b08c3d51SMarkus Hoffrogge * @param string branch name 587b08c3d51SMarkus Hoffrogge * @return string 588b08c3d51SMarkus Hoffrogge */ 589*2762023dSMarkus Hoffrogge public function checkout($branch) 590*2762023dSMarkus Hoffrogge { 591b08c3d51SMarkus Hoffrogge return $this->run("checkout $branch"); 592b08c3d51SMarkus Hoffrogge } 593b08c3d51SMarkus Hoffrogge 594b08c3d51SMarkus Hoffrogge 595b08c3d51SMarkus Hoffrogge /** 596b08c3d51SMarkus Hoffrogge * Runs a `git merge` call 597b08c3d51SMarkus Hoffrogge * 598b08c3d51SMarkus Hoffrogge * Accepts a name for the branch to be merged 599b08c3d51SMarkus Hoffrogge * 600b08c3d51SMarkus Hoffrogge * @access public 601b08c3d51SMarkus Hoffrogge * @param string $branch 602b08c3d51SMarkus Hoffrogge * @return string 603b08c3d51SMarkus Hoffrogge */ 604*2762023dSMarkus Hoffrogge public function merge($branch) 605*2762023dSMarkus Hoffrogge { 606b08c3d51SMarkus Hoffrogge return $this->run("merge $branch --no-ff"); 607b08c3d51SMarkus Hoffrogge } 608b08c3d51SMarkus Hoffrogge 609b08c3d51SMarkus Hoffrogge 610b08c3d51SMarkus Hoffrogge /** 611b08c3d51SMarkus Hoffrogge * Runs a git fetch on the current branch 612b08c3d51SMarkus Hoffrogge * 613b08c3d51SMarkus Hoffrogge * @access public 614b08c3d51SMarkus Hoffrogge * @return string 615b08c3d51SMarkus Hoffrogge */ 616*2762023dSMarkus Hoffrogge public function fetch() 617*2762023dSMarkus Hoffrogge { 618b08c3d51SMarkus Hoffrogge return $this->run("fetch"); 619b08c3d51SMarkus Hoffrogge } 620b08c3d51SMarkus Hoffrogge 621b08c3d51SMarkus Hoffrogge /** 622b08c3d51SMarkus Hoffrogge * Add a new tag on the current position 623b08c3d51SMarkus Hoffrogge * 624b08c3d51SMarkus Hoffrogge * Accepts the name for the tag and the message 625b08c3d51SMarkus Hoffrogge * 626b08c3d51SMarkus Hoffrogge * @param string $tag 627b08c3d51SMarkus Hoffrogge * @param string $message 628b08c3d51SMarkus Hoffrogge * @return string 629b08c3d51SMarkus Hoffrogge */ 630*2762023dSMarkus Hoffrogge public function addTag($tag, $message = null) 631*2762023dSMarkus Hoffrogge { 632b08c3d51SMarkus Hoffrogge if ($message === null) { 633b08c3d51SMarkus Hoffrogge $message = $tag; 634b08c3d51SMarkus Hoffrogge } 635b08c3d51SMarkus Hoffrogge $msgfile = GitBackedUtil::createMessageFile($message); 636b08c3d51SMarkus Hoffrogge try { 637b08c3d51SMarkus Hoffrogge return $this->run("tag -a $tag --file=" . $msgfile); 638b08c3d51SMarkus Hoffrogge } finally { 639b08c3d51SMarkus Hoffrogge unlink($msgfile); 640b08c3d51SMarkus Hoffrogge } 641b08c3d51SMarkus Hoffrogge } 642b08c3d51SMarkus Hoffrogge 643b08c3d51SMarkus Hoffrogge /** 644b08c3d51SMarkus Hoffrogge * List all the available repository tags. 645b08c3d51SMarkus Hoffrogge * 646b08c3d51SMarkus Hoffrogge * Optionally, accept a shell wildcard pattern and return only tags matching it. 647b08c3d51SMarkus Hoffrogge * 648b08c3d51SMarkus Hoffrogge * @access public 649b08c3d51SMarkus Hoffrogge * @param string $pattern Shell wildcard pattern to match tags against. 650b08c3d51SMarkus Hoffrogge * @return array Available repository tags. 651b08c3d51SMarkus Hoffrogge */ 652*2762023dSMarkus Hoffrogge public function listTags($pattern = null) 653*2762023dSMarkus Hoffrogge { 654b08c3d51SMarkus Hoffrogge $tagArray = explode("\n", $this->run("tag -l $pattern")); 655b08c3d51SMarkus Hoffrogge foreach ($tagArray as $i => &$tag) { 656b08c3d51SMarkus Hoffrogge $tag = trim($tag); 657b08c3d51SMarkus Hoffrogge if ($tag == '') { 658b08c3d51SMarkus Hoffrogge unset($tagArray[$i]); 659b08c3d51SMarkus Hoffrogge } 660b08c3d51SMarkus Hoffrogge } 661b08c3d51SMarkus Hoffrogge 662b08c3d51SMarkus Hoffrogge return $tagArray; 663b08c3d51SMarkus Hoffrogge } 664b08c3d51SMarkus Hoffrogge 665b08c3d51SMarkus Hoffrogge /** 666b08c3d51SMarkus Hoffrogge * Push specific branch to a remote 667b08c3d51SMarkus Hoffrogge * 668b08c3d51SMarkus Hoffrogge * Accepts the name of the remote and local branch 669b08c3d51SMarkus Hoffrogge * 670b08c3d51SMarkus Hoffrogge * @param string $remote 671b08c3d51SMarkus Hoffrogge * @param string $branch 672b08c3d51SMarkus Hoffrogge * @return string 673b08c3d51SMarkus Hoffrogge */ 674*2762023dSMarkus Hoffrogge public function push($remote, $branch) 675*2762023dSMarkus Hoffrogge { 676b08c3d51SMarkus Hoffrogge return $this->run("push --tags $remote $branch"); 677b08c3d51SMarkus Hoffrogge } 678b08c3d51SMarkus Hoffrogge 679b08c3d51SMarkus Hoffrogge /** 680b08c3d51SMarkus Hoffrogge * Pull specific branch from remote 681b08c3d51SMarkus Hoffrogge * 682b08c3d51SMarkus Hoffrogge * Accepts the name of the remote and local branch 683b08c3d51SMarkus Hoffrogge * 684b08c3d51SMarkus Hoffrogge * @param string $remote 685b08c3d51SMarkus Hoffrogge * @param string $branch 686b08c3d51SMarkus Hoffrogge * @return string 687b08c3d51SMarkus Hoffrogge */ 688*2762023dSMarkus Hoffrogge public function pull($remote, $branch) 689*2762023dSMarkus Hoffrogge { 690b08c3d51SMarkus Hoffrogge return $this->run("pull $remote $branch"); 691b08c3d51SMarkus Hoffrogge } 692b08c3d51SMarkus Hoffrogge 693b08c3d51SMarkus Hoffrogge /** 694b08c3d51SMarkus Hoffrogge * List log entries. 695b08c3d51SMarkus Hoffrogge * 696b08c3d51SMarkus Hoffrogge * @param strgin $format 697b08c3d51SMarkus Hoffrogge * @return string 698b08c3d51SMarkus Hoffrogge */ 699*2762023dSMarkus Hoffrogge public function log($format = null) 700*2762023dSMarkus Hoffrogge { 701*2762023dSMarkus Hoffrogge if ($format === null) { 702b08c3d51SMarkus Hoffrogge return $this->run('log'); 703*2762023dSMarkus Hoffrogge } else { 704b08c3d51SMarkus Hoffrogge return $this->run('log --pretty=format:"' . $format . '"'); 705b08c3d51SMarkus Hoffrogge } 706*2762023dSMarkus Hoffrogge } 707b08c3d51SMarkus Hoffrogge 708b08c3d51SMarkus Hoffrogge /** 709b08c3d51SMarkus Hoffrogge * Sets the project description. 710b08c3d51SMarkus Hoffrogge * 711b08c3d51SMarkus Hoffrogge * @param string $new 712b08c3d51SMarkus Hoffrogge */ 713*2762023dSMarkus Hoffrogge public function setDescription($new) 714*2762023dSMarkus Hoffrogge { 715*2762023dSMarkus Hoffrogge $path = $this->gitDirectoryPath(); 716b08c3d51SMarkus Hoffrogge file_put_contents($path . "/description", $new); 717b08c3d51SMarkus Hoffrogge } 718b08c3d51SMarkus Hoffrogge 719b08c3d51SMarkus Hoffrogge /** 720b08c3d51SMarkus Hoffrogge * Gets the project description. 721b08c3d51SMarkus Hoffrogge * 722b08c3d51SMarkus Hoffrogge * @return string 723b08c3d51SMarkus Hoffrogge */ 724*2762023dSMarkus Hoffrogge public function getDescription() 725*2762023dSMarkus Hoffrogge { 726*2762023dSMarkus Hoffrogge $path = $this->gitDirectoryPath(); 727b08c3d51SMarkus Hoffrogge return file_get_contents($path . "/description"); 728b08c3d51SMarkus Hoffrogge } 729b08c3d51SMarkus Hoffrogge 730b08c3d51SMarkus Hoffrogge /** 731b08c3d51SMarkus Hoffrogge * Sets custom environment options for calling Git 732b08c3d51SMarkus Hoffrogge * 733b08c3d51SMarkus Hoffrogge * @param string key 734b08c3d51SMarkus Hoffrogge * @param string value 735b08c3d51SMarkus Hoffrogge */ 736*2762023dSMarkus Hoffrogge public function setenv($key, $value) 737*2762023dSMarkus Hoffrogge { 738b08c3d51SMarkus Hoffrogge $this->envopts[$key] = $value; 739b08c3d51SMarkus Hoffrogge } 740b08c3d51SMarkus Hoffrogge} 741b08c3d51SMarkus Hoffrogge 742b08c3d51SMarkus Hoffrogge/* End of file */ 743