Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/_ext/git/Git.php
<?php

/*
 * Git.php
 *
 * A PHP git library
 *
 * @package    Git.php
 * @version    0.1.4
 * @author     James Brumond
 * @copyright  Copyright 2013 James Brumond
 * @repo       http://github.com/kbjr/Git.php
 */

if (__FILE__ == $_SERVER['SCRIPT_FILENAME']) die('Bad load order');

// ------------------------------------------------------------------------

/**
 * Git Interface Class
 *
 * This class enables the creating, reading, and manipulation
 * of git repositories.
 *
 * @class  Git
 */
class Git {

   
/**
     * Git executable location
     *
     * @var string
     */
   
protected static $bin = '/usr/bin/git';

   
/**
     * Sets git executable path
     *
     * @param string $path executable location
     */
   
public static function set_bin($path) {
       
self::$bin = $path;
    }

   
/**
     * Gets git executable path
     */
   
public static function get_bin() {
        return
self::$bin;
    }

   
/**
     * Sets up library for use in a default Windows environment
     */
   
public static function windows_mode() {
       
self::set_bin('git');
    }

   
/**
     * Create a new git repository
     *
     * Accepts a creation path, and, optionally, a source path
     *
     * @access  public
     * @param   string  repository path
     * @param   string  directory to source
     * @return  GitRepo
     */
   
public static function &create($repo_path, $source = null) {
        return
GitRepo::create_new($repo_path, $source);
    }

   
/**
     * Open an existing git repository
     *
     * Accepts a repository path
     *
     * @access  public
     * @param   string  repository path
     * @return  GitRepo
     */
   
public static function open($repo_path) {
        return new
GitRepo($repo_path);
    }

   
/**
     * Clones a remote repo into a directory and then returns a GitRepo object
     * for the newly created local repo
     *
     * Accepts a creation path and a remote to clone from
     *
     * @access  public
     * @param   string  repository path
     * @param   string  remote source
     * @param   string  reference path
     * @return  GitRepo
     **/
   
public static function &clone_remote($repo_path, $remote, $reference = null) {
       
//Changed the below boolean from true to false, since this appears to be a bug when not using a reference repo.  A more robust solution may be appropriate to make it work with AND without a reference.
       
return GitRepo::create_new($repo_path, $remote, false, $reference);
    }

   
/**
     * Checks if a variable is an instance of GitRepo
     *
     * Accepts a variable
     *
     * @access  public
     * @param   mixed   variable
     * @return  bool
     */
   
public static function is_repo($var) {
        return (
get_class($var) == 'GitRepo');
    }

}

// ------------------------------------------------------------------------

/**
 * Git Repository Interface Class
 *
 * This class enables the creating, reading, and manipulation
 * of a git repository
 *
 * @class  GitRepo
 */
class GitRepo {

    protected
$repo_path = null;
    protected
$bare = false;
    protected
$envopts = array();

   
/**
     * Create a new git repository
     *
     * Accepts a creation path, and, optionally, a source path
     *
     * @access  public
     * @param   string  repository path
     * @param   string  directory to source
     * @param   string  reference path
     * @return  GitRepo
     */
   
public static function &create_new($repo_path, $source = null, $remote_source = false, $reference = null) {
        if (
is_dir($repo_path) && file_exists($repo_path."/.git")) {
            throw new
Exception('"'.$repo_path.'" is already a git repository');
        } else {
           
$repo = new self($repo_path, true, false);
            if (
is_string($source)) {
                if (
$remote_source) {
                    if (isset(
$reference)) {
                        if (!
is_dir($reference) || !is_dir($reference.'/.git')) {
                            throw new
Exception('"'.$reference.'" is not a git repository. Cannot use as reference.');
                        } else if (
strlen($reference)) {
                           
$reference = realpath($reference);
                           
$reference = "--reference $reference";
                        }
                    }
                   
$repo->clone_remote($source, $reference);
                } else {
                   
$repo->clone_from($source);
                }
            } else {
               
$repo->run('init');
            }
            return
$repo;
        }
    }

   
/**
     * Constructor
     *
     * Accepts a repository path
     *
     * @access  public
     * @param   string  repository path
     * @param   bool    create if not exists?
     * @return  void
     */
   
public function __construct($repo_path = null, $create_new = false, $_init = true) {
        if (
is_string($repo_path)) {
           
$this->set_repo_path($repo_path, $create_new, $_init);
        }
    }

   
/**
     * Set the repository's path
     *
     * Accepts the repository path
     *
     * @access  public
     * @param   string  repository path
     * @param   bool    create if not exists?
     * @param   bool    initialize new Git repo if not exists?
     * @return  void
     */
   
public function set_repo_path($repo_path, $create_new = false, $_init = true) {
        if (
is_string($repo_path)) {
            if (
$new_path = realpath($repo_path)) {
               
$repo_path = $new_path;
                if (
is_dir($repo_path)) {
                   
// Is this a work tree?
                   
if (file_exists($repo_path."/.git")) {
                       
$this->repo_path = $repo_path;
                       
$this->bare = false;
                   
// Is this a bare repo?
                   
} else if (is_file($repo_path."/config")) {
                     
$parse_ini = parse_ini_file($repo_path."/config");
                        if (
$parse_ini['bare']) {
                           
$this->repo_path = $repo_path;
                           
$this->bare = true;
                        }
                    } else {
                        if (
$create_new) {
                           
$this->repo_path = $repo_path;
                            if (
$_init) {
                               
$this->run('init');
                            }
                        } else {
                            throw new
Exception('"'.$repo_path.'" is not a git repository');
                        }
                    }
                } else {
                    throw new
Exception('"'.$repo_path.'" is not a directory');
                }
            } else {
                if (
$create_new) {
                    if (
$parent = realpath(dirname($repo_path))) {
                       
mkdir($repo_path);
                       
$this->repo_path = $repo_path;
                        if (
$_init) $this->run('init');
                    } else {
                        throw new
Exception('cannot create repository in non-existent directory');
                    }
                } else {
                    throw new
Exception('"'.$repo_path.'" does not exist');
                }
            }
        }
    }
   
   
/**
     * Get the path to the git repo directory (eg. the ".git" directory)
     *
     * @access public
     * @return string
     */
   
public function git_directory_path() {
    if (
$this->bare) {
      return
$this->repo_path;
    } else if (
is_dir($this->repo_path."/.git")) {
      return
$this->repo_path."/.git";
    } else if (
is_file($this->repo_path."/.git")) {
     
$git_file = file_get_contents($this->repo_path."/.git");
      if(
mb_ereg("^gitdir: (.+)$", $git_file, $matches)){
        if(
$matches[1]) {
         
$rel_git_path = $matches[1];
          return
$this->repo_path."/".$rel_git_path;
        }
      }
    }
    throw new
Exception('could not find git dir for '.$this->repo_path.'.');
    }

   
/**
     * Tests if git is installed
     *
     * @access  public
     * @return  bool
     */
   
public function test_git() {
       
$descriptorspec = array(
           
1 => array('pipe', 'w'),
           
2 => array('pipe', 'w'),
        );
       
$pipes = array();
       
$resource = proc_open(Git::get_bin(), $descriptorspec, $pipes);

       
$stdout = stream_get_contents($pipes[1]);
       
$stderr = stream_get_contents($pipes[2]);
        foreach (
$pipes as $pipe) {
           
fclose($pipe);
        }

       
$status = trim(proc_close($resource));
        return (
$status != 127);
    }

   
/**
     * Run a command in the git repository
     *
     * Accepts a shell command to run
     *
     * @access  protected
     * @param   string  command to run
     * @return  string
     */
   
protected function run_command($command) {
       
$descriptorspec = array(
           
1 => array('pipe', 'w'),
           
2 => array('pipe', 'w'),
        );
       
$pipes = array();
       
/* Depending on the value of variables_order, $_ENV may be empty.
         * In that case, we have to explicitly set the new variables with
         * putenv, and call proc_open with env=null to inherit the reset
         * of the system.
         *
         * This is kind of crappy because we cannot easily restore just those
         * variables afterwards.
         *
         * If $_ENV is not empty, then we can just copy it and be done with it.
         */
       
if(count($_ENV) === 0) {
           
$env = NULL;
            foreach(
$this->envopts as $k => $v) {
               
putenv(sprintf("%s=%s",$k,$v));
            }
        } else {
           
$env = array_merge($_ENV, $this->envopts);
        }
       
$cwd = $this->repo_path;
       
$resource = proc_open($command, $descriptorspec, $pipes, $cwd, $env);

       
$stdout = stream_get_contents($pipes[1]);
       
$stderr = stream_get_contents($pipes[2]);
        foreach (
$pipes as $pipe) {
           
fclose($pipe);
        }

       
$status = trim(proc_close($resource));
        if (
$status) throw new Exception($stderr . "\n" . $stdout); //Not all errors are printed to stderr, so include std out as well.

       
return $stdout;
    }

   
/**
     * Run a git command in the git repository
     *
     * Accepts a git command to run
     *
     * @access  public
     * @param   string  command to run
     * @return  string
     */
   
public function run($command) {
        return
$this->run_command(Git::get_bin()." ".$command);
    }

   
/**
     * Runs a 'git status' call
     *
     * Accept a convert to HTML bool
     *
     * @access public
     * @param bool  return string with <br />
     * @return string
     */
   
public function status($html = false) {
       
$msg = $this->run("status");
        if (
$html == true) {
           
$msg = str_replace("\n", "<br />", $msg);
        }
        return
$msg;
    }

   
/**
     * Runs a `git add` call
     *
     * Accepts a list of files to add
     *
     * @access  public
     * @param   mixed   files to add
     * @return  string
     */
   
public function add($files = "*") {
        if (
is_array($files)) {
           
$files = '"'.implode('" "', $files).'"';
        }
        return
$this->run("add $files -v");
    }

   
/**
     * Runs a `git rm` call
     *
     * Accepts a list of files to remove
     *
     * @access  public
     * @param   mixed    files to remove
     * @param   Boolean  use the --cached flag?
     * @return  string
     */
   
public function rm($files = "*", $cached = false) {
        if (
is_array($files)) {
           
$files = '"'.implode('" "', $files).'"';
        }
        return
$this->run("rm ".($cached ? '--cached ' : '').$files);
    }


   
/**
     * Runs a `git commit` call
     *
     * Accepts a commit message string
     *
     * @access  public
     * @param   string  commit message
     * @param   boolean  should all files be committed automatically (-a flag)
     * @return  string
     */
   
public function commit($message = "", $commit_all = true) {
       
$flags = $commit_all ? '-av' : '-v';
        return
$this->run("commit ".$flags." -m ".escapeshellarg($message));
    }

   
/**
     * Runs a `git clone` call to clone the current repository
     * into a different directory
     *
     * Accepts a target directory
     *
     * @access  public
     * @param   string  target directory
     * @return  string
     */
   
public function clone_to($target) {
        return
$this->run("clone --local ".$this->repo_path." $target");
    }

   
/**
     * Runs a `git clone` call to clone a different repository
     * into the current repository
     *
     * Accepts a source directory
     *
     * @access  public
     * @param   string  source directory
     * @return  string
     */
   
public function clone_from($source) {
        return
$this->run("clone --local $source ".$this->repo_path);
    }

   
/**
     * Runs a `git clone` call to clone a remote repository
     * into the current repository
     *
     * Accepts a source url
     *
     * @access  public
     * @param   string  source url
     * @param   string  reference path
     * @return  string
     */
   
public function clone_remote($source, $reference) {
        return
$this->run("clone $reference $source ".$this->repo_path);
    }

   
/**
     * Runs a `git clean` call
     *
     * Accepts a remove directories flag
     *
     * @access  public
     * @param   bool    delete directories?
     * @param   bool    force clean?
     * @return  string
     */
   
public function clean($dirs = false, $force = false) {
        return
$this->run("clean".(($force) ? " -f" : "").(($dirs) ? " -d" : ""));
    }

   
/**
     * Runs a `git branch` call
     *
     * Accepts a name for the branch
     *
     * @access  public
     * @param   string  branch name
     * @return  string
     */
   
public function create_branch($branch) {
        return
$this->run("branch " . escapeshellarg($branch));
    }

   
/**
     * Runs a `git branch -[d|D]` call
     *
     * Accepts a name for the branch
     *
     * @access  public
     * @param   string  branch name
     * @return  string
     */
   
public function delete_branch($branch, $force = false) {
        return
$this->run("branch ".(($force) ? '-D' : '-d')." $branch");
    }

   
/**
     * Runs a `git branch` call
     *
     * @access  public
     * @param   bool    keep asterisk mark on active branch
     * @return  array
     */
   
public function list_branches($keep_asterisk = false) {
       
$branchArray = explode("\n", $this->run("branch"));
        foreach(
$branchArray as $i => &$branch) {
           
$branch = trim($branch);
            if (!
$keep_asterisk) {
               
$branch = str_replace("* ", "", $branch);
            }
            if (
$branch == "") {
                unset(
$branchArray[$i]);
            }
        }
        return
$branchArray;
    }

   
/**
     * Lists remote branches (using `git branch -r`).
     *
     * Also strips out the HEAD reference (e.g. "origin/HEAD -> origin/master").
     *
     * @access  public
     * @return  array
     */
   
public function list_remote_branches() {
       
$branchArray = explode("\n", $this->run("branch -r"));
        foreach(
$branchArray as $i => &$branch) {
           
$branch = trim($branch);
            if (
$branch == "" || strpos($branch, 'HEAD -> ') !== false) {
                unset(
$branchArray[$i]);
            }
        }
        return
$branchArray;
    }

   
/**
     * Returns name of active branch
     *
     * @access  public
     * @param   bool    keep asterisk mark on branch name
     * @return  string
     */
   
public function active_branch($keep_asterisk = false) {
       
$branchArray = $this->list_branches(true);
       
$active_branch = preg_grep("/^\*/", $branchArray);
       
reset($active_branch);
        if (
$keep_asterisk) {
            return
current($active_branch);
        } else {
            return
str_replace("* ", "", current($active_branch));
        }
    }

   
/**
     * Runs a `git checkout` call
     *
     * Accepts a name for the branch
     *
     * @access  public
     * @param   string  branch name
     * @return  string
     */
   
public function checkout($branch) {
        return
$this->run("checkout " . escapeshellarg($branch));
    }


   
/**
     * Runs a `git merge` call
     *
     * Accepts a name for the branch to be merged
     *
     * @access  public
     * @param   string $branch
     * @return  string
     */
   
public function merge($branch) {
        return
$this->run("merge " . escapeshellarg($branch) . " --no-ff");
    }


   
/**
     * Runs a git fetch on the current branch
     *
     * @access  public
     * @return  string
     */
   
public function fetch() {
        return
$this->run("fetch");
    }

   
/**
     * Add a new tag on the current position
     *
     * Accepts the name for the tag and the message
     *
     * @param string $tag
     * @param string $message
     * @return string
     */
   
public function add_tag($tag, $message = null) {
        if (
is_null($message)) {
           
$message = $tag;
        }
        return
$this->run("tag -a $tag -m " . escapeshellarg($message));
    }

   
/**
     * List all the available repository tags.
     *
     * Optionally, accept a shell wildcard pattern and return only tags matching it.
     *
     * @access    public
     * @param    string    $pattern    Shell wildcard pattern to match tags against.
     * @return    array                Available repository tags.
     */
   
public function list_tags($pattern = null) {
       
$tagArray = explode("\n", $this->run("tag -l $pattern"));
        foreach (
$tagArray as $i => &$tag) {
           
$tag = trim($tag);
            if (empty(
$tag)) {
                unset(
$tagArray[$i]);
            }
        }

        return
$tagArray;
    }

   
/**
     * Push specific branch (or all branches) to a remote
     *
     * Accepts the name of the remote and local branch.
         * If omitted, the command will be "git push", and therefore will take
         * on the behavior of your "push.defualt" configuration setting.
     *
     * @param string $remote
     * @param string $branch
     * @return string
     */
   
public function push($remote = "", $branch = "") {
               
//--tags removed since this was preventing branches from being pushed (only tags were)
       
return $this->run("push $remote $branch");
    }

   
/**
     * Pull specific branch from remote
     *
     * Accepts the name of the remote and local branch.
         * If omitted, the command will be "git pull", and therefore will take on the
         * behavior as-configured in your clone / environment.
     *
     * @param string $remote
     * @param string $branch
     * @return string
     */
   
public function pull($remote = "", $branch = "") {
        return
$this->run("pull $remote $branch");
    }

   
/**
     * List log entries.
     *
     * @param strgin $format
     * @return string
     */
   
public function log($format = null, $fulldiff=false, $filepath=null, $follow=false) {
       
$diff = "";
       
                if (
$fulldiff){
                   
$diff = "--full-diff -p ";
                }

        if (
$follow){
           
// Can't use full-diff with follow
           
$diff = "--follow -- ";
        }
   
        if (
$format === null)
            return
$this->run('log ' . $diff . $filepath);
        else
            return
$this->run('log --pretty=format:"' . $format . '" ' . $diff .$filepath);
    }

   
/**
     * Sets the project description.
     *
     * @param string $new
     */
   
public function set_description($new) {
       
$path = $this->git_directory_path();
       
file_put_contents($path."/description", $new);
    }

   
/**
     * Gets the project description.
     *
     * @return string
     */
   
public function get_description() {
       
$path = $this->git_directory_path();
        return
file_get_contents($path."/description");
    }

   
/**
     * Sets custom environment options for calling Git
     *
     * @param string key
     * @param string value
     */
   
public function setenv($key, $value) {
       
$this->envopts[$key] = $value;
    }

}

/* End of file */