GIT-Project Klasse
GIT project handling class
I implemented this GIT class originally to manage gitosis/gitolite reporitories, and nightly build purposes. The example shows you the main features, in short:
- Inspect a local repository directly
- Clone a remote or local directory
- Get log, commits, tags, the tree, branches etc.
- Commit, tag, checkout, push (pull not yet)
Diese Klasse habe ich ursprünglich für Nightly Builds und gitosis/gitolite Repository Management erstellt. Das Beispiel zeigt die Hauptfunktionalitäten, in aller Kürze:
- Ein lokales Repository direkt inspizieren
- Ein lokales oder entferntes Repository "clonen".
- Getters für Log, Commits, Tags, "Trees", "Branches" usw.
- Commit, Tag, Checkout, Push (allerdings noch kein Pull)
Example source code
Anwendungsbeispiel
<?php
require_once('swlib/swlib.class.php');
use sw\GitProject as git;
/**
* First thing: configure (or let it use the defaults). You can ommit configuration
* keys as well to leave the defaults.
*/
git::config(array(
// default is 'git'. You can use any other path as well
'git-bin' => 'git',
// The default directory where to get local projects from
'local-repository-base-dir' => '/tmp/git/repositories',
// You can define your own file format, this is the default:
'time-format' => '%Y-%m-%d %H:%M:%S',
// Data for committing using this class. Null means "Derive it from the
// Apache server data."
'git-config-committer-name' => null,
'git-config-committer-email' => null,
'git-config-author-name' => null,
'git-config-author-email' => null,
// SSH binary for SSH connections NULL = "search it yourself"
'git-config-ssh-bin' => null,
));
/**
*
* Cloned remote: Clone, get some infos, modify, commit, tag, push:
*
*/
print "\n\n------------------------------------------------------------------";
print "\n--- CLONE, MODIFY, COMMIT, TAG, PUSH";
print "\n------------------------------------------------------------------\n\n";
$git = git::remoteRepositoryClone('/tmp/git/repositories/libeemc.git', '/tmp/libeemc');
print "Cloned '/tmp/git/repositories/libeemc.git' to '/tmp/libeemc'\n\n";
$r = $git->getBranches();
print "Branches: " . print_r($r, true) . "\n";
$r = $git->getTags();
print "Tags: " . print_r($r, true) . "\n";
$r = $git->getTreeList();
print "Tree list: " . print_r($r, true) . "\n";
// COMMIT SOMETHING AND PUSH BACK
@file_put_contents('/tmp/libeemc/VERSION', '1.0.0', LOCK_EX);
$git->commit('Set VERSION to 1.0.0', true);
$git->tag('v1.0.0');
$git->push();
$r = $git->getTags();
print "Tags after adding a tag: " . print_r($r, true) . "\n";
$r = $git->log();
print "LOG: " . print_r($r, true) . "\n";
/**
*
* Repository inspection. Not all methods are allowed, so the class (or git)
* will complain if you do something you shouldn't.
*
*/
print "\n\n------------------------------------------------------------------";
print "\n--- REPOSITORY INSPECTION";
print "\n------------------------------------------------------------------\n\n";
// Full path also possible: '/tmp/git/repositories/gitolite-admin.git'
$git = git::localRepository('swlib-php.git');
// Heads
$r = $git->getHeads();
print "Heads: " . print_r($r, true) . "\n" ;
// Commit info of tag or hash, default is HEAD
$r = $git->getCommitInfo();
print "Commit info: " . print_r($r, true) . "\n" ;
Output
Ausgabe
$ php git.test.php
------------------------------------------------------------------
--- CLONE, MODIFY, COMMIT, TAG, PUSH
------------------------------------------------------------------
Cloned '/tmp/git/repositories/libeemc.git' to '/tmp/libeemc'
Branches: Array
(
[0] => master
)
Tags: Array
(
)
Tree list: Array
(
[efe4aad6fad506462437220c455c301f5ac02658] => Array
(
[sha] => efe4aad6fad506462437220c455c301f5ac02658
[path] => .gitignore
[type] => blob
[mode] => 100644
)
[ec9de690e53a4d149efe22428359088b44605367] => Array
(
[sha] => ec9de690e53a4d149efe22428359088b44605367
[path] => Makefile
[type] => blob
[mode] => 100644
)
[e52ae9b3c2641eabd66b2c871ba456b9f4cafaa3] => Array
(
[sha] => e52ae9b3c2641eabd66b2c871ba456b9f4cafaa3
[path] => conf
[type] => tree
[mode] => 040000
)
[4b087f9308a6cebf26d2cfd1ad62bc99e5d7b14a] => Array
(
[sha] => 4b087f9308a6cebf26d2cfd1ad62bc99e5d7b14a
[path] => include
[type] => tree
[mode] => 040000
)
[b384c297b366dadaa61e78370548e059a2ca8712] => Array
(
[sha] => b384c297b366dadaa61e78370548e059a2ca8712
[path] => nbproject
[type] => tree
[mode] => 040000
)
[0fde5b58d0bd72184407a20ffc25d7f682576212] => Array
(
[sha] => 0fde5b58d0bd72184407a20ffc25d7f682576212
[path] => src
[type] => tree
[mode] => 040000
)
)
Tags after adding a tag: Array
(
[0] => Array
(
[hash] => 61f7d1e70715df318df3e591e87aa10ce3f33423
[name] => v1.0.0
)
)
LOG: Array
(
[0] => Array
(
[commit] => 61f7d1e70715df318df3e591e87aa10ce3f33423
[author] => Server <admin@my-server>
[date] => Mon Sep 2 15:12:35 2013 +0200
[comment] => Set VERSION to 1.0.0
)
[1] => Array
(
[commit] => 9a0e53f2642480dbc71706ff2774a4eb9f60b3e8
[author] => stfwi <[email.replaced]>
[date] => Mon Aug 5 17:06:59 2013 +0200
[comment] => ...
)
[2] => Array
(
[commit] => 2e995d1dcdf8df6fb487e5ac11dcc32fe810a77a
[author] => stfwi <[email.replaced]>
[date] => Thu Aug 1 12:29:26 2013 +0200
[comment] => adapted .gitignore
)
[3] => Array
(
[commit] => c2d4b85514e9734ba9ff1e580e4b67180c68b933
[author] => stfwi <[email.replaced]>
[date] => Thu Aug 1 12:18:20 2013 +0200
[comment] => Init
)
)
------------------------------------------------------------------
--- REPOSITORY INSPECTION
------------------------------------------------------------------
Heads: Array
(
[0] => Array
(
[hash] => 0ae932874559f3230fcafa3ca74c63a4fd582375
[name] => localize
)
[1] => Array
(
[hash] => b5faf7a4115e8494c7e2fc678d8577f6c51fc389
[name] => master
)
)
Commit info: Array
(
[commit] => HEAD
[file] =>
[hash] => b5faf7a4115e8494c7e2fc678d8577f6c51fc389
[parents] => Array
(
[c4e51c1142c9d69e39c9122a8479b7ecb94a34d7] => c4e51c1142c9d69e39c9122a8479b7ecb94a34d7
)
[trees] => Array
(
[48417baae32e32061f77b057809e86d59ffbca1a] => 48417baae32e32061f77b057809e86d59ffbca1a
)
[author] => Array
(
[name] => stfwi
[email] => [email.replaced]
[time] => 2013-09-02 10:21:28
[utc] =>
)
[committer] => Array
(
[name] => stfwi
[email] => [email.replaced]
[time] => 2013-09-02 10:21:28
[utc] =>
)
[message] => ...
)
Class source code
Klassen-Quelltext
<?php
/**
* Enables git operation in the local file system. The class is derived from the
* functions.php file of the viewgit project.
*
* @gpackage de.atwillys.sw.php.swLib
* @author Stefan Wilhelm
* @copyright Stefan Wilhelm, 2005-2011
* @license GPL2
* @version 1.0
* @uses Tracer
* @uses GitProjectException
*/
namespace sw;
class GitProject {
/**
* Class configuration
* @var array
*/
private static $config = array(
'git-bin' => 'git',
'local-repository-base-dir' => '/home/gitolite/repositories',
'time-format' => '%Y-%m-%d %H:%M:%S',
'git-config-committer-name' => null,
'git-config-committer-email' => null,
'git-config-author-name' => null,
'git-config-author-email' => null,
'git-config-ssh-bin' => null,
);
/**
* The project name (basename of the repository path without '.git')
* Changed when set getRepositoryPath() is called.
* @var string
*/
protected $projectName = '';
/**
* Contains the project description
* @var string
*/
protected $projectDescription = '';
/**
* Directory of the referred repository
* @var string
*/
protected $projectPath = '';
/**
* Defines if the project location is a gitosis repository path (true) or
* if the location referrs to a cloned project.
* @var bool
*/
protected $isRepository = false;
/**
* Contains the repository location of which the project is cloned form.
* @var string
*/
protected $clonedFrom = '';
/**
* Contains the heads
* @var array
*/
protected $heads = array();
/**
* Contains the defined tags
* @var array
*/
protected $tags = array();
/**
* Contains existing branches
* @var array
*/
protected $branches = array();
/**
* Contains the active branch
* @var string
*/
protected $activeBranch = null;
/**
* Sets/returns the class configuration
* @param array $config=null
* @return array
*/
public static function config($config=null) {
if (!is_null($config)) {
if (!is_array($config)) {
throw new GitProjectException('Configuration must be an assoc. array.');
} else {
self::$config = array_merge(self::$config, $config);
}
$default_name = "Server: {$_SERVER['HTTP_HOST']}";
$default_email = "no-email@{$_SERVER['HTTP_HOST']}";
if (empty(self::$config['git-config-committer-name'])) {
self::$config['git-config-committer-name'] = $default_name;
}
if (empty(self::$config['git-config-committer-email'])) {
self::$config['git-config-committer-email'] = $default_email;
}
if (empty(self::$config['git-config-author-name'])) {
self::$config['git-config-author-name'] = $default_name;
}
if (empty(self::$config['git-config-author-email'])) {
self::$config['git-config-author-email'] = $default_email;
}
if (empty(self::$config['git-config-ssh-bin'])) {
$ssh = trim(shell_exec('which ssh'), "\t\n\r ");
if (empty($ssh)) {
$ssh = 'ssh';
Tracer::trace('Could not automatically get the ssh client using "which ssh"', 2);
}
self::$config['git-config-ssh-bin'] = $ssh;
}
putenv('GIT_COMMITTER_NAME=' . escapeshellarg(self::$config['git-config-committer-name']));
putenv('GIT_COMMITTER_EMAIL=' . escapeshellarg(self::$config['git-config-committer-email']));
putenv('GIT_AUTHOR_NAME=' . escapeshellarg(self::$config['git-config-author-name']));
putenv('GIT_AUTHOR_EMAIL=' . escapeshellarg(self::$config['git-config-author-email']));
putenv('GIT_SSH=' . self::$config['git-config-ssh-bin']);
}
return self::$config;
}
/**
* Returns a GitProject object referred to the given $repository name.
* @param string $repository
* @return GitProject
*/
public static function localRepository($repository) {
$o = new self();
$o->isRepository = true;
if (stripos($repository, self::$config['local-repository-base-dir']) === false && strtolower(FileSystem::getExtension($repository)) == 'git' && stripos($repository, '/.git') === false) {
$repository = str_replace('//', '/', self::$config['local-repository-base-dir'] . '/' . trim(str_ireplace('.git', '', $repository), ' /') . '.git');
}
$o->setPath($repository);
return $o;
}
/**
* Clone a project from a remote ssh repository
* @param string $repository
* @param string $destinationDirectory
* @param string $sshKeyFile
* @return GitProject
*/
public static function remoteRepositoryClone($repository, $destinationDirectory) {
$o = new self();
$return = array();
$code = 0;
$command = " clone --no-hardlinks " . escapeshellarg($repository) . ' ' . escapeshellarg($destinationDirectory);
$cmd = self::$config['git-bin'] . "$command 2>&1";
Tracer::trace("GIT command = $cmd");
$t = ini_get('max_execution_time');
set_time_limit(300);
exec($cmd, $return, $code);
set_time_limit($t);
$o->setPath($destinationDirectory);
return $o;
}
/**
* Constructor
* @param string $path
* @return GitProject
*/
public function __construct($path='') {
if (!empty($path)) {
$this->setPath($path);
}
}
/**
* Executes Git in the repository directory
* @param string $command
* @param bool $addGitDir=true
* @param bool $addWorkTree=true
* @param bool $includeExitCode=false
* @return array
*/
protected function runGit($command, $addGitDir=null, $addWorkTree=null, $includeExitCode=null) {
if (empty($this->projectPath)) {
throw new GitProjectException("Cannot run GIT without a repository path specified before");
}
$addGitDir = $addGitDir === null ? true : $addGitDir;
$addWorkTree = $addWorkTree === null ? true : $addWorkTree;
$includeExitCode = $includeExitCode === null ? false : $includeExitCode;
$return = array();
$code = 0;
$addGitDir = $addGitDir ? (' --git-dir=' . escapeshellarg($this->projectPath . ($this->isRepository ? '' : '/.git'))) : '';
$workTree = $addWorkTree ? ($this->isRepository ? '' : ' --work-tree=' . escapeshellarg($this->projectPath)) : '';
$cmd = self::$config['git-bin'] . "$addGitDir $workTree $command";
Tracer::trace("GIT command = $cmd 2>&1");
$t = ini_get('max_execution_time');
set_time_limit(300);
exec("$cmd 2>&1", $return, $code);
set_time_limit($t);
while (!empty($return) && strlen(trim(reset($return), "\r\t ")) == 0) {
array_shift($return);
}
while (!empty($return) && strlen(trim(end($return), "\r\t ")) == 0) {
array_pop($return);
}
if ($code !== 0) {
Tracer::trace("GIT exit code $code, stdout:" . implode("\n", $return));
}
if ($includeExitCode) {
return array('exitcode' => $code, 'output' => $return);
} else {
return $return;
}
}
/**
* The project name (basename of the repository path without '.git') Changed
* when set getRepositoryPath() is called.
* @return string
*/
public function getProjectName() {
return $this->projectName;
}
/**
* Returns the description file contents (if thie file exists) or ""
* @return string
*/
public function getProjectDescription() {
return $this->projectDescription;
}
/**
* Returns the repository path
* @return string
*/
public function getPath() {
return $this->projectPath;
}
/**
* Sets the project path. If this path ends with ".git", the methods assumes
* the project location is in the in the repository. This means only read
* operations are allowed.
* @param string $path
* @return GitProject
*/
public function setPath($path) {
$path = rtrim($path, ' /');
if (!FileSystem::isDirectory($path)) {
throw new GitProjectException("The directory !path does not exist.", array('!path' => $path));
} else if (!FileSystem::isDirectory($path)) {
throw new GitProjectException("The directory !path is not readable for you.", array('!path' => $path));
} else if (stripos($path, self::$config['local-repository-base-dir']) !== false) {
$this->isRepository = true;
} else if (!FileSystem::isDirectory($path . '/.git')) {
throw new GitProjectException("The path !path does not contain a .git directory.", array('!path' => $path));
}
$this->projectPath = '';
$this->projectDescription = '';
if (!FileSystem::isDirectory($path)) {
throw new GitProjectException("The repository path does not exist: :path", array(':path' => $path));
} else if (!FileSystem::isReadable($path) || !FileSystem::isExecutable($path)) {
throw new GitProjectException("The repository path is not readable for you: :path", array(':path' => $path));
} else if (stripos(FileSystem::getBasename($path), 'gitosis-admin.git') !== false && $this->isRepository) {
throw new GitProjectException("The repository path does not exist :-) :path", array(':path' => $path));
} else {
$this->projectPath = $path;
$this->projectName = FileSystem::getBasename($path, '.git');
if (FileSystem::isFile($this->projectPath . '/description')) {
$this->projectDescription = trim(FileSystem::readFile($this->projectPath . '/description'), "\n\r\t ");
} else if (FileSystem::isFile($this->projectPath . '/.git/description')) {
$this->projectDescription = trim(FileSystem::readFile($this->projectPath . '/.git/description'), "\n\r\t ");
}
}
return $this;
}
/**
* Returns if the project located in the server repository project
* @return bool
*/
public function isRepository() {
return $this->isRepository;
}
/**
* Returns the branch heads as array
* @return array
*/
public function getHeads() {
if (!empty($this->heads)) {
return $this->heads;
}
$this->heads = array();
foreach ($this->runGit('show-ref --heads') as $line) {
list($hash, $line) = explode(' ', $line, 2);
$line = explode('/', $line);
$this->heads[] = array(
'hash' => $hash,
'name' => array_pop($line)
);
if (($line = implode('/', $line)) != 'refs/heads') {
Tracer::trace("Warning: Head $hash: path does not start with 'refs/heads', instad with $line");
}
}
return $this->heads;
}
/**
* Returns the set tags as array
* @return array
*/
public function getTags() {
if (!empty($this->tags)) {
return $this->tags;
}
$this->tags = array();
foreach ($this->runGit('show-ref --tags') as $line) {
list($hash, $line) = explode(' ', $line, 2);
$line = explode('/', $line);
$this->tags[] = array(
'hash' => $hash,
'name' => array_pop($line)
);
if (($line = implode('/', $line)) != 'refs/tags') {
Tracer::trace("Warning: Tag $hash: tag path does not start with 'refs/tags', instad with $line");
}
}
return $this->tags;
}
/**
* Returns the branches
* @return array
*/
public function getBranches() {
if ($this->isRepository) {
throw new GitProjectException("You cannot get or switch branches in a repository, but you can use getHeads() instead");
}
$this->activeBranch = '';
if (empty($this->branches)) {
$this->branches = array();
foreach ($this->runGit('branch') as $v) {
$v = trim($v);
if (!empty($v)) {
if (ltrim($v, '* ') != $v) {
$this->activeBranch = ltrim($v, '* ');
$this->branches[] = $this->activeBranch;
} else {
$this->branches[] = $v;
}
}
}
}
return $this->branches;
}
/**
* Returns the name of the active branch or empty if not on a branch
* @return string
*/
public function getActiveBranch() {
if (is_null($this->activeBranch)) {
$this->getBranches();
}
return $this->activeBranch;
}
/**
* Returns information about a commit as associative array
* @param string $commit
* @param string $file
* @return array
*/
public function getCommitInfo($commit='HEAD', $file='') {
$return = array(
'commit' => $commit,
'file' => $file,
'hash' => '',
'parents' => array(),
'trees' => array(),
'author' => array(),
'committer' => array(),
'message' => ''
);
$commit = escapeshellarg($commit);
$file = empty($file) ? '' : ('-- ' . escapeshellarg($file));
$lines = $this->runGit("rev-list --header --max-count=1 $commit $file");
$return['hash'] = array_shift($lines);
while (!empty($lines)) {
list($key, $line) = explode(' ', trim(array_shift($lines)), 2);
if (empty($key)) {
// Rest is message
while (!empty($lines)) {
$return['message'] .= trim(array_shift($lines)) . "\n";
}
break;
} else if ($key == 'tree' || $key == 'parent') {
$return["{$key}s"][$line] = $line;
} else if ($key == 'committer' || $key == 'author') {
list($name, $line) = explode('<', $line, 2);
$line = explode(' ', $line);
$email = trim(array_shift($line), ' >');
$time = gmstrftime(self::$config['time-format'], array_shift($line));
$offs = array_shift($line);
$utc = gmstrftime(self::$config['time-format'], trim(implode(' ', $line)));
$return[$key] = array(
'name' => $name,
'email' => $email,
'time' => $time,
'utc' => $utc
);
}
}
return $return;
}
/**
* Returns the 'git describe' result, which is the last tag it can find before
* this commit was committed. Returns empty string if no tag was found. The
* $whichMatchEreg is a bash EREG match, only those tags are returned if the
* argument is not empty.
* @param string $commit
* @return string
*/
public function getLastReachableTagOfCommit($commit='HEAD', $whichMatchEreg='') {
if (!empty($whichMatchEreg)) {
$whichMatchEreg = '--match=' . escapeshellarg($whichMatchEreg) . ' ';
}
$r = $this->runGit("describe --abbrev=0 $whichMatchEreg" . escapeshellarg($commit));
$r = trim(reset($r));
if(stripos($r, 'No names found') !== false) $r = '';
return $r;
}
/**
* Returns the file tree of a tag, commit or tree hash
* @param array $tree
* @return array
*/
public function getTreeList($tree='HEAD', $recursive=false) {
$return = array();
foreach ($this->runGit('ls-tree ' . ($recursive ? '-r ' : '') . escapeshellarg($tree)) as $v) {
list($mode, $type, $sha, $path) = preg_split('/[\s]+/', $v, 4);
$return[$sha] = array(
'sha' => $sha,
'path' => $path,
'type' => $type,
'mode' => $mode
);
}
return $return;
}
/**
* Clones a repository to a target directory
* @param string $destinationDirectory
* @return GitProject
*/
public function cloneTo($destinationDirectory) {
if (empty($this->projectPath)) {
throw new GitProjectException("Cannot run GIT without a repository path specified before");
} else if (!FileSystem::isDirectory($destinationDirectory)) {
throw new GitProjectException("Destination directory does not exist");
} else if (!$this->isRepository) {
throw new GitProjectException("Source project is not a repository project and cannot be cloned");
}
if (FileSystem::getBasename($destinationDirectory) != $this->projectName) {
$destinationDirectory = rtrim($destinationDirectory, '/') . '/' . $this->projectName;
}
$this->runGit("clone --no-hardlinks " . escapeshellarg($this->projectPath) . ' ' . escapeshellarg($destinationDirectory), false, false);
$o = new self($destinationDirectory);
$o->clonedFrom = $this->projectPath;
return $o;
}
/**
* Export into a tar/zip archive
* @param string $archiveFile
* @param string $tag
* @param string $format="tar"
*/
public function archiveTo($archiveFile, $tag, $format='tar') {
if (empty($this->projectPath)) {
throw new GitProjectException("Cannot run GIT without a repository path specified before");
} else if (!FileSystem::isDirectory(FileSystem::getDirname($archiveFile))) {
throw new GitProjectException("Destination file parent directory does not exist");
} else if (empty($tag)) {
throw new GitProjectException("No tag given to export");
} else if ($format != 'tar' && $format != 'zip' && $format != 'tar.gz' && $format != 'tgz') {
throw new GitProjectException("Unsupported archive format ':format', must be 'tar', 'tar.gz' or 'zip'", array(':format' => $format));
}
if ($format == 'tar.gz' || $format == 'tgz') {
$gz = true;
$format = 'tar';
}
if (strtolower(FileSystem::getExtension($archiveFile)) != strtolower($format)) {
$archiveFile = "$archiveFile.$format";
}
$this->runGit("archive --format=$format --remote=" . escapeshellarg('file://' . $this->projectPath) . ' ' . escapeshellarg($tag) . " --output=" . escapeshellarg($archiveFile), false, false);
if (!FileSystem::isFile($archiveFile)) {
throw new GitProjectException("Failed to create archive file ':file'", array(':file' => $archiveFile));
}
if (isset($gz)) {
print_r(exec("gzip -9 " . escapeshellarg($archiveFile)));
$archiveFile = "$archiveFile.gz";
if (!FileSystem::isFile($archiveFile)) {
throw new GitProjectException("Failed to create archive file ':file'", array(':file' => $archiveFile));
}
}
return $archiveFile;
}
/**
* Checks out a branch (or given by tag or hash)
* @param string $tag
* @return GitProject
*/
public function checkout($branch) {
if ($this->isRepository) {
throw new GitProjectException("You cannot checkout a branch in the repository");
}
$r = $this->runGit("checkout " . escapeshellarg($branch), null, null, true);
if ($r['exitcode'] != 0) {
Tracer::trace_r($r, 'git checkout ' . escapeshellarg($branch));
throw new GitProjectException('Failed to checkout branch "!branch"', array('!branch' => $branch));
}
$this->branches = array();
return $this;
}
/**
* Commits changes
* @param string $message
* @param bool $addNewFiles
* @return GitProject
*/
public function commit($message, $addNewFiles=true) {
$message = trim(str_replace(array("'", '"'), '', $message), "\n\t\r ");
if ($this->isRepository) {
throw new GitProjectException('You cannot commit changes to a project located in the repository; clone it commit and push it back.');
} else if (empty($message)) {
throw new GitProjectException('You must commit changes with a message');
}
if ($addNewFiles) {
$r = $this->runGit('add .', null, null, true);
if ($r['exitcode'] != 0) {
Tracer::trace_r($r, 'git add .');
if (empty($r['output'])) {
throw new GitProjectException('Failed to add files to during the commit process.');
} else {
throw new GitProjectException('Failed to add files to during the commit process, git says: !error', array('!error' => trim(implode("\n", $r['output'], "\n"))));
}
}
}
$r = $this->runGit('commit -m ' . escapeshellarg($message), null, null, true);
if ($r['exitcode'] != 0) {
Tracer::trace_r($r, 'git commit -m ' . escapeshellarg($message));
if (empty($r['output'])) {
throw new GitProjectException('Failed to commit changes');
} else {
throw new GitProjectException('Failed to commit, git says: "!error"', array('!error' => end($r['output'])));
}
}
return $this;
}
/**
* Pushes the actual branch up to the repository (using "git push '<remote path>' '<active branch>'")
*
* @param $remote = null
* @param $branch = null
* @return GitProject
*/
public function push($remote=null, $branch=null) {
if ($this->isRepository) {
throw new GitProjectException('You cannot push a project located in the repository');
}
$branch = strlen(trim($branch)) > 0 ? $branch : $this->getActiveBranch();
$remote = strlen(trim($remote)) > 0 ? $branch : $this->getPath();
$r = $this->runGit("push '$remote' '$branch'", null, null, true);
if ($r['exitcode'] != 0) {
Tracer::trace_r($r, 'git push');
if (!empty($this->clonedFrom) && !FileSystem::isWritable($this->clonedFrom)) {
throw new GitProjectException('Pushing changes to the repository failed: Repository directory of which the project is cloned from is not writable.');
} else if (empty($r['output'])) {
throw new GitProjectException('Pushing changes to the repository failed');
} else {
throw new GitProjectException('Pushing changes to the repository failed, git says: !error', array('!error' => trim(implode("\n", $r['output'], "\n"))));
}
}
return $this;
}
/**
* Adds the tag $tagname to the actual commit.
*
* @param string $tagname
* @return GitProject
*/
public function tag($tagname) {
if ($this->isRepository) {
throw new GitProjectException('You cannot tag in the repository');
} else if(strlen($tagname=trim($tagname)) == 0) {
throw new GitProjectException('No tag name given');
}
$r = $this->runGit("tag '$tagname'", null, null, true);
if ($r['exitcode'] != 0) {
Tracer::trace_r($r, 'git tag');
throw new GitProjectException('Failed to tag, git says: !error', array('!error' => trim(implode("\n", $r['output'], "\n"))));
}
return $this;
}
/**
* Returns the commit log of cloned projects
* @param int $numLastCommits=null
* @return array
*/
public function log($numLastCommits=null) {
if ($this->isRepository) {
throw new GitProjectException('The git log command is only valid in cloned projects');
}
$numLastCommits = (is_numeric($numLastCommits) && intval($numLastCommits) > 0) ? (' -' . intval($numLastCommits)) : '';
$r = $this->runGit("log $numLastCommits", null, null, true);
if ($r['exitcode'] != 0) {
if (empty($r['output'])) {
throw new GitProjectException('Getting the log failed: !error', array('!error' => trim(implode("\n", $r['output'], "\n"))));
} else {
throw new GitProjectException('Getting the log failed');
}
}
$log = $matches = $commit = array();
while (!empty($r['output'])) {
$line = array_shift($r['output']);
if (preg_match('/^commit[\s]([0-9a-f]+)/i', $line, $matches)) {
$log[] = array();
end($log);
$commit = &$log[key($log)];
$commit['commit'] = end($matches);
} else if (preg_match('/^author:[\s](.+)/i', $line, $matches)) {
$commit['author'] = trim(end($matches));
} else if (preg_match('/^date:[\s](.+)/i', $line, $matches)) {
$commit['date'] = trim(end($matches));
} else if (preg_match('/^[\s](.+)/i', $line, $matches)) {
if (!isset($commit['comment'])) {
$commit['comment'] = trim(end($matches), " \t\n\r");
} else {
$commit['comment'] .= ' ' . trim(end($matches), " \t\n\r");
}
}
}
return $log;
}
}