Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "frozen-doe.net/glip",
"description": "Fork of Git Library In PHP",
"license": "GPL-2.0+",
"authors": [
{
"name": "Patrik Fimml"
}, {
"name": "Josef Kufner",
"email": "jk@frozen-doe.net"
}
],
"require": {
"php": ">=5.1"
},
"autoload": {
"classmap": [ "lib/" ]
}
}

54 changes: 52 additions & 2 deletions doc/mainpage.doxygen
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,65 @@ $master_name = $repo->getTip('master');
$master = $repo->getObject($master_name);
@endcode

@todo It should be possible to look up any ref, not only branches.

In a common git repository, this will give you a GitCommit instance in
$master, referring to the last commit made to the @em master branch.

You can also lookup tag or any ref:

@code
$tag_name = $repo->getTip('v1.0');
$tag = $repo->getObject($tag_name);
@endcode

@code
$origin_master_name = $repo->getTip('refs/remotes/origin/master');
$origin_master = $repo->getObject($tag_name);
@endcode

When you get bored with branches and tags, try Git::getHead():

@code
$head_name = $repo->getHead();
$last_commit = $repo->getObject($head_name);

echo $repo->getHead(false); // false means 'Do not resolve ref, give me human readable name instead.'
@endcode

GitObject%s hold all related data in public attributes. For example,
GitCommit::$summary contains the commit summary of a particular commit.
Utility functions are provided to make it easier to perform common functions.

@subsection tags_refs Working with tags and refs

Tags and refs can be listed using Git::getTags() and Git::getRefs():

@code
echo "<ul>\n";
foreach ($repo->getTags() as $tag => $hash) {
echo "<li>", htmlspecialchars($tag), " &rarr; <code>", sha1_hex($hash), "</code></li>\n";
}
echo "</ul>\n";
@endcode

Usage of Git::getRefs is same, but it returns 'refs/tags/v1.0' instead of 'v1.0'.

@subsection describe Git Describe

There is partial reimplementation of handy 'git describe' command in Git::describe():

@code
$master_name = $repo->getTip('master');
$master = $repo->getObject($master_name); // This is optional, Git::describe() can do this for you.

$describe = $repo->describe($master); // You can use $master_name here also.
echo htmlspecialchars($describe);
@endcode

@note Current implementation is way more stupid than original, but it should be
enough for usual use.

@attention Hash digits included in result string may not uniquely describe commit. There is no check for ambiguous abbrevations.

@section writing Writing to a git repository

To change things in a repository, you currently have to directly make changes
Expand Down
259 changes: 226 additions & 33 deletions lib/git.class.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
/*
* Copyright (C) 2008, 2009 Patrik Fimml
* Copyright (C) 2011 Josef Kufner <jk@frozen-doe.net>
*
* This file is part of glip.
*
Expand All @@ -18,13 +19,6 @@
* along with glip. If not, see <http://www.gnu.org/licenses/>.
*/

require_once('binary.class.php');
require_once('git_object.class.php');
require_once('git_blob.class.php');
require_once('git_commit.class.php');
require_once('git_commit_stamp.class.php');
require_once('git_tree.class.php');

/**
* @relates Git
* @brief Convert a SHA-1 hash from hexadecimal to binary representation.
Expand Down Expand Up @@ -90,7 +84,27 @@ static public function getTypeName($type)
public function __construct($dir)
{
$this->dir = realpath($dir);
if ($this->dir === FALSE || !@is_dir($this->dir))
if ($this->dir === FALSE)
throw new Exception(sprintf('repository not found: %s', $dir));

if (is_file($this->dir)) {
$repo = file_get_contents($this->dir);
if (preg_match('/^gitdir: (.*)/', $repo, $m)) {
if ($m[1][0] == '.') {
if (basename($this->dir) == '.git') {
$this->dir = rtrim(dirname($this->dir), '/').'/'.$m[1];
} else {
$this->dir = rtrim($this->dir, '/').'/'.$m[1];
}
} else {
$this->dir = $m[1];
}
} else {
throw new Exception(sprintf('unknown repository format: %s', $this->dir));
}
}

if (!@is_dir($this->dir))
throw new Exception(sprintf('not a directory: %s', $dir));

$this->packs = array();
Expand Down Expand Up @@ -403,36 +417,215 @@ public function getObject($name)
}

/**
* @brief Look up a branch.
* @brief Look up a branch or tag.
*
* @param $branch (string) The branch to look up, defaulting to @em master.
* @param $branch (string) The branch or tag to look up, defaulting to @em master.
* @returns (string) The tip of the branch (binary sha1).
*/
public function getTip($branch='master')
{
$subpath = sprintf('refs/heads/%s', $branch);
$path = sprintf('%s/%s', $this->dir, $subpath);
if (file_exists($path))
return sha1_bin(file_get_contents($path));
$path = sprintf('%s/packed-refs', $this->dir);
if (file_exists($path))
{
$head = NULL;
$f = fopen($path, 'rb');
flock($f, LOCK_SH);
while ($head === NULL && ($line = fgets($f)) !== FALSE)
{
if ($line{0} == '#')
continue;
$parts = explode(' ', trim($line));
if (count($parts) == 2 && $parts[1] == $subpath)
$head = sha1_bin($parts[0]);
}
fclose($f);
if ($head !== NULL)
return $head;
}
throw new Exception(sprintf('no such branch: %s', $branch));
if (strchr($branch, '/') !== FALSE)
{
$subpath = array($branch);
}
else
{
$subpath = array(
sprintf('refs/heads/%s', $branch),
sprintf('refs/tags/%s', $branch)
);
}
foreach ($subpath as $sp)
{
$path = sprintf('%s/%s', $this->dir, $sp);
if (file_exists($path))
return sha1_bin(file_get_contents($path));
}
$path = sprintf('%s/packed-refs', $this->dir);
if (file_exists($path))
{
$head = NULL;
$f = fopen($path, 'rb');
flock($f, LOCK_SH);
while ($head === NULL && ($line = fgets($f)) !== FALSE)
{
if ($line{0} == '#')
continue;
$parts = explode(' ', trim($line));
if (count($parts) == 2 && in_array($parts[1], $subpath))
$head = sha1_bin($parts[0]);
}
fclose($f);
if ($head !== NULL)
return $head;
}
throw new Exception(sprintf('no such branch: %s', $branch));
}


/**
* @brief Get current head
*
* @param $resolve (bool) It true, returned value is binary sha1, otherwise readable string is returned.
* @returns (string) Current head as readable string or binary sha1 (depends on $resolve arg.). On error, FALSE is returned.
*/
public function getHead($resolve = true)
{
$content = file_get_contents($this->dir.'/HEAD');

if ($content === FALSE)
{
return FALSE;
}
else if (sscanf($content, 'ref: %s', $head) == 1)
{
return $resolve ? $this->getTip($head) : $head;
}
else if (sscanf($content, '%[0-9a-fA-F]s', $hash) == 1)
{
return $resolve ? sha1_bin($hash) : $hash;
}
else
{
return FALSE;
}
}


/**
* @brief List all known refs
*
* @returns (array) List of all known refs (ref -> binary sha1).
*/
public function getRefs()
{
$refs = array();

// refs in files
foreach (array('refs/heads', 'refs/tags') as $subpath)
{
$path = sprintf('%s/%s', $this->dir, $subpath);
foreach(scandir($path) as $f)
{
$file = $path.'/'.$f;
if ($f[0] != '.' && is_file($file)) {
$refs[$subpath.'/'.$f] = sha1_bin(file_get_contents($file));
}
}
}

// packed refs
$path = sprintf('%s/packed-refs', $this->dir);
if (file_exists($path))
{
$head = NULL;
$f = fopen($path, 'rb');
flock($f, LOCK_SH);
while ($head === NULL && ($line = fgets($f)) !== FALSE)
{
if ($line{0} == '#')
continue;
$parts = explode(' ', trim($line));
if (count($parts) == 2)
$refs[$parts[1]] = sha1_bin($parts[0]);
}
fclose($f);
if ($head !== NULL)
return $head;
}

return $refs;
}


/**
* @brief List all tags
*
* @returns (array) List of all tags (tag name -> binary sha1).
*/
public function getTags()
{
$tags = array();
foreach($this->getRefs() as $ref => $hash)
{
if (strncmp($ref, 'refs/tags/', 10) == 0)
{
$tags[substr($ref, 10)] = $hash;
}
}
return $tags;
}

/**
* @brief Implementation of 'git describe' (partial)
*
* @param $commit (GitCommit) Commit to describe or binary sha1 of commit.
* @param $abbrev (int) Hash digits count. Hash may not be uniqe, no checks done.
* @returns (string) 'git describe' string
*/
public function describe($commit, $abbrev = 7)
{
if (is_string($commit))
{
$commit = $this->getObject($commit);
}

if ($commit->getType() == Git::OBJ_TAG)
{
return $commit->tag;
}

// Load tags and get their objects
$tags = array();
foreach($this->getTags() as $tag_name => $hash)
{
$t = $this->getObject($hash);
if ($t->getType() == Git::OBJ_TAG)
{
foreach($t->getObjects() as $obj)
{
$tags[$obj] = $t->tag;
}
}
}

$queue = array(array($commit, 0));
$tag_name = false;
$tag_depth = false;

// DFS (simplified for DAG)
while (!empty($queue))
{
list($tag_object, $tag_depth) = array_shift($queue);
$commit_name = $tag_object->getName();
if (array_key_exists($commit_name, $tags))
{
// Tag found
$tag_name = $tags[$commit_name];
break;
}

// enqueue ancestors
foreach($tag_object->parents as $parent)
{
$parent_object = $this->getObject($parent);
array_push($queue, array($parent_object, $tag_depth + 1));
}
}

// Format result
if ($tag_name === false)
{
return substr(sha1_hex($commit->getName()), 0, $abbrev);
}
if ($tag_depth == 0)
{
return $tag_name;
}
else
{
return sprintf('%s-%d-g%s', $tag_name, $tag_depth, substr(sha1_hex($commit->getName()), 0, $abbrev));
}
}
}

2 changes: 0 additions & 2 deletions lib/git_blob.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
* along with glip. If not, see <http://www.gnu.org/licenses/>.
*/

require_once('git_object.class.php');

class GitBlob extends GitObject
{
/**
Expand Down
3 changes: 0 additions & 3 deletions lib/git_commit.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
* along with glip. If not, see <http://www.gnu.org/licenses/>.
*/

require_once('git_object.class.php');
require_once('git_commit_stamp.class.php');

class GitCommit extends GitObject
{
/**
Expand Down
Loading