Don't read description repeatedly
Don't read description repeatedly

<?php <?php
/** /**
* GitPHP Project config file * GitPHP Project config file
* *
* Copy this example file to config/projects.conf.php * Copy this example file to config/projects.conf.php
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2010 Christopher Han * @copyright Copyright (c) 2010 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Config * @subpackage Config
*/ */
   
   
/* /*
* git_projects * git_projects
* List of projects * List of projects
* *
* There are three ways to list projects: * There are several ways to list projects:
* *
* 1. Array of projects * 1. Array of projects
*/ */
//$git_projects = array( //$git_projects = array(
// 'gentoo.git' // 'gentoo.git'
// 'core/fbx.git', // 'core/fbx.git',
// 'php/gitphp.git', // 'php/gitphp.git',
// 'php/mdb.git', // 'php/mdb.git',
// 'php/xxcache.git', // 'php/xxcache.git',
// 'websites/bth.git', // 'websites/bth.git',
//); //);
   
   
/* /*
* 2. Path to file with list of projects * 2. Path to file with list of projects
* Points to a flat file with a list of projects, * Points to a flat file with a list of projects,
* one on each line. Can also read Gitosis project lists. * one on each line. Can also read Gitosis project lists.
*/ */
//$git_projects = '/git/projectlist.txt'; //$git_projects = '/git/projectlist.txt';
   
/* /*
* 3. Leave commented to read all projects in the project root * 3. Path to scm-manager repositories.xml file
  * Points to the repository config file used by the scm-manager
  * program. In order for this to work, the projectroot for GitPHP
  * must be the same as the git repository directory configured
  * in scm-manager
  */
  //$git_projects = '~/.scm/config/repositories.xml';
   
  /*
  * 4. Leave commented to read all projects in the project root
*/ */
   
   
/* /*
* git_projects_settings * git_projects_settings
* *
* This is used to specify override settings for individual projects. * This is used to specify override settings for individual projects.
* This is an array, where each key is the project, and the value is an * This is an array, where each key is the project, and the value is an
* array of settings. This can be used with any of the project list * array of settings. This can be used with any of the project list
* methods above. * methods above.
* *
* The settings array can have the following key/value settings: * The settings array can have the following key/value settings:
* *
* 'category': the category for the project. * 'category': the category for the project.
* *
* 'owner': the owner of the project. This overrides the actual owner * 'owner': the owner of the project. This overrides the actual owner
* *
* 'description': the description of the project. This overrides the * 'description': the description of the project. This overrides the
* description in the project's description file * description in the project's description file
* *
* 'cloneurl': the full clone url of the project. This overrides the * 'cloneurl': the full clone url of the project. This overrides the
* clone URL setting in the config for this project. * clone URL setting in the config for this project.
* This can also be an empty string to override the global * This can also be an empty string to override the global
* clone url to say that only this project has no clone url. * clone url to say that only this project has no clone url.
* *
* 'pushurl': the full push url of the project. This overrides the * 'pushurl': the full push url of the project. This overrides the
* push URL setting in the config for this project. * push URL setting in the config for this project.
* This can also be an empty string to override the global * This can also be an empty string to override the global
* push url to say that only this project has no push url. * push url to say that only this project has no push url.
* *
* 'bugpattern': the bug number pattern of the project. This overrides * 'bugpattern': the bug number pattern of the project. This overrides
* the bug pattern setting in the config for this project. * the bug pattern setting in the config for this project.
* This can also be an empty string to override the global * This can also be an empty string to override the global
* bug pattern to say that only this project has no bug * bug pattern to say that only this project has no bug
* pattern. * pattern.
* *
* 'bugurl': the bug url for this project. This overrides the bug url * 'bugurl': the bug url for this project. This overrides the bug url
* setting in the config for this project. This can also be * setting in the config for this project. This can also be
* an empty string to override the global bug url to say that * an empty string to override the global bug url to say that
* only this project has no bug url. * only this project has no bug url.
*/ */
//$git_projects_settings['php/gitphp.git'] = array( //$git_projects_settings['php/gitphp.git'] = array(
// 'category' => 'PHP', // 'category' => 'PHP',
// 'description' => 'GitPHP, a web-based git repository browser in PHP', // 'description' => 'GitPHP, a web-based git repository browser in PHP',
// 'owner' => 'Christopher Han', // 'owner' => 'Christopher Han',
// 'cloneurl' => 'http://git.xiphux.com/php/gitphp.git', // 'cloneurl' => 'http://git.xiphux.com/php/gitphp.git',
// 'pushurl' => '', // 'pushurl' => '',
// 'bugpattern' => '/#([0-9]+)/', // 'bugpattern' => '/#([0-9]+)/',
// 'bugurl' => 'http://mantis.xiphux.com/view.php?id=${1}' // 'bugurl' => 'http://mantis.xiphux.com/view.php?id=${1}'
//); //);
//$git_projects_settings['gentoo.git'] = array( //$git_projects_settings['gentoo.git'] = array(
// 'description' => 'Gentoo portage overlay' // 'description' => 'Gentoo portage overlay'
//); //);
//$git_projects_settings['core/fbx.git'] = array( //$git_projects_settings['core/fbx.git'] = array(
// 'description' => 'FBX music player', // 'description' => 'FBX music player',
// 'category' => 'Core' // 'category' => 'Core'
//); //);
//$git_projects_settings['php/mdb.git'] = array( //$git_projects_settings['php/mdb.git'] = array(
// 'category' => 'PHP', // 'category' => 'PHP',
// 'description' => 'MDB: Media Database', // 'description' => 'MDB: Media Database',
// 'cloneurl' => '', // 'cloneurl' => '',
// 'pushurl' => '' // 'pushurl' => ''
//); //);
   
<?php <?php
/** /**
* GitPHP Project * GitPHP Project
* *
* Represents a single git project * Represents a single git project
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2010 Christopher Han * @copyright Copyright (c) 2010 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
   
require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php'); require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Commit.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Commit.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Head.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Head.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Tag.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Tag.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Pack.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Pack.class.php');
   
/** /**
* Project class * Project class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Project class GitPHP_Project
{ {
   
/** /**
* projectRoot * projectRoot
* *
* Stores the project root internally * Stores the project root internally
* *
* @access protected * @access protected
*/ */
protected $projectRoot; protected $projectRoot;
   
/** /**
* project * project
* *
* Stores the project internally * Stores the project internally
* *
* @access protected * @access protected
*/ */
protected $project; protected $project;
   
/** /**
* owner * owner
* *
* Stores the owner internally * Stores the owner internally
* *
* @access protected * @access protected
*/ */
protected $owner = ""; protected $owner = "";
   
/** /**
* ownerRead * ownerRead
* *
* Stores whether the file owner has been read * Stores whether the file owner has been read
* *
* @access protected * @access protected
*/ */
protected $ownerRead = false; protected $ownerRead = false;
   
/** /**
* description * description
* *
* Stores the description internally * Stores the description internally
* *
* @access protected * @access protected
*/ */
protected $description; protected $description;
   
/** /**
* readDescription * readDescription
* *
* Stores whether the description has been * Stores whether the description has been
* read from the file yet * read from the file yet
* *
* @access protected * @access protected
*/ */
protected $readDescription = false; protected $readDescription = false;
   
/** /**
* category * category
* *
* Stores the category internally * Stores the category internally
* *
* @access protected * @access protected
*/ */
protected $category = ''; protected $category = '';
   
/** /**
* epoch * epoch
* *
* Stores the project epoch internally * Stores the project epoch internally
* *
* @access protected * @access protected
*/ */
protected $epoch; protected $epoch;
   
/** /**
* epochRead * epochRead
* *
* Stores whether the project epoch has been read yet * Stores whether the project epoch has been read yet
* *
* @access protected * @access protected
*/ */
protected $epochRead = false; protected $epochRead = false;
   
/** /**
* head * head
* *
* Stores the head hash internally * Stores the head hash internally
* *
* @access protected * @access protected
*/ */
protected $head; protected $head;
   
/** /**
* readHeadRef * readHeadRef
* *
* Stores whether the head ref has been read yet * Stores whether the head ref has been read yet
* *
* @access protected * @access protected
*/ */
protected $readHeadRef = false; protected $readHeadRef = false;
   
/** /**
* tags * tags
* *
* Stores the tags for the project * Stores the tags for the project
* *
* @access protected * @access protected
*/ */
protected $tags = array(); protected $tags = array();
   
/** /**
* heads * heads
* *
* Stores the heads for the project * Stores the heads for the project
* *
* @access protected * @access protected
*/ */
protected $heads = array(); protected $heads = array();
   
/** /**
* readRefs * readRefs
* *
* Stores whether refs have been read yet * Stores whether refs have been read yet
* *
* @access protected * @access protected
*/ */
protected $readRefs = false; protected $readRefs = false;
   
/** /**
* cloneUrl * cloneUrl
* *
* Stores the clone url internally * Stores the clone url internally
* *
* @access protected * @access protected
*/ */
protected $cloneUrl = null; protected $cloneUrl = null;
   
/** /**
* pushUrl * pushUrl
* *
* Stores the push url internally * Stores the push url internally
* *
* @access protected * @access protected
*/ */
protected $pushUrl = null; protected $pushUrl = null;
   
/** /**
* bugUrl * bugUrl
* *
* Stores the bug url internally * Stores the bug url internally
* *
* @access protected * @access protected
*/ */
protected $bugUrl = null; protected $bugUrl = null;
   
/** /**
* bugPattern * bugPattern
* *
* Stores the bug pattern internally * Stores the bug pattern internally
* *
* @access protected * @access protected
*/ */
protected $bugPattern = null; protected $bugPattern = null;
   
/** /**
* commitCache * commitCache
* *
* Caches fetched commit objects in case of * Caches fetched commit objects in case of
* repeated requests for the same object * repeated requests for the same object
* *
* @access protected * @access protected
*/ */
protected $commitCache = array(); protected $commitCache = array();
   
/** /**
* packs * packs
* *
* Stores the list of packs * Stores the list of packs
* *
* @access protected * @access protected
*/ */
protected $packs = array(); protected $packs = array();
   
/** /**
* packsRead * packsRead
* *
* Stores whether packs have been read * Stores whether packs have been read
* *
* @access protected * @access protected
*/ */
protected $packsRead = false; protected $packsRead = false;
   
/** /**
* __construct * __construct
* *
* Class constructor * Class constructor
* *
* @access public * @access public
* @param string $projectRoot project root * @param string $projectRoot project root
* @param string $project project * @param string $project project
* @throws Exception if project is invalid or outside of projectroot * @throws Exception if project is invalid or outside of projectroot
*/ */
public function __construct($projectRoot, $project) public function __construct($projectRoot, $project)
{ {
$this->projectRoot = GitPHP_Util::AddSlash($projectRoot); $this->projectRoot = GitPHP_Util::AddSlash($projectRoot);
$this->SetProject($project); $this->SetProject($project);
} }
   
/** /**
* SetProject * SetProject
* *
* Attempts to set the project * Attempts to set the project
* *
* @access private * @access private
* @throws Exception if project is invalid or outside of projectroot * @throws Exception if project is invalid or outside of projectroot
*/ */
private function SetProject($project) private function SetProject($project)
{ {
$realProjectRoot = realpath($this->projectRoot); $realProjectRoot = realpath($this->projectRoot);
$path = $this->projectRoot . $project; $path = $this->projectRoot . $project;
$fullPath = realpath($path); $fullPath = realpath($path);
   
if (!is_dir($fullPath)) { if (!is_dir($fullPath)) {
throw new Exception(sprintf(__('%1$s is not a directory'), $project)); throw new Exception(sprintf(__('%1$s is not a directory'), $project));
} }
   
if (!is_file($fullPath . '/HEAD')) { if (!is_file($fullPath . '/HEAD')) {
throw new Exception(sprintf(__('%1$s is not a git repository'), $project)); throw new Exception(sprintf(__('%1$s is not a git repository'), $project));
} }
   
if (preg_match('/(^|\/)\.{0,2}(\/|$)/', $project)) { if (preg_match('/(^|\/)\.{0,2}(\/|$)/', $project)) {
throw new Exception(sprintf(__('%1$s is attempting directory traversal'), $project)); throw new Exception(sprintf(__('%1$s is attempting directory traversal'), $project));
} }
   
$pathPiece = substr($fullPath, 0, strlen($realProjectRoot)); $pathPiece = substr($fullPath, 0, strlen($realProjectRoot));
   
if ((!is_link($path)) && (strcmp($pathPiece, $realProjectRoot) !== 0)) { if ((!is_link($path)) && (strcmp($pathPiece, $realProjectRoot) !== 0)) {
throw new Exception(sprintf(__('%1$s is outside of the projectroot'), $project)); throw new Exception(sprintf(__('%1$s is outside of the projectroot'), $project));
} }
   
$this->project = $project; $this->project = $project;
   
} }
   
/** /**
* GetOwner * GetOwner
* *
* Gets the project's owner * Gets the project's owner
* *
* @access public * @access public
* @return string project owner * @return string project owner
*/ */
public function GetOwner() public function GetOwner()
{ {
if (empty($this->owner) && !$this->ownerRead) { if (empty($this->owner) && !$this->ownerRead) {
$this->ReadOwner(); $this->ReadOwner();
} }
return $this->owner; return $this->owner;
} }
   
/** /**
* ReadOwner * ReadOwner
* *
* Reads the project owner * Reads the project owner
* *
* @access protected * @access protected
*/ */
protected function ReadOwner() protected function ReadOwner()
{ {
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
$this->ReadOwnerGit(); $this->ReadOwnerGit();
} else { } else {
$this->ReadOwnerRaw(); $this->ReadOwnerRaw();
} }
   
if (empty($this->owner) && function_exists('posix_getpwuid')) { if (empty($this->owner) && function_exists('posix_getpwuid')) {
$uid = fileowner($this->GetPath()); $uid = fileowner($this->GetPath());
if ($uid !== false) { if ($uid !== false) {
$data = posix_getpwuid($uid); $data = posix_getpwuid($uid);
if (isset($data['gecos']) && !empty($data['gecos'])) { if (isset($data['gecos']) && !empty($data['gecos'])) {
$this->owner = $data['gecos']; $this->owner = $data['gecos'];
} elseif (isset($data['name']) && !empty($data['name'])) { } elseif (isset($data['name']) && !empty($data['name'])) {
$this->owner = $data['name']; $this->owner = $data['name'];
} }
} }
} }
   
$this->ownerRead = true; $this->ownerRead = true;
} }
   
/** /**
* ReadOwnerGit * ReadOwnerGit
* *
* Reads the project owner using the git executable * Reads the project owner using the git executable
* *
* @access private * @access private
*/ */
private function ReadOwnerGit() private function ReadOwnerGit()
{ {
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = 'gitweb.owner'; $args[] = 'gitweb.owner';
$this->owner = $exe->Execute(GIT_CONFIG, $args); $this->owner = $exe->Execute(GIT_CONFIG, $args);
unset($exe); unset($exe);
} }
   
/** /**
* ReadOwnerRaw * ReadOwnerRaw
* *
* Reads the project owner using the raw config file * Reads the project owner using the raw config file
* *
* @access private * @access private
*/ */
private function ReadOwnerRaw() private function ReadOwnerRaw()
{ {
// not worth writing a full config parser right now // not worth writing a full config parser right now
   
if (!file_exists($this->GetPath() . '/config')) if (!file_exists($this->GetPath() . '/config'))
return; return;
   
$configData = explode("\n", file_get_contents($this->GetPath() . '/config')); $configData = explode("\n", file_get_contents($this->GetPath() . '/config'));
   
$gitwebSection = false; $gitwebSection = false;
foreach ($configData as $configLine) { foreach ($configData as $configLine) {
$trimmed = trim($configLine); $trimmed = trim($configLine);
if (empty($trimmed)) { if (empty($trimmed)) {
continue; continue;
} }
   
if (preg_match('/^\[(.+)\]$/', $trimmed, $regs)) { if (preg_match('/^\[(.+)\]$/', $trimmed, $regs)) {
// section header // section header
$gitwebSection = ($regs[1] == 'gitweb'); $gitwebSection = ($regs[1] == 'gitweb');
} else if ($gitwebSection) { } else if ($gitwebSection) {
$eq = strpos($trimmed, '='); $eq = strpos($trimmed, '=');
if ($eq === false) { if ($eq === false) {
continue; continue;
} }
   
$key = trim(substr($trimmed, 0, $eq)); $key = trim(substr($trimmed, 0, $eq));
if ($key == 'owner') { if ($key == 'owner') {
$this->owner = trim(substr($trimmed, $eq+1)); $this->owner = trim(substr($trimmed, $eq+1));
break; break;
} }
} }
} }
} }
   
/** /**
* SetOwner * SetOwner
* *
* Sets the project's owner (from an external source) * Sets the project's owner (from an external source)
* *
* @access public * @access public
* @param string $owner the owner * @param string $owner the owner
*/ */
public function SetOwner($owner) public function SetOwner($owner)
{ {
$this->owner = $owner; $this->owner = $owner;
} }
   
/** /**
* GetProject * GetProject
* *
* Gets the project * Gets the project
* *
* @access public * @access public
* @return string the project * @return string the project
*/ */
public function GetProject() public function GetProject()
{ {
return $this->project; return $this->project;
} }
   
/** /**
* GetProjectRoot * GetProjectRoot
* *
* Gets the project root * Gets the project root
* *
* @access public * @access public
* @return string the project root * @return string the project root
*/ */
public function GetProjectRoot() public function GetProjectRoot()
{ {
return $this->projectRoot; return $this->projectRoot;
} }
   
/** /**
* GetSlug * GetSlug
* *
* Gets the project as a filename/url friendly slug * Gets the project as a filename/url friendly slug
* *
* @access public * @access public
* @return string the slug * @return string the slug
*/ */
public function GetSlug() public function GetSlug()
{ {
$project = $this->project; $project = $this->project;
   
if (substr($project, -4) == '.git') if (substr($project, -4) == '.git')
$project = substr($project, 0, -4); $project = substr($project, 0, -4);
return GitPHP_Util::MakeSlug($project); return GitPHP_Util::MakeSlug($project);
} }
   
/** /**
* GetPath * GetPath
* *
* Gets the full project path * Gets the full project path
* *
* @access public * @access public
* @return string project path * @return string project path
*/ */
public function GetPath() public function GetPath()
{ {
return $this->projectRoot . $this->project; return $this->projectRoot . $this->project;
} }
   
/** /**
* GetDescription * GetDescription
* *
* Gets the project description * Gets the project description
* *
* @access public * @access public
* @param $trim length to trim description to (0 for no trim) * @param $trim length to trim description to (0 for no trim)
* @return string project description * @return string project description
*/ */
public function GetDescription($trim = 0) public function GetDescription($trim = 0)
{ {
if (!$this->readDescription) { if (!$this->readDescription) {
$this->description = file_get_contents($this->GetPath() . '/description'); if (file_exists($this->GetPath() . '/description')) {
  $this->description = file_get_contents($this->GetPath() . '/description');
  }
  $this->readDescription = true;
} }
if (($trim > 0) && (strlen($this->description) > $trim)) { if (($trim > 0) && (strlen($this->description) > $trim)) {
return substr($this->description, 0, $trim) . '…'; return substr($this->description, 0, $trim) . '…';
} }
   
return $this->description; return $this->description;
} }
   
/** /**
* SetDescription * SetDescription
* *
* Overrides the project description * Overrides the project description
* *
* @access public * @access public
* @param string $descr description * @param string $descr description
*/ */
public function SetDescription($descr) public function SetDescription($descr)
{ {
$this->description = $descr; $this->description = $descr;
$this->readDescription = true; $this->readDescription = true;
} }
   
/** /**
* GetDaemonEnabled * GetDaemonEnabled
* *
* Returns whether gitdaemon is allowed for this project * Returns whether gitdaemon is allowed for this project
* *
* @access public * @access public
* @return boolean git-daemon-export-ok? * @return boolean git-daemon-export-ok?
*/ */
public function GetDaemonEnabled() public function GetDaemonEnabled()
{ {
return file_exists($this->GetPath() . '/git-daemon-export-ok'); return file_exists($this->GetPath() . '/git-daemon-export-ok');
} }
   
/** /**
* GetCategory * GetCategory
* *
* Gets the project's category * Gets the project's category
* *
* @access public * @access public
* @return string category * @return string category
*/ */
public function GetCategory() public function GetCategory()
{ {
return $this->category; return $this->category;
} }
   
/** /**
* SetCategory * SetCategory
* *
* Sets the project's category * Sets the project's category
* *
* @access public * @access public
* @param string $category category * @param string $category category
*/ */
public function SetCategory($category) public function SetCategory($category)
{ {
$this->category = $category; $this->category = $category;
} }
   
/** /**
* GetCloneUrl * GetCloneUrl
* *
* Gets the clone URL for this repository, if specified * Gets the clone URL for this repository, if specified
* *
* @access public * @access public
* @return string clone url * @return string clone url
*/ */
public function GetCloneUrl() public function GetCloneUrl()
{ {
if ($this->cloneUrl !== null) if ($this->cloneUrl !== null)
return $this->cloneUrl; return $this->cloneUrl;
   
$cloneurl = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('cloneurl', ''), false); $cloneurl = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('cloneurl', ''), false);
if (!empty($cloneurl)) if (!empty($cloneurl))
$cloneurl .= $this->project; $cloneurl .= $this->project;
   
return $cloneurl; return $cloneurl;
} }
   
/** /**
* SetCloneUrl * SetCloneUrl
* *
* Overrides the clone URL for this repository * Overrides the clone URL for this repository
* *
* @access public * @access public
* @param string $cUrl clone url * @param string $cUrl clone url
*/ */
public function SetCloneUrl($cUrl) public function SetCloneUrl($cUrl)
{ {
$this->cloneUrl = $cUrl; $this->cloneUrl = $cUrl;
} }
   
/** /**
* GetPushUrl * GetPushUrl
* *
* Gets the push URL for this repository, if specified * Gets the push URL for this repository, if specified
* *
* @access public * @access public
* @return string push url * @return string push url
*/ */
public function GetPushUrl() public function GetPushUrl()
{ {
if ($this->pushUrl !== null) if ($this->pushUrl !== null)
return $this->pushUrl; return $this->pushUrl;
   
$pushurl = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('pushurl', ''), false); $pushurl = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('pushurl', ''), false);
if (!empty($pushurl)) if (!empty($pushurl))
$pushurl .= $this->project; $pushurl .= $this->project;
   
return $pushurl; return $pushurl;
} }
   
/** /**
* SetPushUrl * SetPushUrl
* *
* Overrides the push URL for this repository * Overrides the push URL for this repository
* *
* @access public * @access public
* @param string $pUrl push url * @param string $pUrl push url
*/ */
public function SetPushUrl($pUrl) public function SetPushUrl($pUrl)
{ {
$this->pushUrl = $pUrl; $this->pushUrl = $pUrl;
} }
   
/** /**
* GetBugUrl * GetBugUrl
* *
* Gets the bug URL for this repository, if specified * Gets the bug URL for this repository, if specified
* *
* @access public * @access public
* @return string bug url * @return string bug url
*/ */
public function GetBugUrl() public function GetBugUrl()
{ {
if ($this->bugUrl != null) if ($this->bugUrl != null)
return $this->bugUrl; return $this->bugUrl;
   
return GitPHP_Config::GetInstance()->GetValue('bugurl', ''); return GitPHP_Config::GetInstance()->GetValue('bugurl', '');
} }
   
/** /**
* SetBugUrl * SetBugUrl
* *
* Overrides the bug URL for this repository * Overrides the bug URL for this repository
* *
* @access public * @access public
* @param string $bUrl bug url * @param string $bUrl bug url
*/ */
public function SetBugUrl($bUrl) public function SetBugUrl($bUrl)
{ {
$this->bugUrl = $bUrl; $this->bugUrl = $bUrl;
} }
   
/** /**
* GetBugPattern * GetBugPattern
* *
* Gets the bug pattern for this repository, if specified * Gets the bug pattern for this repository, if specified
* *
* @access public * @access public
* @return string bug pattern * @return string bug pattern
*/ */
public function GetBugPattern() public function GetBugPattern()
{ {
if ($this->bugPattern != null) if ($this->bugPattern != null)
return $this->bugPattern; return $this->bugPattern;
   
return GitPHP_Config::GetInstance()->GetValue('bugpattern', ''); return GitPHP_Config::GetInstance()->GetValue('bugpattern', '');
} }
   
/** /**
* SetBugPattern * SetBugPattern
* *
* Overrides the bug pattern for this repository * Overrides the bug pattern for this repository
* *
* @access public * @access public
* @param string $bPat bug pattern * @param string $bPat bug pattern
*/ */
public function SetBugPattern($bPat) public function SetBugPattern($bPat)
{ {
$this->bugPattern = $bPat; $this->bugPattern = $bPat;
} }
   
/** /**
* GetHeadCommit * GetHeadCommit
* *
* Gets the head commit for this project * Gets the head commit for this project
* Shortcut for getting the tip commit of the HEAD branch * Shortcut for getting the tip commit of the HEAD branch
* *
* @access public * @access public
* @return mixed head commit * @return mixed head commit
*/ */
public function GetHeadCommit() public function GetHeadCommit()
{ {
if (!$this->readHeadRef) if (!$this->readHeadRef)
$this->ReadHeadCommit(); $this->ReadHeadCommit();
   
return $this->GetCommit($this->head); return $this->GetCommit($this->head);
} }
   
/** /**
* ReadHeadCommit * ReadHeadCommit
* *
* Reads the head commit hash * Reads the head commit hash
* *
* @access protected * @access protected
*/ */
public function ReadHeadCommit() public function ReadHeadCommit()
{ {
$this->readHeadRef = true; $this->readHeadRef = true;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
$this->ReadHeadCommitGit(); $this->ReadHeadCommitGit();
} else { } else {
$this->ReadHeadCommitRaw(); $this->ReadHeadCommitRaw();
} }
} }
   
/** /**
* ReadHeadCommitGit * ReadHeadCommitGit
* *
* Read head commit using git executable * Read head commit using git executable
* *
* @access private * @access private
*/ */
private function ReadHeadCommitGit() private function ReadHeadCommitGit()
{ {
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = '--verify'; $args[] = '--verify';
$args[] = 'HEAD'; $args[] = 'HEAD';
$this->head = trim($exe->Execute(GIT_REV_PARSE, $args)); $this->head = trim($exe->Execute(GIT_REV_PARSE, $args));
} }
   
/** /**
* ReadHeadCommitRaw * ReadHeadCommitRaw
* *
* Read head commit using raw git head pointer * Read head commit using raw git head pointer
* *
* @access private * @access private
*/ */
private function ReadHeadCommitRaw() private function ReadHeadCommitRaw()
{ {
$head = trim(file_get_contents($this->GetPath() . '/HEAD')); $head = trim(file_get_contents($this->GetPath() . '/HEAD'));
if (preg_match('/^([0-9A-Fa-f]{40})$/', $head, $regs)) { if (preg_match('/^([0-9A-Fa-f]{40})$/', $head, $regs)) {
/* Detached HEAD */ /* Detached HEAD */
$this->head = $regs[1]; $this->head = $regs[1];
} else if (preg_match('/^ref: (.+)$/', $head, $regs)) { } else if (preg_match('/^ref: (.+)$/', $head, $regs)) {
/* standard pointer to head */ /* standard pointer to head */
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
if (isset($this->heads[$regs[1]])) { if (isset($this->heads[$regs[1]])) {
$this->head = $this->heads[$regs[1]]->GetHash(); $this->head = $this->heads[$regs[1]]->GetHash();
} }
} }
} }
   
/** /**
* GetCommit * GetCommit
* *
* Get a commit for this project * Get a commit for this project
* *
* @access public * @access public
*/ */
public function GetCommit($hash) public function GetCommit($hash)
{ {
if (empty($hash)) if (empty($hash))
return null; return null;
   
if ($hash === 'HEAD') if ($hash === 'HEAD')
return $this->GetHeadCommit(); return $this->GetHeadCommit();
   
if (substr_compare($hash, 'refs/heads/', 0, 11) === 0) { if (substr_compare($hash, 'refs/heads/', 0, 11) === 0) {
$head = $this->GetHead(substr($hash, 11)); $head = $this->GetHead(substr($hash, 11));
if ($head != null) if ($head != null)
return $head->GetCommit(); return $head->GetCommit();
return null; return null;
} else if (substr_compare($hash, 'refs/tags/', 0, 10) === 0) { } else if (substr_compare($hash, 'refs/tags/', 0, 10) === 0) {
$tag = $this->GetTag(substr($hash, 10)); $tag = $this->GetTag(substr($hash, 10));
if ($tag != null) { if ($tag != null) {
$obj = $tag->GetCommit(); $obj = $tag->GetCommit();
if ($obj != null) { if ($obj != null) {
return $obj; return $obj;
} }
} }
return null; return null;
} }
   
if (preg_match('/[0-9a-f]{40}/i', $hash)) { if (preg_match('/[0-9a-f]{40}/i', $hash)) {
   
if (!isset($this->commitCache[$hash])) { if (!isset($this->commitCache[$hash])) {
$cacheKey = 'project|' . $this->project . '|commit|' . $hash; $cacheKey = 'project|' . $this->project . '|commit|' . $hash;
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey); $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
if ($cached) if ($cached)
$this->commitCache[$hash] = $cached; $this->commitCache[$hash] = $cached;
else else
$this->commitCache[$hash] = new GitPHP_Commit($this, $hash); $this->commitCache[$hash] = new GitPHP_Commit($this, $hash);
} }
   
return $this->commitCache[$hash]; return $this->commitCache[$hash];
   
} }
   
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
if (isset($this->heads['refs/heads/' . $hash])) if (isset($this->heads['refs/heads/' . $hash]))
return $this->heads['refs/heads/' . $hash]->GetCommit(); return $this->heads['refs/heads/' . $hash]->GetCommit();
   
if (isset($this->tags['refs/tags/' . $hash])) if (isset($this->tags['refs/tags/' . $hash]))
return $this->tags['refs/tags/' . $hash]->GetCommit(); return $this->tags['refs/tags/' . $hash]->GetCommit();
   
return null; return null;
} }
   
/** /**
* CompareProject * CompareProject
* *
* Compares two projects by project name * Compares two projects by project name
* *
* @access public * @access public
* @static * @static
* @param mixed $a first project * @param mixed $a first project
* @param mixed $b second project * @param mixed $b second project
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareProject($a, $b) public static function CompareProject($a, $b)
{ {
$catCmp = strcmp($a->GetCategory(), $b->GetCategory()); $catCmp = strcmp($a->GetCategory(), $b->GetCategory());
if ($catCmp !== 0) if ($catCmp !== 0)
return $catCmp; return $catCmp;
   
return strcmp($a->GetProject(), $b->GetProject()); return strcmp($a->GetProject(), $b->GetProject());
} }
   
/** /**
* CompareDescription * CompareDescription
* *
* Compares two projects by description * Compares two projects by description
* *
* @access public * @access public
* @static * @static
* @param mixed $a first project * @param mixed $a first project
* @param mixed $b second project * @param mixed $b second project
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareDescription($a, $b) public static function CompareDescription($a, $b)
{ {
$catCmp = strcmp($a->GetCategory(), $b->GetCategory()); $catCmp = strcmp($a->GetCategory(), $b->GetCategory());
if ($catCmp !== 0) if ($catCmp !== 0)
return $catCmp; return $catCmp;
   
return strcmp($a->GetDescription(), $b->GetDescription()); return strcmp($a->GetDescription(), $b->GetDescription());
} }
   
/** /**
* CompareOwner * CompareOwner
* *
* Compares two projects by owner * Compares two projects by owner
* *
* @access public * @access public
* @static * @static
* @param mixed $a first project * @param mixed $a first project
* @param mixed $b second project * @param mixed $b second project
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareOwner($a, $b) public static function CompareOwner($a, $b)
{ {
$catCmp = strcmp($a->GetCategory(), $b->GetCategory()); $catCmp = strcmp($a->GetCategory(), $b->GetCategory());
if ($catCmp !== 0) if ($catCmp !== 0)
return $catCmp; return $catCmp;
   
return strcmp($a->GetOwner(), $b->GetOwner()); return strcmp($a->GetOwner(), $b->GetOwner());
} }
   
/** /**
* CompareAge * CompareAge
* *
* Compares two projects by age * Compares two projects by age
* *
* @access public * @access public
* @static * @static
* @param mixed $a first project * @param mixed $a first project
* @param mixed $b second project * @param mixed $b second project
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareAge($a, $b) public static function CompareAge($a, $b)
{ {
$catCmp = strcmp($a->GetCategory(), $b->GetCategory()); $catCmp = strcmp($a->GetCategory(), $b->GetCategory());
if ($catCmp !== 0) if ($catCmp !== 0)
return $catCmp; return $catCmp;
   
if ($a->GetAge() === $b->GetAge()) if ($a->GetAge() === $b->GetAge())
return 0; return 0;
return ($a->GetAge() < $b->GetAge() ? -1 : 1); return ($a->GetAge() < $b->GetAge() ? -1 : 1);
} }
   
/** /**
* GetRefs * GetRefs
* *
* Gets the list of refs for the project * Gets the list of refs for the project
* *
* @access public * @access public
* @param string $type type of refs to get * @param string $type type of refs to get
* @return array array of refs * @return array array of refs
*/ */
public function GetRefs($type = '') public function GetRefs($type = '')
{ {
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
if ($type == 'tags') { if ($type == 'tags') {
return $this->tags; return $this->tags;
} else if ($type == 'heads') { } else if ($type == 'heads') {
return $this->heads; return $this->heads;
} }
   
return array_merge($this->heads, $this->tags); return array_merge($this->heads, $this->tags);
} }
   
/** /**
* ReadRefList * ReadRefList
* *
* Reads the list of refs for this project * Reads the list of refs for this project
* *
* @access protected * @access protected
*/ */
protected function ReadRefList() protected function ReadRefList()
{ {
$this->readRefs = true; $this->readRefs = true;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
$this->ReadRefListGit(); $this->ReadRefListGit();
} else { } else {
$this->ReadRefListRaw(); $this->ReadRefListRaw();
} }
} }
   
/** /**
* ReadRefListGit * ReadRefListGit
* *
* Reads the list of refs for this project using the git executable * Reads the list of refs for this project using the git executable
* *
* @access private * @access private
*/ */
private function ReadRefListGit() private function ReadRefListGit()
{ {
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = '--heads'; $args[] = '--heads';
$args[] = '--tags'; $args[] = '--tags';
$args[] = '--dereference'; $args[] = '--dereference';
$ret = $exe->Execute(GIT_SHOW_REF, $args); $ret = $exe->Execute(GIT_SHOW_REF, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
foreach ($lines as $line) { foreach ($lines as $line) {
if (preg_match('/^([0-9a-fA-F]{40}) refs\/(tags|heads)\/([^^]+)(\^{})?$/', $line, $regs)) { if (preg_match('/^([0-9a-fA-F]{40}) refs\/(tags|heads)\/([^^]+)(\^{})?$/', $line, $regs)) {
try { try {
$key = 'refs/' . $regs[2] . '/' . $regs[3]; $key = 'refs/' . $regs[2] . '/' . $regs[3];
if ($regs[2] == 'tags') { if ($regs[2] == 'tags') {
if ((!empty($regs[4])) && ($regs[4] == '^{}')) { if ((!empty($regs[4])) && ($regs[4] == '^{}')) {
$derefCommit = $this->GetCommit($regs[1]); $derefCommit = $this->GetCommit($regs[1]);
if ($derefCommit && isset($this->tags[$key])) { if ($derefCommit && isset($this->tags[$key])) {
$this->tags[$key]->SetCommit($derefCommit); $this->tags[$key]->SetCommit($derefCommit);
} }
} else if (!isset($this->tags[$key])) { } else if (!isset($this->tags[$key])) {
$this->tags[$key] = $this->LoadTag($regs[3], $regs[1]); $this->tags[$key] = $this->LoadTag($regs[3], $regs[1]);
} }
} else if ($regs[2] == 'heads') { } else if ($regs[2] == 'heads') {
$this->heads[$key] = new GitPHP_Head($this, $regs[3], $regs[1]); $this->heads[$key] = new GitPHP_Head($this, $regs[3], $regs[1]);
} }
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
} }
} }
   
/** /**
* ReadRefListRaw * ReadRefListRaw
* *
* Reads the list of refs for this project using the raw git files * Reads the list of refs for this project using the raw git files
* *
* @access private * @access private
*/ */
private function ReadRefListRaw() private function ReadRefListRaw()
{ {
$pathlen = strlen($this->GetPath()) + 1; $pathlen = strlen($this->GetPath()) + 1;
   
// read loose heads // read loose heads
$heads = $this->ListDir($this->GetPath() . '/refs/heads'); $heads = $this->ListDir($this->GetPath() . '/refs/heads');
for ($i = 0; $i < count($heads); $i++) { for ($i = 0; $i < count($heads); $i++) {
$key = trim(substr($heads[$i], $pathlen), "/\\"); $key = trim(substr($heads[$i], $pathlen), "/\\");
   
if (isset($this->heads[$key])) { if (isset($this->heads[$key])) {
continue; continue;
} }
   
$hash = trim(file_get_contents($heads[$i])); $hash = trim(file_get_contents($heads[$i]));
if (preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) { if (preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) {
$head = substr($key, strlen('refs/heads/')); $head = substr($key, strlen('refs/heads/'));
$this->heads[$key] = new GitPHP_Head($this, $head, $hash); $this->heads[$key] = new GitPHP_Head($this, $head, $hash);
} }
} }
   
// read loose tags // read loose tags
$tags = $this->ListDir($this->GetPath() . '/refs/tags'); $tags = $this->ListDir($this->GetPath() . '/refs/tags');
for ($i = 0; $i < count($tags); $i++) { for ($i = 0; $i < count($tags); $i++) {
$key = trim(substr($tags[$i], $pathlen), "/\\"); $key = trim(substr($tags[$i], $pathlen), "/\\");
   
if (isset($this->tags[$key])) { if (isset($this->tags[$key])) {
continue; continue;
} }
   
$hash = trim(file_get_contents($tags[$i])); $hash = trim(file_get_contents($tags[$i]));
if (preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) { if (preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) {
$tag = substr($key, strlen('refs/tags/')); $tag = substr($key, strlen('refs/tags/'));
$this->tags[$key] = $this->LoadTag($tag, $hash); $this->tags[$key] = $this->LoadTag($tag, $hash);
} }
} }
   
// check packed refs // check packed refs
if (file_exists($this->GetPath() . '/packed-refs')) { if (file_exists($this->GetPath() . '/packed-refs')) {
$packedRefs = explode("\n", file_get_contents($this->GetPath() . '/packed-refs')); $packedRefs = explode("\n", file_get_contents($this->GetPath() . '/packed-refs'));
   
$lastRef = null; $lastRef = null;
foreach ($packedRefs as $ref) { foreach ($packedRefs as $ref) {
   
if (preg_match('/^\^([0-9A-Fa-f]{40})$/', $ref, $regs)) { if (preg_match('/^\^([0-9A-Fa-f]{40})$/', $ref, $regs)) {
// dereference of previous ref // dereference of previous ref
if (($lastRef != null) && ($lastRef instanceof GitPHP_Tag)) { if (($lastRef != null) && ($lastRef instanceof GitPHP_Tag)) {
$derefCommit = $this->GetCommit($regs[1]); $derefCommit = $this->GetCommit($regs[1]);
if ($derefCommit) { if ($derefCommit) {
$lastRef->SetCommit($derefCommit); $lastRef->SetCommit($derefCommit);
} }
} }
} }
   
$lastRef = null; $lastRef = null;
   
if (preg_match('/^([0-9A-Fa-f]{40}) refs\/(tags|heads)\/(.+)$/', $ref, $regs)) { if (preg_match('/^([0-9A-Fa-f]{40}) refs\/(tags|heads)\/(.+)$/', $ref, $regs)) {
// standard tag/head // standard tag/head
$key = 'refs/' . $regs[2] . '/' . $regs[3]; $key = 'refs/' . $regs[2] . '/' . $regs[3];
if ($regs[2] == 'tags') { if ($regs[2] == 'tags') {
if (!isset($this->tags[$key])) { if (!isset($this->tags[$key])) {
$lastRef = $this->LoadTag($regs[3], $regs[1]); $lastRef = $this->LoadTag($regs[3], $regs[1]);
$this->tags[$key] = $lastRef; $this->tags[$key] = $lastRef;
} }
} else if ($regs[2] == 'heads') { } else if ($regs[2] == 'heads') {
if (!isset($this->heads[$key])) { if (!isset($this->heads[$key])) {
$this->heads[$key] = new GitPHP_Head($this, $regs[3], $regs[1]); $this->heads[$key] = new GitPHP_Head($this, $regs[3], $regs[1]);
} }
} }
} }
} }
} }
} }
   
/** /**
* ListDir * ListDir
* *
* Recurses into a directory and lists files inside * Recurses into a directory and lists files inside
* *
* @access private * @access private
* @param string $dir directory * @param string $dir directory
* @return array array of filenames * @return array array of filenames
*/ */
private function ListDir($dir) private function ListDir($dir)
{ {
$files = array(); $files = array();
if ($dh = opendir($dir)) { if ($dh = opendir($dir)) {
while (($file = readdir($dh)) !== false) { while (($file = readdir($dh)) !== false) {
if (($file == '.') || ($file == '..')) { if (($file == '.') || ($file == '..')) {
continue; continue;
} }
$fullFile = $dir . '/' . $file; $fullFile = $dir . '/' . $file;
if (is_dir($fullFile)) { if (is_dir($fullFile)) {
$subFiles = $this->ListDir($fullFile); $subFiles = $this->ListDir($fullFile);
if (count($subFiles) > 0) { if (count($subFiles) > 0) {
$files = array_merge($files, $subFiles); $files = array_merge($files, $subFiles);
} }
} else { } else {
$files[] = $fullFile; $files[] = $fullFile;
} }
} }
} }
return $files; return $files;
} }
   
/** /**
* GetTags * GetTags
* *
* Gets list of tags for this project by age descending * Gets list of tags for this project by age descending
* *
* @access public * @access public
* @param integer $count number of tags to load * @param integer $count number of tags to load
* @return array array of tags * @return array array of tags
*/ */
public function GetTags($count = 0) public function GetTags($count = 0)
{ {
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
return $this->GetTagsGit($count); return $this->GetTagsGit($count);
} else { } else {
return $this->GetTagsRaw($count); return $this->GetTagsRaw($count);
} }
} }
   
/** /**
* GetTagsGit * GetTagsGit
* *
* Gets list of tags for this project by age descending using git executable * Gets list of tags for this project by age descending using git executable
* *
* @access private * @access private
* @param integer $count number of tags to load * @param integer $count number of tags to load
* @return array array of tags * @return array array of tags
*/ */
private function GetTagsGit($count = 0) private function GetTagsGit($count = 0)
{ {
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = '--sort=-creatordate'; $args[] = '--sort=-creatordate';
$args[] = '--format="%(refname)"'; $args[] = '--format="%(refname)"';
if ($count > 0) { if ($count > 0) {
$args[] = '--count=' . $count; $args[] = '--count=' . $count;
} }
$args[] = '--'; $args[] = '--';
$args[] = 'refs/tags'; $args[] = 'refs/tags';
$ret = $exe->Execute(GIT_FOR_EACH_REF, $args); $ret = $exe->Execute(GIT_FOR_EACH_REF, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
$tags = array(); $tags = array();
   
foreach ($lines as $ref) { foreach ($lines as $ref) {
if (isset($this->tags[$ref])) { if (isset($this->tags[$ref])) {
$tags[] = $this->tags[$ref]; $tags[] = $this->tags[$ref];
} }
} }
   
return $tags; return $tags;
} }
   
/** /**
* GetTagsRaw * GetTagsRaw
* *
* Gets list of tags for this project by age descending using raw git objects * Gets list of tags for this project by age descending using raw git objects
* *
* @access private * @access private
* @param integer $count number of tags to load * @param integer $count number of tags to load
* @return array array of tags * @return array array of tags
*/ */
private function GetTagsRaw($count = 0) private function GetTagsRaw($count = 0)
{ {
$tags = $this->tags; $tags = $this->tags;
usort($tags, array('GitPHP_Tag', 'CompareCreationEpoch')); usort($tags, array('GitPHP_Tag', 'CompareCreationEpoch'));
   
if (($count > 0) && (count($tags) > $count)) { if (($count > 0) && (count($tags) > $count)) {
$tags = array_slice($tags, 0, $count); $tags = array_slice($tags, 0, $count);
} }
   
return $tags; return $tags;
} }
   
/** /**
* GetTag * GetTag
* *
* Gets a single tag * Gets a single tag
* *
* @access public * @access public
* @param string $tag tag to find * @param string $tag tag to find
* @return mixed tag object * @return mixed tag object
*/ */
public function GetTag($tag) public function GetTag($tag)
{ {
if (empty($tag)) if (empty($tag))
return null; return null;
   
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
$key = 'refs/tags/' . $tag; $key = 'refs/tags/' . $tag;
   
if (!isset($this->tags[$key])) { if (!isset($this->tags[$key])) {
$this->tags[$key] = $this->LoadTag($tag); $this->tags[$key] = $this->LoadTag($tag);
} }
   
return $this->tags[$key]; return $this->tags[$key];
} }
   
/** /**
* LoadTag * LoadTag
* *
* Attempts to load a cached tag, or creates a new object * Attempts to load a cached tag, or creates a new object
* *
* @access private * @access private
* @param string $tag tag to find * @param string $tag tag to find
* @return mixed tag object * @return mixed tag object
*/ */
private function LoadTag($tag, $hash = '') private function LoadTag($tag, $hash = '')
{ {
if (empty($tag)) if (empty($tag))
return; return;
   
$cacheKey = 'project|' . $this->project . '|tag|' . $tag; $cacheKey = 'project|' . $this->project . '|tag|' . $tag;
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey); $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
if ($cached) { if ($cached) {
return $cached; return $cached;
} else { } else {
return new GitPHP_Tag($this, $tag, $hash); return new GitPHP_Tag($this, $tag, $hash);
} }
} }
   
/** /**
* GetHeads * GetHeads
* *
* Gets list of heads for this project by age descending * Gets list of heads for this project by age descending
* *
* @access public * @access public
* @param integer $count number of tags to load * @param integer $count number of tags to load
* @return array array of heads * @return array array of heads
*/ */
public function GetHeads($count = 0) public function GetHeads($count = 0)
{ {
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
return $this->GetHeadsGit($count); return $this->GetHeadsGit($count);
} else { } else {
return $this->GetHeadsRaw($count); return $this->GetHeadsRaw($count);
} }
} }
   
/** /**
* GetHeadsGit * GetHeadsGit
* *
* Gets the list of sorted heads using the git executable * Gets the list of sorted heads using the git executable
* *
* @access private * @access private
* @param integer $count number of tags to load * @param integer $count number of tags to load
* @return array array of heads * @return array array of heads
*/ */
private function GetHeadsGit($count = 0) private function GetHeadsGit($count = 0)
{ {
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
$args = array(); $args = array();
$args[] = '--sort=-committerdate'; $args[] = '--sort=-committerdate';
$args[] = '--format="%(refname)"'; $args[] = '--format="%(refname)"';
if ($count > 0) { if ($count > 0) {
$args[] = '--count=' . $count; $args[] = '--count=' . $count;
} }
$args[] = '--'; $args[] = '--';
$args[] = 'refs/heads'; $args[] = 'refs/heads';
$ret = $exe->Execute(GIT_FOR_EACH_REF, $args); $ret = $exe->Execute(GIT_FOR_EACH_REF, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
$heads = array(); $heads = array();
   
foreach ($lines as $ref) { foreach ($lines as $ref) {
if (isset($this->heads[$ref])) { if (isset($this->heads[$ref])) {
$heads[] = $this->heads[$ref]; $heads[] = $this->heads[$ref];
} }
} }
   
return $heads; return $heads;
} }
   
/** /**
* GetHeadsRaw * GetHeadsRaw
* *
* Gets the list of sorted heads using raw git objects * Gets the list of sorted heads using raw git objects
* *
* @access private * @access private
* @param integer $count number of tags to load * @param integer $count number of tags to load
* @return array array of heads * @return array array of heads
*/ */
private function GetHeadsRaw($count = 0) private function GetHeadsRaw($count = 0)
{ {
$heads = $this->heads; $heads = $this->heads;
usort($heads, array('GitPHP_Head', 'CompareAge')); usort($heads, array('GitPHP_Head', 'CompareAge'));
   
if (($count > 0) && (count($heads) > $count)) { if (($count > 0) && (count($heads) > $count)) {
$heads = array_slice($heads, 0, $count); $heads = array_slice($heads, 0, $count);
} }
return $heads; return $heads;
} }
   
/** /**
* GetHead * GetHead
* *
* Gets a single head * Gets a single head
* *
* @access public * @access public
* @param string $head head to find * @param string $head head to find
* @return mixed head object * @return mixed head object
*/ */
public function GetHead($head) public function GetHead($head)
{ {
if (empty($head)) if (empty($head))
return null; return null;
   
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
$key = 'refs/heads/' . $head; $key = 'refs/heads/' . $head;
   
if (!isset($this->heads[$key])) { if (!isset($this->heads[$key])) {
$this->heads[$key] = new GitPHP_Head($this, $head); $this->heads[$key] = new GitPHP_Head($this, $head);
} }
   
return $this->heads[$key]; return $this->heads[$key];
} }
   
/** /**
* GetLogHash * GetLogHash
* *
* Gets log entries as an array of hashes * Gets log entries as an array of hashes
* *
* @access private * @access private
* @param string $hash hash to start the log at * @param string $hash hash to start the log at
* @param integer $count number of entries to get * @param integer $count number of entries to get
* @param integer $skip number of entries to skip * @param integer $skip number of entries to skip
* @return array array of hashes * @return array array of hashes
*/ */
private function GetLogHash($hash, $count = 50, $skip = 0) private function GetLogHash($hash, $count = 50, $skip = 0)
{ {
return $this->RevList($hash, $count, $skip); return $this->RevList($hash, $count, $skip);
} }
   
/** /**
* GetLog * GetLog
* *
* Gets log entries as an array of commit objects * Gets log entries as an array of commit objects
* *
* @access public * @access public
* @param string $hash hash to start the log at * @param string $hash hash to start the log at
* @param integer $count number of entries to get * @param integer $count number of entries to get
* @param integer $skip number of entries to skip * @param integer $skip number of entries to skip
* @return array array of commit objects * @return array array of commit objects
*/ */
public function GetLog($hash, $count = 50, $skip = 0) public function GetLog($hash, $count = 50, $skip = 0)
{ {
if (GitPHP_Config::GetInstance()->GetValue('compat', false) || ($skip > GitPHP_Config::GetInstance()->GetValue('largeskip', 200)) ) { if (GitPHP_Config::GetInstance()->GetValue('compat', false) || ($skip > GitPHP_Config::GetInstance()->GetValue('largeskip', 200)) ) {
return $this->GetLogGit($hash, $count, $skip); return $this->GetLogGit($hash, $count, $skip);
} else { } else {
return $this->GetLogRaw($hash, $count, $skip); return $this->GetLogRaw($hash, $count, $skip);
} }
} }
   
/** /**
* GetLogGit * GetLogGit
* *
* Gets log entries using git exe * Gets log entries using git exe
* *
* @access private * @access private
* @param string $hash hash to start the log at * @param string $hash hash to start the log at
* @param integer $count number of entries to get * @param integer $count number of entries to get
* @param integer $skip number of entries to skip * @param integer $skip number of entries to skip
* @return array array of commit objects * @return array array of commit objects
*/ */
private function GetLogGit($hash, $count = 50, $skip = 0) private function GetLogGit($hash, $count = 50, $skip = 0)
{ {
$log = $this->GetLogHash($hash, $count, $skip); $log = $this->GetLogHash($hash, $count, $skip);
$len = count($log); $len = count($log);
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$log[$i] = $this->GetCommit($log[$i]); $log[$i] = $this->GetCommit($log[$i]);
} }
return $log; return $log;
} }
   
/** /**
* GetLogRaw * GetLogRaw
* *
* Gets log entries using raw git objects * Gets log entries using raw git objects
* Based on history walking code from glip * Based on history walking code from glip
* *
* @access private * @access private
*/ */
private function GetLogRaw($hash, $count = 50, $skip = 0) private function GetLogRaw($hash, $count = 50, $skip = 0)
{ {
$total = $count + $skip; $total = $count + $skip;
   
$inc = array(); $inc = array();
$num = 0; $num = 0;
$queue = array($this->GetCommit($hash)); $queue = array($this->GetCommit($hash));
while (($commit = array_shift($queue)) !== null) { while (($commit = array_shift($queue)) !== null) {
$parents = $commit->GetParents(); $parents = $commit->GetParents();
foreach ($parents as $parent) { foreach ($parents as $parent) {
if (!isset($inc[$parent->GetHash()])) { if (!isset($inc[$parent->GetHash()])) {
$inc[$parent->GetHash()] = 1; $inc[$parent->GetHash()] = 1;
$queue[] = $parent; $queue[] = $parent;
$num++; $num++;
} else { } else {
$inc[$parent->GetHash()]++; $inc[$parent->GetHash()]++;
} }
} }
if ($num >= $total) if ($num >= $total)
break; break;
} }
   
$queue = array($this->GetCommit($hash)); $queue = array($this->GetCommit($hash));
$log = array(); $log = array();
$num = 0; $num = 0;
while (($commit = array_pop($queue)) !== null) { while (($commit = array_pop($queue)) !== null) {
array_push($log, $commit); array_push($log, $commit);
$num++; $num++;
if ($num == $total) { if ($num == $total) {
break; break;
} }
$parents = $commit->GetParents(); $parents = $commit->GetParents();
foreach ($parents as $parent) { foreach ($parents as $parent) {
if (isset($inc[$parent->GetHash()])) { if (isset($inc[$parent->GetHash()])) {
if (--$inc[$parent->GetHash()] == 0) { if (--$inc[$parent->GetHash()] == 0) {
$queue[] = $parent; $queue[] = $parent;
} }
} }
} }
} }
   
if ($skip > 0) { if ($skip > 0) {
$log = array_slice($log, $skip, $count); $log = array_slice($log, $skip, $count);
} }
usort($log, array('GitPHP_Commit', 'CompareAge')); usort($log, array('GitPHP_Commit', 'CompareAge'));
return $log; return $log;
} }
   
/** /**
* GetBlob * GetBlob
* *
* Gets a blob from this project * Gets a blob from this project
* *
* @access public * @access public
* @param string $hash blob hash * @param string $hash blob hash
*/ */
public function GetBlob($hash) public function GetBlob($hash)
{ {
if (empty($hash)) if (empty($hash))
return null; return null;
   
$cacheKey = 'project|' . $this->project . '|blob|' . $hash; $cacheKey = 'project|' . $this->project . '|blob|' . $hash;
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey); $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
if ($cached) if ($cached)
return $cached; return $cached;
   
return new GitPHP_Blob($this, $hash); return new GitPHP_Blob($this, $hash);
} }
   
/** /**
* GetTree * GetTree
* *
* Gets a tree from this project * Gets a tree from this project
* *
* @access public * @access public
* @param string $hash tree hash * @param string $hash tree hash
*/ */
public function GetTree($hash) public function GetTree($hash)
{ {
if (empty($hash)) if (empty($hash))
return null; return null;
   
$cacheKey = 'project|' . $this->project . '|tree|' . $hash; $cacheKey = 'project|' . $this->project . '|tree|' . $hash;
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey); $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
if ($cached) if ($cached)
return $cached; return $cached;
   
return new GitPHP_Tree($this, $hash); return new GitPHP_Tree($this, $hash);
} }
   
/** /**
* SearchCommit * SearchCommit
* *
* Gets a list of commits with commit messages matching the given pattern * Gets a list of commits with commit messages matching the given pattern
* *
* @access public * @access public
* @param string $pattern search pattern * @param string $pattern search pattern
* @param string $hash hash to start searching from * @param string $hash hash to start searching from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @return array array of matching commits * @return array array of matching commits
*/ */
public function SearchCommit($pattern, $hash = 'HEAD', $count = 50, $skip = 0) public function SearchCommit($pattern, $hash = 'HEAD', $count = 50, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$args = array(); $args = array();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
if ($exe->CanIgnoreRegexpCase()) if ($exe->CanIgnoreRegexpCase())
$args[] = '--regexp-ignore-case'; $args[] = '--regexp-ignore-case';
unset($exe); unset($exe);
   
$args[] = '--grep=\'' . $pattern . '\''; $args[] = '--grep=\'' . $pattern . '\'';
   
$ret = $this->RevList($hash, $count, $skip, $args); $ret = $this->RevList($hash, $count, $skip, $args);
$len = count($ret); $len = count($ret);
   
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$ret[$i] = $this->GetCommit($ret[$i]); $ret[$i] = $this->GetCommit($ret[$i]);
} }
return $ret; return $ret;
} }
   
/** /**
* SearchAuthor * SearchAuthor
* *
* Gets a list of commits with authors matching the given pattern * Gets a list of commits with authors matching the given pattern
* *
* @access public * @access public
* @param string $pattern search pattern * @param string $pattern search pattern
* @param string $hash hash to start searching from * @param string $hash hash to start searching from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @return array array of matching commits * @return array array of matching commits
*/ */
public function SearchAuthor($pattern, $hash = 'HEAD', $count = 50, $skip = 0) public function SearchAuthor($pattern, $hash = 'HEAD', $count = 50, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$args = array(); $args = array();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
if ($exe->CanIgnoreRegexpCase()) if ($exe->CanIgnoreRegexpCase())
$args[] = '--regexp-ignore-case'; $args[] = '--regexp-ignore-case';
unset($exe); unset($exe);
   
$args[] = '--author=\'' . $pattern . '\''; $args[] = '--author=\'' . $pattern . '\'';
   
$ret = $this->RevList($hash, $count, $skip, $args); $ret = $this->RevList($hash, $count, $skip, $args);
$len = count($ret); $len = count($ret);
   
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$ret[$i] = $this->GetCommit($ret[$i]); $ret[$i] = $this->GetCommit($ret[$i]);
} }
return $ret; return $ret;
} }
   
/** /**
* SearchCommitter * SearchCommitter
* *
* Gets a list of commits with committers matching the given pattern * Gets a list of commits with committers matching the given pattern
* *
* @access public * @access public
* @param string $pattern search pattern * @param string $pattern search pattern
* @param string $hash hash to start searching from * @param string $hash hash to start searching from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @return array array of matching commits * @return array array of matching commits
*/ */
public function SearchCommitter($pattern, $hash = 'HEAD', $count = 50, $skip = 0) public function SearchCommitter($pattern, $hash = 'HEAD', $count = 50, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$args = array(); $args = array();
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
if ($exe->CanIgnoreRegexpCase()) if ($exe->CanIgnoreRegexpCase())
$args[] = '--regexp-ignore-case'; $args[] = '--regexp-ignore-case';
unset($exe); unset($exe);
   
$args[] = '--committer=\'' . $pattern . '\''; $args[] = '--committer=\'' . $pattern . '\'';
   
$ret = $this->RevList($hash, $count, $skip, $args); $ret = $this->RevList($hash, $count, $skip, $args);
$len = count($ret); $len = count($ret);
   
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$ret[$i] = $this->GetCommit($ret[$i]); $ret[$i] = $this->GetCommit($ret[$i]);
} }
return $ret; return $ret;
} }
   
/** /**
* RevList * RevList
* *
* Common code for using rev-list command * Common code for using rev-list command
* *
* @access private * @access private
* @param string $hash hash to list from * @param string $hash hash to list from
* @param integer $count number of results to get * @param integer $count number of results to get
* @param integer $skip number of results to skip * @param integer $skip number of results to skip
* @param array $args args to give to rev-list * @param array $args args to give to rev-list
* @return array array of hashes * @return array array of hashes
*/ */
private function RevList($hash, $count = 50, $skip = 0, $args = array()) private function RevList($hash, $count = 50, $skip = 0, $args = array())
{ {
if ($count < 1) if ($count < 1)
return; return;
   
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
   
$canSkip = true; $canSkip = true;
if ($skip > 0) if ($skip > 0)
$canSkip = $exe->CanSkip(); $canSkip = $exe->CanSkip();
   
if ($canSkip) { if ($canSkip) {
$args[] = '--max-count=' . $count; $args[] = '--max-count=' . $count;
if ($skip > 0) { if ($skip > 0) {
$args[] = '--skip=' . $skip; $args[] = '--skip=' . $skip;
} }
} else { } else {
$args[] = '--max-count=' . ($count + $skip); $args[] = '--max-count=' . ($count + $skip);
} }
   
$args[] = $hash; $args[] = $hash;
   
$revlist = explode("\n", $exe->Execute(GIT_REV_LIST, $args)); $revlist = explode("\n", $exe->Execute(GIT_REV_LIST, $args));
   
if (!$revlist[count($revlist)-1]) { if (!$revlist[count($revlist)-1]) {
/* the last newline creates a null entry */ /* the last newline creates a null entry */
array_splice($revlist, -1, 1); array_splice($revlist, -1, 1);
} }
   
if (($skip > 0) && (!$exe->CanSkip())) { if (($skip > 0) && (!$exe->CanSkip())) {
return array_slice($revlist, $skip, $count); return array_slice($revlist, $skip, $count);
} }
   
return $revlist; return $revlist;
} }
   
/** /**
* GetEpoch * GetEpoch
* *
* Gets this project's epoch * Gets this project's epoch
* (time of last change) * (time of last change)
* *
* @access public * @access public
* @return integer timestamp * @return integer timestamp
*/ */
public function GetEpoch() public function GetEpoch()
{ {
if (!$this->epochRead) if (!$this->epochRead)
$this->ReadEpoch(); $this->ReadEpoch();
   
return $this->epoch; return $this->epoch;
} }
   
/** /**
* GetAge * GetAge
* *
* Gets this project's age * Gets this project's age
* (time since most recent change) * (time since most recent change)
* *
* @access public * @access public
* @return integer age * @return integer age
*/ */
public function GetAge() public function GetAge()
{ {
if (!$this->epochRead) if (!$this->epochRead)
$this->ReadEpoch(); $this->ReadEpoch();
   
return time() - $this->epoch; return time() - $this->epoch;
} }
   
/** /**
* ReadEpoch * ReadEpoch
* *
* Reads this project's epoch * Reads this project's epoch
* (timestamp of most recent change) * (timestamp of most recent change)
* *
* @access private * @access private
*/ */
private function ReadEpoch() private function ReadEpoch()
{ {
$this->epochRead = true; $this->epochRead = true;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
$this->ReadEpochGit(); $this->ReadEpochGit();
} else { } else {
$this->ReadEpochRaw(); $this->ReadEpochRaw();
} }
} }
   
/** /**
* ReadEpochGit * ReadEpochGit
* *
* Reads this project's epoch using git executable * Reads this project's epoch using git executable
* *
* @access private * @access private
*/ */
private function ReadEpochGit() private function ReadEpochGit()
{ {
$exe = new GitPHP_GitExe($this); $exe = new GitPHP_GitExe($this);
   
$args = array(); $args = array();
$args[] = '--format="%(committer)"'; $args[] = '--format="%(committer)"';
$args[] = '--sort=-committerdate'; $args[] = '--sort=-committerdate';
$args[] = '--count=1'; $args[] = '--count=1';
$args[] = 'refs/heads'; $args[] = 'refs/heads';
   
$epochstr = trim($exe->Execute(GIT_FOR_EACH_REF, $args)); $epochstr = trim($exe->Execute(GIT_FOR_EACH_REF, $args));
   
if (preg_match('/ (\d+) [-+][01]\d\d\d$/', $epochstr, $regs)) { if (preg_match('/ (\d+) [-+][01]\d\d\d$/', $epochstr, $regs)) {
$this->epoch = $regs[1]; $this->epoch = $regs[1];
} }
   
unset($exe); unset($exe);
} }
   
/** /**
* ReadEpochRaw * ReadEpochRaw
* *
* Reads this project's epoch using raw objects * Reads this project's epoch using raw objects
* *
* @access private * @access private
*/ */
private function ReadEpochRaw() private function ReadEpochRaw()
{ {
if (!$this->readRefs) if (!$this->readRefs)
$this->ReadRefList(); $this->ReadRefList();
   
$epoch = 0; $epoch = 0;
foreach ($this->heads as $head) { foreach ($this->heads as $head) {
$commit = $head->GetCommit(); $commit = $head->GetCommit();
if ($commit) { if ($commit) {
if ($commit->GetCommitterEpoch() > $epoch) { if ($commit->GetCommitterEpoch() > $epoch) {
$epoch = $commit->GetCommitterEpoch(); $epoch = $commit->GetCommitterEpoch();
} }
} }
} }
if ($epoch > 0) { if ($epoch > 0) {
$this->epoch = $epoch; $this->epoch = $epoch;
} }
} }
   
/** /**
* GetObject * GetObject
* *
* Gets the raw content of an object * Gets the raw content of an object
* *
* @access public * @access public
* @param string $hash object hash * @param string $hash object hash
* @return string object data * @return string object data
*/ */
public function GetObject($hash, &$type = 0) public function GetObject($hash, &$type = 0)
{ {
if (!preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) { if (!preg_match('/^[0-9A-Fa-f]{40}$/', $hash)) {
return false; return false;
} }
   
// first check if it's unpacked // first check if it's unpacked
$path = $this->GetPath() . '/objects/' . substr($hash, 0, 2) . '/' . substr($hash, 2); $path = $this->GetPath() . '/objects/' . substr($hash, 0, 2) . '/' . substr($hash, 2);
if (file_exists($path)) { if (file_exists($path)) {
list($header, $data) = explode("\0", gzuncompress(file_get_contents($path)), 2); list($header, $data) = explode("\0", gzuncompress(file_get_contents($path)), 2);
sscanf($header, "%s %d", $typestr, $size); sscanf($header, "%s %d", $typestr, $size);
switch ($typestr) { switch ($typestr) {
case 'commit': case 'commit':
$type = GitPHP_Pack::OBJ_COMMIT; $type = GitPHP_Pack::OBJ_COMMIT;
break; break;
case 'tree': case 'tree':
$type = GitPHP_Pack::OBJ_TREE; $type = GitPHP_Pack::OBJ_TREE;
break; break;
case 'blob': case 'blob':
$type = GitPHP_Pack::OBJ_BLOB; $type = GitPHP_Pack::OBJ_BLOB;
break; break;
case 'tag': case 'tag':
$type = GitPHP_Pack::OBJ_TAG; $type = GitPHP_Pack::OBJ_TAG;
break; break;
} }
return $data; return $data;
} }
   
if (!$this->packsRead) { if (!$this->packsRead) {
$this->ReadPacks(); $this->ReadPacks();
} }
   
// then try packs // then try packs
foreach ($this->packs as $pack) { foreach ($this->packs as $pack) {
$data = $pack->GetObject($hash, $type); $data = $pack->GetObject($hash, $type);
if ($data !== false) { if ($data !== false) {
return $data; return $data;
} }
} }
   
return false; return false;
} }
   
/** /**
* ReadPacks * ReadPacks
* *
* Read the list of packs in the repository * Read the list of packs in the repository
* *
* @access private * @access private
*/ */
private function ReadPacks() private function ReadPacks()
{ {
$dh = opendir($this->GetPath() . '/objects/pack'); $dh = opendir($this->GetPath() . '/objects/pack');
if ($dh !== false) { if ($dh !== false) {
while (($file = readdir($dh)) !== false) { while (($file = readdir($dh)) !== false) {
if (preg_match('/^pack-([0-9A-Fa-f]{40})\.idx$/', $file, $regs)) { if (preg_match('/^pack-([0-9A-Fa-f]{40})\.idx$/', $file, $regs)) {
$this->packs[] = new GitPHP_Pack($this, $regs[1]); $this->packs[] = new GitPHP_Pack($this, $regs[1]);
} }
} }
} }
$this->packsRead = true; $this->packsRead = true;
} }
   
} }
   
<?php <?php
/** /**
* GitPHP ProjectList * GitPHP ProjectList
* *
* Project list singleton instance and factory * Project list singleton instance and factory
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2010 Christopher Han * @copyright Copyright (c) 2010 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
   
require_once(GITPHP_GITOBJECTDIR . 'ProjectListDirectory.class.php'); require_once(GITPHP_GITOBJECTDIR . 'ProjectListDirectory.class.php');
require_once(GITPHP_GITOBJECTDIR . 'ProjectListFile.class.php'); require_once(GITPHP_GITOBJECTDIR . 'ProjectListFile.class.php');
require_once(GITPHP_GITOBJECTDIR . 'ProjectListArray.class.php'); require_once(GITPHP_GITOBJECTDIR . 'ProjectListArray.class.php');
require_once(GITPHP_GITOBJECTDIR . 'ProjectListArrayLegacy.class.php'); require_once(GITPHP_GITOBJECTDIR . 'ProjectListArrayLegacy.class.php');
  require_once(GITPHP_GITOBJECTDIR . 'ProjectListScmManager.class.php');
   
/** /**
* ProjectList class * ProjectList class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_ProjectList class GitPHP_ProjectList
{ {
   
/** /**
* instance * instance
* *
* Stores the singleton instance of the projectlist * Stores the singleton instance of the projectlist
* *
* @access protected * @access protected
* @static * @static
*/ */
protected static $instance = null; protected static $instance = null;
   
/** /**
* GetInstance * GetInstance
* *
* Returns the singleton instance * Returns the singleton instance
* *
* @access public * @access public
* @static * @static
* @return mixed instance of projectlist * @return mixed instance of projectlist
* @throws Exception if projectlist has not been instantiated yet * @throws Exception if projectlist has not been instantiated yet
*/ */
public static function GetInstance() public static function GetInstance()
{ {
return self::$instance; return self::$instance;
} }
   
/** /**
* Instantiate * Instantiate
* *
* Instantiates the singleton instance * Instantiates the singleton instance
* *
* @access private * @access private
* @static * @static
* @param string $file config file with git projects * @param string $file config file with git projects
* @param boolean $legacy true if this is the legacy project config * @param boolean $legacy true if this is the legacy project config
* @throws Exception if there was an error reading the file * @throws Exception if there was an error reading the file
*/ */
public static function Instantiate($file = null, $legacy = false) public static function Instantiate($file = null, $legacy = false)
{ {
if (self::$instance) if (self::$instance)
return; return;
   
if (!empty($file) && is_file($file) && include($file)) { if (!empty($file) && is_file($file) && include($file)) {
if (isset($git_projects)) { if (isset($git_projects)) {
if (is_string($git_projects)) { if (is_string($git_projects)) {
self::$instance = new GitPHP_ProjectListFile($git_projects); if (function_exists('simplexml_load_file') && GitPHP_ProjectListScmManager::IsSCMManager($git_projects)) {
  self::$instance = new GitPHP_ProjectListScmManager($git_projects);
  } else {
  self::$instance = new GitPHP_ProjectListFile($git_projects);
  }
} else if (is_array($git_projects)) { } else if (is_array($git_projects)) {
if ($legacy) { if ($legacy) {
self::$instance = new GitPHP_ProjectListArrayLegacy($git_projects); self::$instance = new GitPHP_ProjectListArrayLegacy($git_projects);
} else { } else {
self::$instance = new GitPHP_ProjectListArray($git_projects); self::$instance = new GitPHP_ProjectListArray($git_projects);
} }
} }
} }
} }
   
if (!self::$instance) if (!self::$instance)
self::$instance = new GitPHP_ProjectListDirectory(GitPHP_Config::GetInstance()->GetValue('projectroot')); self::$instance = new GitPHP_ProjectListDirectory(GitPHP_Config::GetInstance()->GetValue('projectroot'));
   
if (isset($git_projects_settings) && !$legacy) if (isset($git_projects_settings) && !$legacy)
self::$instance->ApplySettings($git_projects_settings); self::$instance->ApplySettings($git_projects_settings);
} }
   
} }
   
   
  <?php
  /**
  * GitPHP ProjectListScmManager
  *
  * Lists all projects in an scm-manager config file
  *
  * @author Christopher Han <xiphux@gmail.com>
  * @copyright Copyright (c) 2011 Christopher Han
  * @package GitPHP
  * @subpackage Git
  */
 
  require_once(GITPHP_INCLUDEDIR . 'Config.class.php');
  require_once(GITPHP_GITOBJECTDIR . 'ProjectListBase.class.php');
  require_once(GITPHP_GITOBJECTDIR . 'Project.class.php');
 
  /**
  * ProjectListScmManager class
  *
  * @package GitPHP
  * @subpackage Git
  */
  class GitPHP_ProjectListScmManager extends GitPHP_ProjectListBase
  {
  /**
  * __construct
  *
  * constructor
  *
  * @param string $projectFile file to read
  * @throws Exception if parameter is not a readable file
  * @access public
  */
  public function __construct($projectFile)
  {
  if (!(is_string($projectFile) && is_file($projectFile))) {
  throw new Exception(sprintf(__('%1$s is not a file'), $projectFile));
  }
 
  $this->projectConfig = $projectFile;
 
  parent::__construct();
  }
 
  /**
  * PopulateProjects
  *
  * Populates the internal list of projects
  *
  * @access protected
  * @throws Exception if file cannot be read
  */
  protected function PopulateProjects()
  {
  $projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot'));
 
  $use_errors = libxml_use_internal_errors(true);
 
  $xml = simplexml_load_file($this->projectConfig);
 
  libxml_clear_errors();
  libxml_use_internal_errors($use_errors);
 
  if (!$xml) {
  throw new Exception(sprintf('Could not load SCM manager config %1$s', $this->projectConfig));
  }
 
  foreach ($xml->repositories->repository as $repository) {
 
  if ($repository->type != 'git')
  continue;
  if ($repository->public != 'true')
  continue;
 
  $projName = trim($repository->name);
  if (empty($projName))
  continue;
 
  if (is_file($projectRoot . $projName . '/HEAD')) {
  try {
  $projObj = new GitPHP_Project($projectRoot, $projName);
  $projOwner = trim($repository->contact);
  if (!empty($projOwner)) {
  $projObj->SetOwner($projOwner);
  }
  $projDesc = trim($repository->description);
  if (!empty($projDesc)) {
  $projObj->SetDescription($projDesc);
  }
  $this->projects[$projName] = $projObj;
  } catch (Exception $e) {
  }
  }
  }
  }
 
  /**
  * IsSCMManager
  *
  * Tests if this file is an SCM manager config file
  *
  * @access protected
  * @returns true if file is an SCM manager config
  */
  public static function IsSCMManager($file)
  {
  if (empty($file))
  return false;
 
  if (!(is_string($file) && is_file($file)))
  return false;
 
  $use_errors = libxml_use_internal_errors(true);
 
  $xml = simplexml_load_file($file);
 
  libxml_clear_errors();
  libxml_use_internal_errors($use_errors);
 
  if (!$xml)
  return false;
 
  if ($xml->getName() !== 'repository-db')
  return false;
 
  return true;
  }
 
  }
 
# SOME DESCRIPTIVE TITLE. # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Christopher Han # Copyright (C) YEAR Christopher Han
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: GitPHP 0.2.3\n" "Project-Id-Version: GitPHP 0.2.4\n"
"Report-Msgid-Bugs-To: xiphux@gmail.com\n" "Report-Msgid-Bugs-To: xiphux@gmail.com\n"
"POT-Creation-Date: 2011-06-18 21:03-0500\n" "POT-Creation-Date: 2011-07-22 23:42-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
   
# Used as link to and title for page displaying a blob, which is what git calls a single file # Used as link to and title for page displaying a blob, which is what git calls a single file
#: templates/blobdiff.tpl #: templates/blobdiff.tpl
#: templates/commit.tpl #: templates/commit.tpl
#: templates/treelist.tpl #: templates/treelist.tpl
#: templates/searchfiles.tpl #: templates/searchfiles.tpl
#: templates/history.tpl #: templates/history.tpl
#: include/controller/Controller_Blob.class.php:78 #: include/controller/Controller_Blob.class.php:78
msgid "blob" msgid "blob"
msgstr "" msgstr ""
   
# Used as link to and title for the file history, which displays all commits that have modified a certain file # Used as link to and title for the file history, which displays all commits that have modified a certain file
#: templates/commit.tpl #: templates/commit.tpl
#: templates/treelist.tpl #: templates/treelist.tpl
#: templates/searchfiles.tpl #: templates/searchfiles.tpl
#: templates/blob.tpl #: templates/blob.tpl
#: include/controller/Controller_History.class.php:76 #: include/controller/Controller_History.class.php:76
msgid "history" msgid "history"
msgstr "" msgstr ""
   
# Used as title for and link to a list of files in a directory, which git calls a 'tree' # Used as title for and link to a list of files in a directory, which git calls a 'tree'
#: templates/search.tpl #: templates/search.tpl
#: templates/commit.tpl #: templates/commit.tpl
#: templates/headlist.tpl #: templates/headlist.tpl
#: templates/nav.tpl #: templates/nav.tpl
#: templates/treelist.tpl #: templates/treelist.tpl
#: templates/searchfiles.tpl #: templates/searchfiles.tpl
#: templates/log.tpl #: templates/log.tpl
#: templates/projectlist.tpl #: templates/projectlist.tpl
#: templates/shortloglist.tpl #: templates/shortloglist.tpl
#: include/controller/Controller_Tree.class.php:79 #: include/controller/Controller_Tree.class.php:79
msgid "tree" msgid "tree"
msgstr "" msgstr ""
   
# Used as link to download a copy of the files in a given commit # Used as link to download a copy of the files in a given commit
#: templates/search.tpl #: templates/search.tpl
#: templates/commit.tpl #: templates/commit.tpl
#: templates/taglist.tpl #: templates/taglist.tpl
#: templates/header.tpl #: templates/header.tpl
#: templates/treelist.tpl #: templates/treelist.tpl
#: templates/projectlist.tpl #: templates/projectlist.tpl
#: templates/shortloglist.tpl #: templates/shortloglist.tpl
#: include/controller/Controller_Snapshot.class.php:85 #: include/controller/Controller_Snapshot.class.php:85
msgid "snapshot" msgid "snapshot"
msgstr "" msgstr ""
   
# Used to label something stored in a git repository where the type of item - tag, blob, etc - isn't known # Used to label something stored in a git repository where the type of item - tag, blob, etc - isn't known
#: templates/tag.tpl #: templates/tag.tpl
msgid "object" msgid "object"
msgstr "" msgstr ""
   
# Used as link to and title for page displaying info about a single commit in the project # Used as link to and title for page displaying info about a single commit in the project
#: templates/tag.tpl #: templates/tag.tpl
#: templates/search.tpl #: templates/search.tpl
#: templates/commit.tpl #: templates/commit.tpl
#: templates/nav.tpl #: templates/nav.tpl
#: templates/taglist.tpl #: templates/taglist.tpl
#: templates/header.tpl #: templates/header.tpl
#: templates/log.tpl #: templates/log.tpl
#: templates/history.tpl #: templates/history.tpl
#: templates/shortloglist.tpl #: templates/shortloglist.tpl
#: include/controller/Controller_Commit.class.php:79 #: include/controller/Controller_Commit.class.php:79
msgid "commit" msgid "commit"
msgstr "" msgstr ""
   
# Used as link to and title for page displaying detailed info about a tag # Used as link to and title for page displaying detailed info about a tag
#: templates/tag.tpl #: templates/tag.tpl
#: templates/tagtip.tpl #: templates/tagtip.tpl
#: templates/taglist.tpl #: templates/taglist.tpl
#: include/controller/Controller_Tag.class.php:79 #: include/controller/Controller_Tag.class.php:79
msgid "tag" msgid "tag"
msgstr "" msgstr ""
   
# Used to label the author of the commit, and as a field to search # Used to label the author of the commit, and as a field to search
# The author is the person who wrote the changes in the commit # The author is the person who wrote the changes in the commit
#: templates/tag.tpl #: templates/tag.tpl
#: templates/commit.tpl #: templates/commit.tpl
#: templates/committip.tpl #: templates/committip.tpl
#: templates/header.tpl #: templates/header.tpl
msgid "author" msgid "author"
msgstr "" msgstr ""
   
# Used as a link to a plaintext version of a page # Used as a link to a plaintext version of a page
#: templates/blobdiff.tpl #: templates/blobdiff.tpl
#: templates/commit.tpl #: templates/commit.tpl
#: templates/blame.tpl #: templates/blame.tpl
#: templates/treelist.tpl #: templates/treelist.tpl
#: templates/commitdiff.tpl #: templates/commitdiff.tpl
#: templates/blob.tpl #: templates/blob.tpl
msgid "plain" msgid "plain"
msgstr "" msgstr ""
   
# Used as a link to a side-by-side version of a diff # Used as a link to a side-by-side version of a diff
#: templates/blobdiff.tpl #: templates/blobdiff.tpl
#: templates/commitdiff.tpl #: templates/commitdiff.tpl
msgid "side by side" msgid "side by side"
msgstr "" msgstr ""
   
# Used as a link to a unified version of a diff # Used as a link to a unified version of a diff
#: templates/blobdiff.tpl #: templates/blobdiff.tpl
#: templates/commitdiff.tpl #: templates/commitdiff.tpl
msgid "unified" msgid "unified"
msgstr "" msgstr ""
   
# Used as a link to the first page in a list of results # Used as a link to the first page in a list of results
#: templates/search.tpl #: templates/search.tpl
#: templates/searchfiles.tpl #: templates/searchfiles.tpl
msgid "first" msgid "first"
msgstr "" msgstr ""
   
# Used as a link to the previous page in a list of results # Used as a link to the previous page in a list of results
#: templates/search.tpl #: templates/search.tpl
#: templates/searchfiles.tpl #: templates/searchfiles.tpl
#: templates/log.tpl #: templates/log.tpl
#: templates/shortlog.tpl #: templates/shortlog.tpl
msgid "prev" msgid "prev"
msgstr "" msgstr ""
   
# Used as a link to the next page in a list of results # Used as a link to the next page in a list of results
#: templates/search.tpl #: templates/search.tpl
#: templates/searchfiles.tpl #: templates/searchfiles.tpl
#: templates/log.tpl #: templates/log.tpl
#: templates/shortlog.tpl #: templates/shortlog.tpl
#: templates/shortloglist.tpl #: templates/shortloglist.tpl
msgid "next" msgid "next"
msgstr "" msgstr ""
   
# Used as link to and title for the full diff of all the changes in a commit # Used as link to and title for the full diff of all the changes in a commit
#: templates/search.tpl #: templates/search.tpl
#: templates/commit.tpl #: templates/commit.tpl
#: templates/nav.tpl #: templates/nav.tpl
#: templates/log.tpl #: templates/log.tpl
#: templates/history.tpl #: templates/history.tpl
#: templates/shortloglist.tpl #: templates/shortloglist.tpl
#: include/controller/Controller_Commitdiff.class.php:85 #: include/controller/Controller_Commitdiff.class.php:85
msgid "commitdiff" msgid "commitdiff"
msgstr "" msgstr ""
   
# Used to label the committer of the commit, and as a field to search # Used to label the committer of the commit, and as a field to search
# The committer is the person who put the commit into this project # The committer is the person who put the commit into this project
#: templates/commit.tpl #: templates/commit.tpl
#: templates/committip.tpl #: templates/committip.tpl
#: templates/header.tpl #: templates/header.tpl
msgid "committer" msgid "committer"
msgstr "" msgstr ""
   
# Used to label the parent of this commit # Used to label the parent of this commit
# The parent is the commit preceding this one in the project history # The parent is the commit preceding this one in the project history
#: templates/commit.tpl #: templates/commit.tpl
msgid "parent" msgid "parent"
msgstr "" msgstr ""
   
# Used to indicate the number of files changed in a commit # Used to indicate the number of files changed in a commit
# Comes before a list of files # Comes before a list of files
# %1: the number of files # %1: the number of files
#: templates/commit.tpl #: templates/commit.tpl
#: templates/commitdiff.tpl #: templates/commitdiff.tpl
msgid "%1 file changed:" msgid "%1 file changed:"
msgid_plural "%1 files changed:" msgid_plural "%1 files changed:"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
   
# Used to indicate a new object was added with an access mode # Used to indicate a new object was added with an access mode
# %1: the type of object # %1: the type of object
# %2: the mode # %2: the mode
#: templates/commit.tpl #: templates/commit.tpl
msgid "new %1 with mode %2" msgid "new %1 with mode %2"
msgstr "" msgstr ""
   
# Used to indicate a new object was added # Used to indicate a new object was added
# %1: the type of object # %1: the type of object
#: templates/commit.tpl #: templates/commit.tpl
msgid "new %1" msgid "new %1"
msgstr "" msgstr ""
   
# Used to indicate an object was deleted # Used to indicate an object was deleted
# %1: the type of object # %1: the type of object
#: templates/commit.tpl #: templates/commit.tpl
msgid "deleted %1" msgid "deleted %1"
msgstr "" msgstr ""
   
# Used to indicate a file type changed, including original and new file modes # Used to indicate a file type changed, including original and new file modes
# (when both original and new files are regular files) # (when both original and new files are regular files)
# %1: the original file type # %1: the original file type
# %2: the new file type # %2: the new file type
# %3: the original file mode # %3: the original file mode
# %4: the new file mode # %4: the new file mode
#: templates/commit.tpl #: templates/commit.tpl
msgid "changed from %1 to %2 mode: %3 -> %4" msgid "changed from %1 to %2 mode: %3 -> %4"
msgstr "" msgstr ""
   
# Used to indicate a file type changed, with only new file mode # Used to indicate a file type changed, with only new file mode
# (when old file type wasn't a normal file) # (when old file type wasn't a normal file)
# %1: the original file type # %1: the original file type
# %2: the new file type # %2: the new file type
# %3: the original file mode # %3: the original file mode
# %4: the new file mode # %4: the new file mode
#: templates/commit.tpl #: templates/commit.tpl
msgid "changed from %1 to %2 mode: %3" msgid "changed from %1 to %2 mode: %3"
msgstr "" msgstr ""
   
# Used to indicate a file type changed # Used to indicate a file type changed
# %1: the original file type # %1: the original file type
# %2: the new file type # %2: the new file type
#: templates/commit.tpl #: templates/commit.tpl
msgid "changed from %1 to %2" msgid "changed from %1 to %2"
msgstr "" msgstr ""
   
# Used to indicate a file mode changed # Used to indicate a file mode changed
# %1: the original file mode # %1: the original file mode
# %2: the new file mode # %2: the new file mode
#: templates/commit.tpl #: templates/commit.tpl
msgid "changed mode: %1 -> %2" msgid "changed mode: %1 -> %2"
msgstr "" msgstr ""
   
# Used to indicate a file mode changed # Used to indicate a file mode changed
# %1: the new file mode # %1: the new file mode
#: templates/commit.tpl #: templates/commit.tpl
msgid "changed mode: %1" msgid "changed mode: %1"
msgstr "" msgstr ""
   
# Used to indicate a file mode changed # Used to indicate a file mode changed
#: templates/commit.tpl #: templates/commit.tpl
msgid "changed" msgid "changed"
msgstr "" msgstr ""
   
# Used as link to diff this file version with the previous version # Used as link to diff this file version with the previous version
#: templates/commit.tpl #: templates/commit.tpl
msgid "diff" msgid "diff"
msgstr "" msgstr ""
   
# Used to indicate a file was moved and the file mode changed # Used to indicate a file was moved and the file mode changed
# This string should be HTML safe # This string should be HTML safe
# %1: the old file # %1: the old file
# %2: the similarity as a percent number # %2: the similarity as a percent number
# %3: the new file mode # %3: the new file mode
#: templates/commit.tpl #: templates/commit.tpl
msgid "moved from %1 with %2%% similarity, mode: %3" msgid "moved from %1 with %2%% similarity, mode: %3"
msgstr "" msgstr ""
   
# Used to indicate a file was moved # Used to indicate a file was moved
# This string should be HTML safe # This string should be HTML safe
# %1: the old file # %1: the old file
# %2: the similarity as a percent number # %2: the similarity as a percent number
#: templates/commit.tpl #: templates/commit.tpl
msgid "moved from %1 with %2%% similarity" msgid "moved from %1 with %2%% similarity"
msgstr "" msgstr ""
   
# Used as title for and link to the compact log view with one line abbreviated commits # Used as title for and link to the compact log view with one line abbreviated commits
#: templates/headlist.tpl #: templates/headlist.tpl
#: templates/nav.tpl #: templates/nav.tpl
#: templates/taglist.tpl #: templates/taglist.tpl
#: templates/title.tpl #: templates/title.tpl
#: templates/projectlist.tpl #: templates/projectlist.tpl
#: include/controller/Controller_Log.class.php:80 #: include/controller/Controller_Log.class.php:80
msgid "shortlog" msgid "shortlog"
msgstr "" msgstr ""
   
# Used as title for and link to log view with full commit messages # Used as title for and link to log view with full commit messages
#: templates/headlist.tpl #: templates/headlist.tpl
#: templates/nav.tpl #: templates/nav.tpl
#: templates/taglist.tpl #: templates/taglist.tpl
#: templates/projectlist.tpl #: templates/projectlist.tpl
#: include/controller/Controller_Log.class.php:85 #: include/controller/Controller_Log.class.php:85
msgid "log" msgid "log"
msgstr "" msgstr ""
   
# Used as title for and link to project summary page # Used as title for and link to project summary page
#: templates/nav.tpl #: templates/nav.tpl
#: templates/projectlist.tpl #: templates/projectlist.tpl
#: include/controller/Controller_Project.class.php:75 #: include/controller/Controller_Project.class.php:75
msgid "summary" msgid "summary"
msgstr "" msgstr ""
   
# Link back to the list of projects # Link back to the list of projects
#: templates/header.tpl #: templates/header.tpl
#: include/controller/ControllerBase.class.php:257 #: include/controller/ControllerBase.class.php:257
#: include/controller/Controller_ProjectList.class.php:94 #: include/controller/Controller_ProjectList.class.php:94
msgid "projects" msgid "projects"
msgstr "" msgstr ""
   
# Used as a search type, to search the contents of files in the project # Used as a search type, to search the contents of files in the project
#: templates/header.tpl #: templates/header.tpl
#: include/git/Blob.class.php:174 #: include/git/Blob.class.php:178
msgid "file" msgid "file"
msgstr "" msgstr ""
   
# Used as title for search page, and also is the label for the search box # Used as title for search page, and also is the label for the search box
#: templates/header.tpl #: templates/header.tpl
#: include/controller/Controller_Search.class.php:93 #: include/controller/Controller_Search.class.php:93
msgid "search" msgid "search"
msgstr "" msgstr ""
   
# Used as a link to the HEAD of a project for a log or file # Used as a link to the HEAD of a project for a log or file
# (note: HEAD is standard git terminology) # (note: HEAD is standard git terminology)
#: templates/blame.tpl #: templates/blame.tpl
#: templates/log.tpl #: templates/log.tpl
#: templates/shortlog.tpl #: templates/shortlog.tpl
#: templates/blob.tpl #: templates/blob.tpl
msgid "HEAD" msgid "HEAD"
msgstr "" msgstr ""
   
# Used to indicate the last change in a project # Used to indicate the last change in a project
# %1: the timestamp of the latest change # %1: the timestamp of the latest change
#: templates/log.tpl #: templates/log.tpl
msgid "Last change %1" msgid "Last change %1"
msgstr "" msgstr ""
   
# Message displayed when there are no commits in the project to display # Message displayed when there are no commits in the project to display
#: templates/log.tpl #: templates/log.tpl
#: templates/projectlist.tpl #: templates/projectlist.tpl
#: templates/shortloglist.tpl #: templates/shortloglist.tpl
msgid "No commits" msgid "No commits"
msgstr "" msgstr ""
   
# Used as link to diff this file version with the current file # Used as link to diff this file version with the current file
#: templates/history.tpl #: templates/history.tpl
msgid "diff to current" msgid "diff to current"
msgstr "" msgstr ""
   
# Used as link to and title for page showing all tags in a project # Used as link to and title for page showing all tags in a project
#: templates/title.tpl #: templates/title.tpl
#: include/controller/Controller_Tags.class.php:76 #: include/controller/Controller_Tags.class.php:76
msgid "tags" msgid "tags"
msgstr "" msgstr ""
   
# Used as link to and title for page showing all heads in a project # Used as link to and title for page showing all heads in a project
#: templates/title.tpl #: templates/title.tpl
#: include/controller/Controller_Heads.class.php:76 #: include/controller/Controller_Heads.class.php:76
msgid "heads" msgid "heads"
msgstr "" msgstr ""
   
# Used when diffing a file, to indicate that it's been deleted # Used when diffing a file, to indicate that it's been deleted
#: templates/commitdiff.tpl #: templates/commitdiff.tpl
msgid "(deleted)" msgid "(deleted)"
msgstr "" msgstr ""
   
# Used when diffing a file, to indicate that it's a new file # Used when diffing a file, to indicate that it's a new file
#: templates/commitdiff.tpl #: templates/commitdiff.tpl
msgid "(new)" msgid "(new)"
msgstr "" msgstr ""
   
# Used to label the project description # Used to label the project description
#: templates/project.tpl #: templates/project.tpl
msgid "description" msgid "description"
msgstr "" msgstr ""
   
# Used to label the project owner # Used to label the project owner
#: templates/project.tpl #: templates/project.tpl
msgid "owner" msgid "owner"
msgstr "" msgstr ""
   
# Used to label the time the project was last changed # Used to label the time the project was last changed
# (the time of the most recent commit) # (the time of the most recent commit)
#: templates/project.tpl #: templates/project.tpl
msgid "last change" msgid "last change"
msgstr "" msgstr ""
   
# Used to label the url that users can use to clone the project # Used to label the url that users can use to clone the project
#: templates/project.tpl #: templates/project.tpl
msgid "clone url" msgid "clone url"
msgstr "" msgstr ""
   
# Used to label the url that users can use to push commits to the project # Used to label the url that users can use to push commits to the project
#: templates/project.tpl #: templates/project.tpl
msgid "push url" msgid "push url"
msgstr "" msgstr ""
   
# Used as the header for the project name column # Used as the header for the project name column
#: templates/projectlist.tpl #: templates/projectlist.tpl
msgid "Project" msgid "Project"
msgstr "" msgstr ""
   
# Used as the header for the project description column # Used as the header for the project description column
#: templates/projectlist.tpl #: templates/projectlist.tpl
msgid "Description" msgid "Description"
msgstr "" msgstr ""
   
# Used as the header for the column showing the person that owns the project # Used as the header for the column showing the person that owns the project
#: templates/projectlist.tpl #: templates/projectlist.tpl
msgid "Owner" msgid "Owner"
msgstr "" msgstr ""
   
# Used as the header for the last change column # Used as the header for the last change column
# (how long ago was the last commit) # (how long ago was the last commit)
#: templates/projectlist.tpl #: templates/projectlist.tpl
msgid "Last Change" msgid "Last Change"
msgstr "" msgstr ""
   
# Used as the header for the actions column, which is a list of links users can use to jump to various parts of this project # Used as the header for the actions column, which is a list of links users can use to jump to various parts of this project
#: templates/projectlist.tpl #: templates/projectlist.tpl
msgid "Actions" msgid "Actions"
msgstr "" msgstr ""
   
# Message shown when there were no projects found to display # Message shown when there were no projects found to display
#: templates/projectlist.tpl #: templates/projectlist.tpl
msgid "No projects found" msgid "No projects found"
msgstr "" msgstr ""
   
# Used as link to and title for page displaying blame info (who last touched what line) in a file # Used as link to and title for page displaying blame info (who last touched what line) in a file
#: templates/blob.tpl #: templates/blob.tpl
#: include/controller/Controller_Blame.class.php:79 #: include/controller/Controller_Blame.class.php:79
msgid "blame" msgid "blame"
msgstr "" msgstr ""
   
# Error message when user tries to do an action that requires a project but a project isn't specified # Error message when user tries to do an action that requires a project but a project isn't specified
#: include/controller/Controller_Feed.class.php:45 #: include/controller/Controller_Feed.class.php:45
#: include/controller/Controller_Tree.class.php:34 #: include/controller/Controller_Tree.class.php:34
#: include/controller/Controller_Commit.class.php:34 #: include/controller/Controller_Commit.class.php:34
#: include/controller/Controller_Log.class.php:34 #: include/controller/Controller_Log.class.php:34
#: include/controller/Controller_Blame.class.php:34 #: include/controller/Controller_Blame.class.php:34
#: include/controller/Controller_Snapshot.class.php:43 #: include/controller/Controller_Snapshot.class.php:43
#: include/controller/Controller_Blob.class.php:34 #: include/controller/Controller_Blob.class.php:34
#: include/controller/Controller_Tag.class.php:34 #: include/controller/Controller_Tag.class.php:34
#: include/controller/Controller_Tags.class.php:34 #: include/controller/Controller_Tags.class.php:34
#: include/controller/Controller_Project.class.php:33 #: include/controller/Controller_Project.class.php:33
#: include/controller/Controller_Commitdiff.class.php:36 #: include/controller/Controller_Commitdiff.class.php:36
#: include/controller/Controller_Blobdiff.class.php:36 #: include/controller/Controller_Blobdiff.class.php:36
#: include/controller/Controller_History.class.php:34 #: include/controller/Controller_History.class.php:34
#: include/controller/Controller_Heads.class.php:34 #: include/controller/Controller_Heads.class.php:34
#: include/controller/Controller_Search.class.php:47 #: include/controller/Controller_Search.class.php:47
msgid "Project is required" msgid "Project is required"
msgstr "" msgstr ""
   
# Used as the title of the rss controller # Used as the title of the rss controller
# rss is a standard web feed format # rss is a standard web feed format
#: include/controller/Controller_Feed.class.php:91 #: include/controller/Controller_Feed.class.php:91
msgid "rss" msgid "rss"
msgstr "" msgstr ""
   
# Used as link to and title for a diff of a single file # Used as link to and title for a diff of a single file
#: include/controller/Controller_Blobdiff.class.php:81 #: include/controller/Controller_Blobdiff.class.php:81
msgid "blobdiff" msgid "blobdiff"
msgstr "" msgstr ""
   
# Error message when user tries to access a project that doesn't exist # Error message when user tries to access a project that doesn't exist
# %1$s: the project the user tried to access # %1$s: the project the user tried to access
#: include/controller/ControllerBase.class.php:93 #: include/controller/ControllerBase.class.php:93
#, php-format #, php-format
msgid "Invalid project %1$s" msgid "Invalid project %1$s"
msgstr "" msgstr ""
   
# Used as the title for the opml controller # Used as the title for the opml controller
# OPML is a standard XML outline format # OPML is a standard XML outline format
#: include/controller/Controller_ProjectList.class.php:84 #: include/controller/Controller_ProjectList.class.php:84
msgid "opml" msgid "opml"
msgstr "" msgstr ""
   
# Used as the title of the project index controller # Used as the title of the project index controller
#: include/controller/Controller_ProjectList.class.php:89 #: include/controller/Controller_ProjectList.class.php:89
msgid "project index" msgid "project index"
msgstr "" msgstr ""
   
# Error message when a user tries to search but searching has been disabled in the config # Error message when a user tries to search but searching has been disabled in the config
#: include/controller/Controller_Search.class.php:41 #: include/controller/Controller_Search.class.php:41
msgid "Search has been disabled" msgid "Search has been disabled"
msgstr "" msgstr ""
   
# Error message when a user tries to do a file search but searching files has been disabled in the config # Error message when a user tries to do a file search but searching files has been disabled in the config
#: include/controller/Controller_Search.class.php:112 #: include/controller/Controller_Search.class.php:112
msgid "File search has been disabled" msgid "File search has been disabled"
msgstr "" msgstr ""
   
# Error message when a user's search query is too short # Error message when a user's search query is too short
# %1$d: the minimum number of characters # %1$d: the minimum number of characters
#: include/controller/Controller_Search.class.php:118 #: include/controller/Controller_Search.class.php:118
#, php-format #, php-format
msgid "You must enter search text of at least %1$d character" msgid "You must enter search text of at least %1$d character"
msgid_plural "You must enter search text of at least %1$d characters" msgid_plural "You must enter search text of at least %1$d characters"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
   
# Error message when the user enters an unsupported search type # Error message when the user enters an unsupported search type
#: include/controller/Controller_Search.class.php:162 #: include/controller/Controller_Search.class.php:162
msgid "Invalid search type" msgid "Invalid search type"
msgstr "" msgstr ""
   
# Error message when a user's search didn't produce any results # Error message when a user's search didn't produce any results
# %1$s: the user's search string # %1$s: the user's search string
#: include/controller/Controller_Search.class.php:168 #: include/controller/Controller_Search.class.php:168
#, php-format #, php-format
msgid "No matches for \"%1$s\"" msgid "No matches for \"%1$s\""
msgstr "" msgstr ""
   
# A type of filesystem object stored in a project # A type of filesystem object stored in a project
#: include/git/Blob.class.php:162 #: include/git/Blob.class.php:166
msgid "directory" msgid "directory"
msgstr "" msgstr ""
   
# A type of filesystem object stored in a project # A type of filesystem object stored in a project
#: include/git/Blob.class.php:168 #: include/git/Blob.class.php:172
msgid "symlink" msgid "symlink"
msgstr "" msgstr ""
   
# Used when an object is stored in a project but git doesn't know what type it is # Used when an object is stored in a project but git doesn't know what type it is
#: include/git/Blob.class.php:181 #: include/git/Blob.class.php:185
msgid "unknown" msgid "unknown"
msgstr "" msgstr ""
   
# Error message when user specifies a path for a project root or project, but the path given isn't a directory # Error message when user specifies a path for a project root or project, but the path given isn't a directory
# %1$s: the path the user specified # %1$s: the path the user specified
#: include/git/ProjectListDirectory.class.php:47 #: include/git/ProjectListDirectory.class.php:47
#: include/git/Project.class.php:221 #: include/git/Project.class.php:250
#, php-format #, php-format
msgid "%1$s is not a directory" msgid "%1$s is not a directory"
msgstr "" msgstr ""
   
# Error message when a path specified in the config is not a git repository # Error message when a path specified in the config is not a git repository
# %1$s: the specified path # %1$s: the specified path
#: include/git/Project.class.php:225 #: include/git/Project.class.php:254
#, php-format #, php-format
msgid "%1$s is not a git repository" msgid "%1$s is not a git repository"
msgstr "" msgstr ""
   
# Error message when a path specified is using '..' to break out of the project root (a hack attempt) # Error message when a path specified is using '..' to break out of the project root (a hack attempt)
# %1$s: The specified path # %1$s: The specified path
#: include/git/Project.class.php:229 #: include/git/Project.class.php:258
#, php-format #, php-format
msgid "%1$s is attempting directory traversal" msgid "%1$s is attempting directory traversal"
msgstr "" msgstr ""
   
# Error message when a path specified is outside of the project root # Error message when a path specified is outside of the project root
# %1$s: The specified path # %1$s: The specified path
#: include/git/Project.class.php:235 #: include/git/Project.class.php:264
#, php-format #, php-format
msgid "%1$s is outside of the projectroot" msgid "%1$s is outside of the projectroot"
msgstr "" msgstr ""
   
# Error message when a temporary directory isn't specified in the config # Error message when a temporary directory isn't specified in the config
#: include/git/TmpDir.class.php:136 #: include/git/TmpDir.class.php:136
msgid "No tmpdir defined" msgid "No tmpdir defined"
msgstr "" msgstr ""
   
# Error message when the system can't write to the temporary directory # Error message when the system can't write to the temporary directory
# %1$s: the temp dir specified # %1$s: the temp dir specified
#: include/git/TmpDir.class.php:142 #: include/git/TmpDir.class.php:142
#, php-format #, php-format
msgid "Specified tmpdir %1$s is not writable" msgid "Specified tmpdir %1$s is not writable"
msgstr "" msgstr ""
   
# Error message when the temporary directory specified isn't a directory # Error message when the temporary directory specified isn't a directory
# %1$s: the temp dir specified # %1$s: the temp dir specified
#: include/git/TmpDir.class.php:145 #: include/git/TmpDir.class.php:145
#, php-format #, php-format
msgid "Specified tmpdir %1$s is not a directory" msgid "Specified tmpdir %1$s is not a directory"
msgstr "" msgstr ""
   
# Error message when the system attempts to create the temporary directory but can't # Error message when the system attempts to create the temporary directory but can't
# %1$s: the temp dir it's trying to create # %1$s: the temp dir it's trying to create
#: include/git/TmpDir.class.php:148 #: include/git/TmpDir.class.php:148
#, php-format #, php-format
msgid "Could not create tmpdir %1$s" msgid "Could not create tmpdir %1$s"
msgstr "" msgstr ""
   
# Error message when user tries to specify a file with a list of the projects, but it isn't a file # Error message when user tries to specify a file with a list of the projects, but it isn't a file
# %1$s: the path the user specified # %1$s: the path the user specified
#: include/git/ProjectListFile.class.php:38 #: include/git/ProjectListFile.class.php:38
#, php-format #, php-format
msgid "%1$s is not a file" msgid "%1$s is not a file"
msgstr "" msgstr ""
   
# Error message when user tries to specify a file with a list of the projects, but the system can't read the file # Error message when user tries to specify a file with a list of the projects, but the system can't read the file
# %1$s: the file the user specified # %1$s: the file the user specified
#: include/git/ProjectListFile.class.php:57 #: include/git/ProjectListFile.class.php:57
#, php-format #, php-format
msgid "Failed to open project list file %1$s" msgid "Failed to open project list file %1$s"
msgstr "" msgstr ""
   
# Error message when a hash specified in a URL isn't a valid git hash # Error message when a hash specified in a URL isn't a valid git hash
# %1$s: the hash entered # %1$s: the hash entered
#: include/git/GitObject.class.php:107 #: include/git/Pack.class.php:80 include/git/GitObject.class.php:107
#, php-format #, php-format
msgid "Invalid hash %1$s" msgid "Invalid hash %1$s"
msgstr "" msgstr ""
   
# Used to represent an age in years # Used to represent an age in years
# %1$d: the number of years # %1$d: the number of years
#: include/smartyplugins/modifier.agestring.php:25 #: include/smartyplugins/modifier.agestring.php:25
#, php-format #, php-format
msgid "%1$d year ago" msgid "%1$d year ago"
msgid_plural "%1$d years ago" msgid_plural "%1$d years ago"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
   
# Used to represent an age in months # Used to represent an age in months
# %1$d: the number of months # %1$d: the number of months
#: include/smartyplugins/modifier.agestring.php:30 #: include/smartyplugins/modifier.agestring.php:30
#, php-format #, php-format
msgid "%1$d month ago" msgid "%1$d month ago"
msgid_plural "%1$d months ago" msgid_plural "%1$d months ago"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
   
# Used to represent an age in weeks # Used to represent an age in weeks
# %1$d: the number of weeks # %1$d: the number of weeks
#: include/smartyplugins/modifier.agestring.php:35 #: include/smartyplugins/modifier.agestring.php:35
#, php-format #, php-format
msgid "%1$d week ago" msgid "%1$d week ago"
msgid_plural "%1$d weeks ago" msgid_plural "%1$d weeks ago"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
   
# Used to represent an age in days # Used to represent an age in days
# %1$d: the number of days # %1$d: the number of days
#: include/smartyplugins/modifier.agestring.php:40 #: include/smartyplugins/modifier.agestring.php:40
#, php-format #, php-format
msgid "%1$d day ago" msgid "%1$d day ago"
msgid_plural "%1$d days ago" msgid_plural "%1$d days ago"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
   
# Used to represent an age in hours # Used to represent an age in hours
# %1$d: the number of hours # %1$d: the number of hours
#: include/smartyplugins/modifier.agestring.php:45 #: include/smartyplugins/modifier.agestring.php:45
#, php-format #, php-format
msgid "%1$d hour ago" msgid "%1$d hour ago"
msgid_plural "%1$d hours ago" msgid_plural "%1$d hours ago"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
   
# Used to represent an age in minutes # Used to represent an age in minutes
# %1$d: the number of minutes # %1$d: the number of minutes
#: include/smartyplugins/modifier.agestring.php:50 #: include/smartyplugins/modifier.agestring.php:50
#, php-format #, php-format
msgid "%1$d min ago" msgid "%1$d min ago"
msgid_plural "%1$d min ago" msgid_plural "%1$d min ago"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
   
# Used to represent an age in seconds # Used to represent an age in seconds
# %1$d: the number of seconds # %1$d: the number of seconds
#: include/smartyplugins/modifier.agestring.php:55 #: include/smartyplugins/modifier.agestring.php:55
#, php-format #, php-format
msgid "%1$d sec ago" msgid "%1$d sec ago"
msgid_plural "%1$d sec ago" msgid_plural "%1$d sec ago"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
   
# Used to represent a modification time of right now # Used to represent a modification time of right now
#: include/smartyplugins/modifier.agestring.php:59 #: include/smartyplugins/modifier.agestring.php:59
msgid "right now" msgid "right now"
msgstr "" msgstr ""
   
# Error message when user hasn't defined a project root in the config # Error message when user hasn't defined a project root in the config
# "projectroot" refers to a root directory where the user's git projects are stored # "projectroot" refers to a root directory where the user's git projects are stored
#: index.php:123 #: index.php:123
msgid "A projectroot must be set in the config" msgid "A projectroot must be set in the config"
msgstr "" msgstr ""
   
# Caption for the rss button for a project # Caption for the rss button for a project
# rss is a standard web feed format # rss is a standard web feed format
#: templates/footer.tpl #: templates/footer.tpl
msgid "RSS" msgid "RSS"
msgstr "" msgstr ""
   
# Caption for the OPML button on the project list # Caption for the OPML button on the project list
# OPML is a standard XML outline format # OPML is a standard XML outline format
#: templates/footer.tpl #: templates/footer.tpl
msgid "OPML" msgid "OPML"
msgstr "" msgstr ""
   
# Caption for the button to get a plaintext list of projects # Caption for the button to get a plaintext list of projects
#: templates/footer.tpl #: templates/footer.tpl
msgid "TXT" msgid "TXT"
msgstr "" msgstr ""
   
# Label for the selected commit, when selecting commits to diff # Label for the selected commit, when selecting commits to diff
#: templates/log.tpl #: templates/log.tpl
#: templates/shortlog.tpl #: templates/shortlog.tpl
msgid "selected" msgid "selected"
msgstr "" msgstr ""
   
# Link to deselect the currently selected diff # Link to deselect the currently selected diff
#: templates/log.tpl #: templates/log.tpl
#: templates/shortlog.tpl #: templates/shortlog.tpl
#: templates/shortloglist.tpl #: templates/shortloglist.tpl
msgid "deselect" msgid "deselect"
msgstr "" msgstr ""
   
# Link beside commits - diffs this commit against the currently selected commit # Link beside commits - diffs this commit against the currently selected commit
#: templates/log.tpl #: templates/log.tpl
#: templates/shortloglist.tpl #: templates/shortloglist.tpl
msgid "diff with selected" msgid "diff with selected"
msgstr "" msgstr ""
   
# Link beside commits - selects this commit to be used in a diff # Link beside commits - selects this commit to be used in a diff
#: templates/log.tpl #: templates/log.tpl
#: templates/shortloglist.tpl #: templates/shortloglist.tpl
msgid "select for diff" msgid "select for diff"
msgstr "" msgstr ""
   
# Used as an alternate text on javascript "loading" images # Used as an alternate text on javascript "loading" images
#: templates/header.tpl #: templates/header.tpl
msgid "Loading…" msgid "Loading…"
msgstr "" msgstr ""
   
# Used as a loading message while blame data is being pulled from the server # Used as a loading message while blame data is being pulled from the server
#: templates/header.tpl #: templates/header.tpl
msgid "Loading blame data…" msgid "Loading blame data…"
msgstr "" msgstr ""
   
# Used as a label by the language selector drop-down box # Used as a label by the language selector drop-down box
#: templates/header.tpl #: templates/header.tpl
msgid "language:" msgid "language:"
msgstr "" msgstr ""
   
# Used as a button by the language selector drop-down box to set the # Used as a button by the language selector drop-down box to set the
# language to the user's choice # language to the user's choice
#: templates/header.tpl #: templates/header.tpl
msgid "set" msgid "set"
msgstr "" msgstr ""
   
# Caption for the Atom button for a project # Caption for the Atom button for a project
# Atom is a standard web feed format # Atom is a standard web feed format
#: templates/footer.tpl #: templates/footer.tpl
msgid "Atom" msgid "Atom"
msgstr "" msgstr ""
   
# Used as the title of the Atom controller # Used as the title of the Atom controller
# Atom is a standard web feed format # Atom is a standard web feed format
#: include/controller/Controller_Feed.class.php:96 #: include/controller/Controller_Feed.class.php:96
msgid "atom" msgid "atom"
msgstr "" msgstr ""
   
# Used as an error message when memcache is turned # Used as an error message when memcache is turned
# on without the appropriate PHP extension installed # on without the appropriate PHP extension installed
#: include/cache/Memcache.class.php:103 #: include/cache/Memcache.class.php:103
msgid "" msgid ""
"The Memcached or Memcache PHP extension is required for Memcache support" "The Memcached or Memcache PHP extension is required for Memcache support"
msgstr "" msgstr ""
   
# Message when searching the project list, and nothing is found # Message when searching the project list, and nothing is found
# %1: the search string entered # %1: the search string entered
#: templates/header.tpl #: templates/header.tpl
#: templates/projectlist.tpl #: templates/projectlist.tpl
msgid "No matches found for \"%1\"" msgid "No matches found for \"%1\""
msgstr "" msgstr ""
   
# Label for the field to search the project list # Label for the field to search the project list
#: templates/projectlist.tpl #: templates/projectlist.tpl
msgid "Search projects" msgid "Search projects"
msgstr "" msgstr ""
   
# Error message displayed when the git executable isn't found or doesn't work # Error message displayed when the git executable isn't found or doesn't work
# %1$s: the git executable the system is trying to run # %1$s: the git executable the system is trying to run
# %2$s: the config value the user needs to set to specify the correct path # %2$s: the config value the user needs to set to specify the correct path
#: index.php:131 #: index.php:131
#, php-format #, php-format
msgid "" msgid ""
"Could not run the git executable \"%1$s\". You may need to set the \"%2$s\" " "Could not run the git executable \"%1$s\". You may need to set the \"%2$s\" "
"config value." "config value."
msgstr "" msgstr ""
   
# Error message displayed when the diff executable isn't found or doesn't work # Error message displayed when the diff executable isn't found or doesn't work
# %1$s: the diff executable the system is trying to run # %1$s: the diff executable the system is trying to run
# %2$s: the config value the user needs to set to specify the correct path # %2$s: the config value the user needs to set to specify the correct path
#: index.php:135 #: index.php:136
#, php-format #, php-format
msgid "" msgid ""
"Could not run the diff executable \"%1$s\". You may need to set the \"%2$s" "Could not run the diff executable \"%1$s\". You may need to set the \"%2$s"
"\" config value." "\" config value."
msgstr "" msgstr ""
   
# Link displayed in commitdiff view, when the user has filtered # Link displayed in commitdiff view, when the user has filtered
# the display to a single file using the list of changed files. # the display to a single file using the list of changed files.
# This will go back to showing all files in the commitdiff # This will go back to showing all files in the commitdiff
#: templates/commitdiff.tpl #: templates/commitdiff.tpl
msgid "(show all)" msgid "(show all)"
msgstr "" msgstr ""
   
  # Message displayed when diffing two binary files.
  # %1$s: the filename of the first file
  # %2$s: the filename of the second file
  #: include/git/FileDiff.class.php:810
  #, php-format
  msgid "Binary files %1$s and %2$s differ"
  msgstr ""
   
comments