Suppress warnings when description file doesn't exist
Suppress warnings when description file doesn't exist

<?php <?php
/** /**
* GitPHP Config defaults * GitPHP Config defaults
* *
* Lists all the config options and their default values * Lists all the config options and their default values
* *
* @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
*/ */
   
   
/** /**
* This file is not usable as an actual config file. * This file is not usable as an actual config file.
* To use a config value you should copy the value * To use a config value you should copy the value
* into gitphp.conf.php * into gitphp.conf.php
*/ */
throw new Exception('The defaults file should not be used as your config.'); throw new Exception('The defaults file should not be used as your config.');
   
   
/********************************************************* /*********************************************************
* Projects * Projects
*/ */
   
/* /*
* projectroot * projectroot
* Full directory on server where projects are located * Full directory on server where projects are located
*/ */
//$gitphp_conf['projectroot'] = '/pub/gitprojects/'; //$gitphp_conf['projectroot'] = '/pub/gitprojects/';
   
/* /*
* exportedonly * exportedonly
* When listing all projects in the project root, * When listing all projects in the project root,
* (not specifying any projects manually or using a project list file) * (not specifying any projects manually or using a project list file)
* set this to true to only allow repositories with the * set this to true to only allow repositories with the
* special file git-daemon-export-ok (see the git-daemon man page) * special file git-daemon-export-ok (see the git-daemon man page)
*/ */
$gitphp_conf['exportedonly'] = false; $gitphp_conf['exportedonly'] = false;
   
   
   
/********************************************************* /*********************************************************
* Appearance * Appearance
*/ */
   
/* /*
* locale * locale
* This is the default locale/language used in the interface. * This is the default locale/language used in the interface.
* The locale must exist in include/resources/locale * The locale must exist in include/resources/locale
*/ */
$gitphp_conf['locale'] = 'en_US'; $gitphp_conf['locale'] = 'en_US';
   
/* /*
* title * title
* The string that will be used as the page title * The string that will be used as the page title
* The variable '$gitphp_appstring' will expand to * The variable '$gitphp_appstring' will expand to
* the name (gitphp) and version * the name (gitphp) and version
* The variable '$gitphp_version' will expand to the * The variable '$gitphp_version' will expand to the
* version number only * version number only
*/ */
$gitphp_conf['title'] = "$gitphp_appstring"; $gitphp_conf['title'] = "$gitphp_appstring";
   
/* /*
* homelink * homelink
* This is the text of the link in the upper left corner * This is the text of the link in the upper left corner
* that takes you back to the project list. * that takes you back to the project list.
*/ */
$gitphp_conf['homelink'] = 'projects'; $gitphp_conf['homelink'] = 'projects';
   
/* /*
* cloneurl * cloneurl
* Sets the base clone url to display for a project. * Sets the base clone url to display for a project.
* This is the publicly-accessible url of the projectroot * This is the publicly-accessible url of the projectroot
* that gets prepended to the project path to create the clone * that gets prepended to the project path to create the clone
* url. It can be any format, for example: * url. It can be any format, for example:
* *
* http://server.com/ * http://server.com/
* ssh://server.com/git/ * ssh://server.com/git/
* git://server.com/gitprojects/ * git://server.com/gitprojects/
* *
* If left blank/commented, no clone url will display. * If left blank/commented, no clone url will display.
*/ */
$gitphp_conf['cloneurl'] = 'http://localhost/git/'; $gitphp_conf['cloneurl'] = 'http://localhost/git/';
   
/* /*
* pushurl * pushurl
* Sets the base push url to display for a project. * Sets the base push url to display for a project.
* Works the same as cloneurl. * Works the same as cloneurl.
*/ */
$gitphp_conf['pushurl'] = 'ssh://localhost/git/'; $gitphp_conf['pushurl'] = 'ssh://localhost/git/';
   
/* /*
* bugpattern * bugpattern
* Sets the regular expression to use to find bug number * Sets the regular expression to use to find bug number
* references in log messages. The pattern should have a * references in log messages. The pattern should have a
* group that extracts just the bug ID to pass to the * group that extracts just the bug ID to pass to the
* bug tracker. * bug tracker.
* For example, '/#([0-9+)/' will recognize any number * For example, '/#([0-9+)/' will recognize any number
* with a '#' in front of it, and groups the numeric part * with a '#' in front of it, and groups the numeric part
* only. Another common example is '/bug:([0-9]+)/' to * only. Another common example is '/bug:([0-9]+)/' to
* extract bug numbers with 'bug:' in front of them. * extract bug numbers with 'bug:' in front of them.
*/ */
//$gitphp_conf['bugpattern'] = '/#([0-9]+)/'; //$gitphp_conf['bugpattern'] = '/#([0-9]+)/';
   
/* /*
* bugurl * bugurl
* Sets the URL for the bug tracker. This URL must have * Sets the URL for the bug tracker. This URL must have
* a backreference to the group in the bug pattern that * a backreference to the group in the bug pattern that
* contains the ID. For example, ${1} uses the first * contains the ID. For example, ${1} uses the first
* group. * group.
*/ */
//$gitphp_conf['bugurl'] = 'http://localhost/mantis/view.php?id=${1}'; //$gitphp_conf['bugurl'] = 'http://localhost/mantis/view.php?id=${1}';
   
/* /*
* self * self
* This is the path to the script that will be inserted * This is the path to the script that will be inserted
* in urls. If you leave this blank/commented the script * in urls. If you leave this blank/commented the script
* will try to guess the correct URL, but you can override * will try to guess the correct URL, but you can override
* it here if it's not being guessed correctly. * it here if it's not being guessed correctly.
*/ */
$gitphp_conf['self'] = 'http://localhost/gitphp/'; $gitphp_conf['self'] = 'http://localhost/gitphp/';
   
/* /*
* stylesheet * stylesheet
* Path to look and feel (skin) stylesheet * Path to look and feel (skin) stylesheet
*/ */
$gitphp_conf['stylesheet'] = 'gitphpskin.css'; $gitphp_conf['stylesheet'] = 'gitphpskin.css';
   
/* /*
* javascript * javascript
* Toggles on javascript features * Toggles on javascript features
*/ */
$gitphp_conf['javascript'] = true; $gitphp_conf['javascript'] = true;
   
   
   
/********************************************************* /*********************************************************
* Features * Features
*/ */
   
/* /*
* compat * compat
* Set this to true to turn on compatibility mode. This will cause * Set this to true to turn on compatibility mode. This will cause
* GitPHP to rely more on the git executable for loading data, * GitPHP to rely more on the git executable for loading data,
* which will bypass some of the limitations of PHP at the expense * which will bypass some of the limitations of PHP at the expense
* of performance. * of performance.
* Turn this on if you are experiencing issues viewing data for * Turn this on if you are experiencing issues viewing data for
* your projects. * your projects.
*/ */
$gitphp_conf['compat'] = false; $gitphp_conf['compat'] = false;
   
  /**
  * largeskip
  * When GitPHP is reading through the history for pages of the shortlog/log
  * beyond the first, it needs to read from the tip but skip a number of commits
  * for the previous pages. The more commits it needs to skip, the longer it takes.
  * Calling the git executable is faster when skipping a large number of commits,
  * ie reading a log page significantly beyond the first. This determines
  * the threshold at which GitPHP will fall back to using the git exe for the log.
  * Currently each log page shows 100 commits, so this would be calculated at
  * page number * 100. So for example at the default of 200, pages 0-2 would be
  * loaded natively and pages 3+ would fall back on the git exe.
  */
  $gitphp_conf['largeskip'] = 200;
   
/* /*
* compressformat * compressformat
* Indicates what kind of compression will be done on the * Indicates what kind of compression will be done on the
* snapshot archive. Recognized settings are: * snapshot archive. Recognized settings are:
* *
* GITPHP_COMPRESS_BZ2 - create a tar.bz2 file (php must have bz2 support) * GITPHP_COMPRESS_BZ2 - create a tar.bz2 file (php must have bz2 support)
* GITPHP_COMPRESS_GZ - create a tar.gz file (php must have gzip support) * GITPHP_COMPRESS_GZ - create a tar.gz file (php must have gzip support)
* GITPHP_COMPRESS_ZIP - create a zip file * GITPHP_COMPRESS_ZIP - create a zip file
* *
* Any other setting, or no setting, will create uncompressed tar archives * Any other setting, or no setting, will create uncompressed tar archives
* If you choose a compression format and your php does not support it, * If you choose a compression format and your php does not support it,
* gitphp will fall back to uncompressed tar archives * gitphp will fall back to uncompressed tar archives
*/ */
$gitphp_conf['compressformat'] = GITPHP_COMPRESS_ZIP; $gitphp_conf['compressformat'] = GITPHP_COMPRESS_ZIP;
   
/* /*
* compresslevel * compresslevel
* Sets the compression level for snapshots. Ranges from 1-9, with * Sets the compression level for snapshots. Ranges from 1-9, with
* 9 being the most compression but requiring the most processing * 9 being the most compression but requiring the most processing
* (bzip defaults to 4, gzip defaults to -1) * (bzip defaults to 4, gzip defaults to -1)
*/ */
$gitphp_conf['compresslevel'] = 9; $gitphp_conf['compresslevel'] = 9;
   
/* /*
* geshi * geshi
* Run blob output through geshi syntax highlighting * Run blob output through geshi syntax highlighting
* and line numbering * and line numbering
*/ */
$gitphp_conf['geshi'] = true; $gitphp_conf['geshi'] = true;
   
/* /*
* search * search
* Set this to false to disable searching * Set this to false to disable searching
*/ */
$gitphp_conf['search'] = true; $gitphp_conf['search'] = true;
   
/* /*
* filesearch * filesearch
* Set this to false to disable searching within files * Set this to false to disable searching within files
* (it can be resource intensive) * (it can be resource intensive)
*/ */
$gitphp_conf['filesearch'] = true; $gitphp_conf['filesearch'] = true;
   
/* /*
* filemimetype * filemimetype
* Attempt to read the file's mimetype when displaying * Attempt to read the file's mimetype when displaying
* (for example, displaying an image as an actual image * (for example, displaying an image as an actual image
* in a browser) * in a browser)
* This requires either PHP >= 5.3.0, PECL fileinfo, or * This requires either PHP >= 5.3.0, PECL fileinfo, or
* Linux * Linux
*/ */
$gitphp_conf['filemimetype'] = true; $gitphp_conf['filemimetype'] = true;
   
   
   
   
/********************************************************* /*********************************************************
* Executable/filesystem options * Executable/filesystem options
* Important to check if you're running windows * Important to check if you're running windows
*/ */
   
/* /*
* gitbin * gitbin
* Path to git binary * Path to git binary
* For example, /usr/bin/git on Linux * For example, /usr/bin/git on Linux
* or C:\\Program Files\\Git\\bin\\git.exe on Windows * or C:\\Program Files\\Git\\bin\\git.exe on Windows
* with msysgit. You can also omit the full path and just * with msysgit. You can also omit the full path and just
* use the executable name to search the user's $PATH. * use the executable name to search the user's $PATH.
* Note: Versions of PHP below 5.2 have buggy handling of spaces * Note: Versions of PHP below 5.2 have buggy handling of spaces
* in paths. Use the 8.3 version of the filename if you're * in paths. Use the 8.3 version of the filename if you're
* having trouble, e.g. C:\\Progra~1\\Git\\bin\\git.exe * having trouble, e.g. C:\\Progra~1\\Git\\bin\\git.exe
*/ */
// Linux: // Linux:
$gitphp_conf['gitbin'] = 'git'; $gitphp_conf['gitbin'] = 'git';
// Windows (msysgit): // Windows (msysgit):
$gitphp_conf['gitbin'] = 'C:\\Progra~1\\Git\\bin\\git.exe'; $gitphp_conf['gitbin'] = 'C:\\Progra~1\\Git\\bin\\git.exe';
   
/* /*
* diffbin * diffbin
* Path to diff binary * Path to diff binary
* Same rules as gitbin * Same rules as gitbin
*/ */
// Linux: // Linux:
$gitphp_conf['diffbin'] = 'diff'; $gitphp_conf['diffbin'] = 'diff';
// Windows (msysgit): // Windows (msysgit):
$gitphp_conf['diffbin'] = 'C:\\Progra~1\\Git\\bin\\diff.exe'; $gitphp_conf['diffbin'] = 'C:\\Progra~1\\Git\\bin\\diff.exe';
   
/* /*
* gittmp * gittmp
* Location for temporary files for diffs * Location for temporary files for diffs
*/ */
$gitphp_conf['gittmp'] = '/tmp/gitphp/'; $gitphp_conf['gittmp'] = '/tmp/gitphp/';
   
/* /*
* magicdb * magicdb
* Path to the libmagic db used to read mimetype * Path to the libmagic db used to read mimetype
* Only applies if filemimetype = true * Only applies if filemimetype = true
* You can leave this as null and let the system * You can leave this as null and let the system
* try to find the database for you, but that method * try to find the database for you, but that method
* is known to have issues * is known to have issues
* If the path is correct but it's still not working, * If the path is correct but it's still not working,
* try removing the file extension if you have it on, * try removing the file extension if you have it on,
* or vice versa * or vice versa
*/ */
// Linux: // Linux:
$gitphp_conf['magicdb'] = '/usr/share/misc/magic'; $gitphp_conf['magicdb'] = '/usr/share/misc/magic';
// Windows: // Windows:
$gitphp_conf['magicdb'] = 'C:\\wamp\\php\\extras\\magic'; $gitphp_conf['magicdb'] = 'C:\\wamp\\php\\extras\\magic';
   
   
   
   
   
/******************************************************* /*******************************************************
* Cache options * Cache options
*/ */
   
/* /*
* cache * cache
* Turns on template caching. If in doubt, leave it off * Turns on template caching. If in doubt, leave it off
* You will need to create a directory 'cache' and make it * You will need to create a directory 'cache' and make it
* writable by the server * writable by the server
*/ */
$gitphp_conf['cache'] = false; $gitphp_conf['cache'] = false;
   
/* /*
* objectcache * objectcache
* Turns on object caching. This caches immutable pieces of * Turns on object caching. This caches immutable pieces of
* data from the git repository. You will need to create a * data from the git repository. You will need to create a
* directory 'cache' and make it writable by the server. * directory 'cache' and make it writable by the server.
* This can be used in place of the template cache, or * This can be used in place of the template cache, or
* in addition to it for the maximum benefit. * in addition to it for the maximum benefit.
*/ */
$gitphp_conf['objectcache'] = false; $gitphp_conf['objectcache'] = false;
   
/* /*
* cacheexpire * cacheexpire
* Attempts to automatically expire cache when a new commit renders * Attempts to automatically expire cache when a new commit renders
* it out of date. * it out of date.
* This is a good option for most users because it ensures the cache * This is a good option for most users because it ensures the cache
* is always up to date and users are seeing correct information, * is always up to date and users are seeing correct information,
* although it is a slight performance hit. * although it is a slight performance hit.
* However, if your commits are coming in so quickly that the cache * However, if your commits are coming in so quickly that the cache
* is constantly being expired, turn this off. * is constantly being expired, turn this off.
*/ */
$gitphp_conf['cacheexpire'] = true; $gitphp_conf['cacheexpire'] = true;
   
/* /*
* cachelifetime * cachelifetime
* Sets how long a page will be cached, in seconds * Sets how long a page will be cached, in seconds
* If you are automatically expiring the cache * If you are automatically expiring the cache
* (see the 'cacheexpire' option above), then this can be set * (see the 'cacheexpire' option above), then this can be set
* relatively high - 3600 seconds (1 hour) or even longer. * relatively high - 3600 seconds (1 hour) or even longer.
* -1 means no timeout. * -1 means no timeout.
* If you have turned cacheexpire off because of too many * If you have turned cacheexpire off because of too many
* cache expirations, set this low (5-10 seconds). * cache expirations, set this low (5-10 seconds).
*/ */
$gitphp_conf['cachelifetime'] = 3600; $gitphp_conf['cachelifetime'] = 3600;
   
/* /*
* objectcachelifetime * objectcachelifetime
* Sets how long git objects will be cached, in seconds * Sets how long git objects will be cached, in seconds
* The object cache only stores immutable objects from * The object cache only stores immutable objects from
* the git repository, so there's no harm in setting * the git repository, so there's no harm in setting
* this to a high number. Set to -1 to never expire. * this to a high number. Set to -1 to never expire.
*/ */
$gitphp_conf['objectcachelifetime'] = 86400; $gitphp_conf['objectcachelifetime'] = 86400;
   
/* /*
* memcache * memcache
* Enables memcache support for caching data, instead of * Enables memcache support for caching data, instead of
* Smarty's standard on-disk cache. * Smarty's standard on-disk cache.
* Only applies if cache = true or objectcache = true (or both) * Only applies if cache = true or objectcache = true (or both)
* Requires either the Memcached or Memcache PHP extensions. * Requires either the Memcached or Memcache PHP extensions.
* This is an array of servers. Each server is specified as an * This is an array of servers. Each server is specified as an
* array. * array.
* Index 0 (required): The server hostname/IP * Index 0 (required): The server hostname/IP
* Index 1 (optional): The port, default is 11211 * Index 1 (optional): The port, default is 11211
* Index 2 (optional): The weight, default is 1 * Index 2 (optional): The weight, default is 1
*/ */
//$gitphp_conf['memcache'] = array( //$gitphp_conf['memcache'] = array(
// array('127.0.0.1', 11211, 2), // array('127.0.0.1', 11211, 2),
// array('memcacheserver1', 11211), // array('memcacheserver1', 11211),
// array('memcacheserver2') // array('memcacheserver2')
//); //);
   
   
   
/******************************************************* /*******************************************************
* Paths to php libraries * Paths to php libraries
*/ */
   
/* /*
* smarty_prefix * smarty_prefix
* This is the prefix where smarty is installed. * This is the prefix where smarty is installed.
* If an absolute (starts with /) path is given, * If an absolute (starts with /) path is given,
* Smarty.class.php will be searched for in that directory. * Smarty.class.php will be searched for in that directory.
* If a relative (doesn't start with /) path is given, * If a relative (doesn't start with /) path is given,
* that subdirectory inside the php include dirs will be * that subdirectory inside the php include dirs will be
* searched. So, for example, if you specify the path as * searched. So, for example, if you specify the path as
* "/usr/share/Smarty/" then the script will look for * "/usr/share/Smarty/" then the script will look for
* /usr/share/Smarty/Smarty.class.php. * /usr/share/Smarty/Smarty.class.php.
* If you specify the path as "smarty/" then it will search * If you specify the path as "smarty/" then it will search
* the include directories in php.ini's include_path directive, * the include directories in php.ini's include_path directive,
* so it would search in places like /usr/share/php and /usr/lib/php: * so it would search in places like /usr/share/php and /usr/lib/php:
* /usr/share/php/smarty/Smarty.class.php, * /usr/share/php/smarty/Smarty.class.php,
* /usr/lib/php/smarty/Smarty.class.php, etc. * /usr/lib/php/smarty/Smarty.class.php, etc.
* Leave blank to just search in the root of the php include directories * Leave blank to just search in the root of the php include directories
* like /usr/share/php/Smarty.class.php, /usr/lib/php/Smarty.class.php, etc. * like /usr/share/php/Smarty.class.php, /usr/lib/php/Smarty.class.php, etc.
*/ */
$gitphp_conf['smarty_prefix'] = 'lib/smarty/libs/'; $gitphp_conf['smarty_prefix'] = 'lib/smarty/libs/';
   
/* /*
* geshiroot * geshiroot
* Directory where geshi is installed, only applies if geshi is enabled * Directory where geshi is installed, only applies if geshi is enabled
* NOTE: this is the path to the base geshi.php file to include, * NOTE: this is the path to the base geshi.php file to include,
* NOT the various other geshi php source files! * NOT the various other geshi php source files!
* Leave blank if geshi.php is in the gitphp root * Leave blank if geshi.php is in the gitphp root
*/ */
$gitphp_conf['geshiroot'] = 'lib/geshi/'; $gitphp_conf['geshiroot'] = 'lib/geshi/';
   
   
   
   
/******************************************************* /*******************************************************
* Debugging options * Debugging options
*/ */
   
/* /*
* debug * debug
* Turns on extra warning messages and benchmarking. * Turns on extra warning messages and benchmarking.
* Not recommended for production systems, as it will give * Not recommended for production systems, as it will give
* way more benchmarking info than you care about, and * way more benchmarking info than you care about, and
* will screw up non-html output (rss, opml, snapshots, etc) * will screw up non-html output (rss, opml, snapshots, etc)
*/ */
$gitphp_conf['debug'] = false; $gitphp_conf['debug'] = false;
   
   
<?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' => ''
//); //);
   
/* /*
* gitphpskin.css * gitphpskin.css
* *
* GitPHP look and feel stylesheet * GitPHP look and feel stylesheet
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2006-2011 Christopher Han * @copyright Copyright (c) 2006-2011 Christopher Han
* @package GitPHP * @package GitPHP
*/ */
   
   
/* /*
* Base styles * Base styles
*/ */
body { body {
font-family: sans-serif; font-family: sans-serif;
font-size: 12px; font-size: 12px;
border: solid #d9d8d1; border: solid #d9d8d1;
border-width: 1px; border-width: 1px;
margin: 10px; margin: 10px;
background-color: #ffffff; background-color: #ffffff;
color: #000000; color: #000000;
} }
   
a { a {
color: #0000cc; color: #0000cc;
} }
   
a:hover, a:visited, a:active { a:hover, a:visited, a:active {
color: #880000; color: #880000;
} }
   
.empty { .empty {
/* various empty / no data messages */ /* various empty / no data messages */
color: gray; color: gray;
} }
   
   
/* /*
* Page header * Page header
* (topmost bar with project link, language bar, etc) * (topmost bar with project link, language bar, etc)
*/ */
div.page_header { div.page_header {
height: 25px; height: 25px;
padding: 8px; padding: 8px;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
background-color: #d9d8d1; background-color: #d9d8d1;
} }
   
div.page_header a:visited, a.header { div.page_header a:visited, a.header {
color: #0000cc; color: #0000cc;
} }
   
div.page_header a:hover { div.page_header a:hover {
color: #880000; color: #880000;
} }
   
   
/* /*
* Navigation header links * Navigation header links
*/ */
div.page_nav { div.page_nav {
padding: 8px; padding: 8px;
} }
   
div.page_nav a:visited { div.page_nav a:visited {
color: #0000cc; color: #0000cc;
} }
   
   
/* /*
* Path header * Path header
* (tree/blob path navigation links) * (tree/blob path navigation links)
*/ */
div.page_path { div.page_path {
padding: 8px; padding: 8px;
border: solid #d9d8d1; border: solid #d9d8d1;
border-width: 0px 0px 1px; border-width: 0px 0px 1px;
} }
   
   
/* /*
* Page footer * Page footer
* (footer bar with project description and atom/rss links) * (footer bar with project description and atom/rss links)
*/ */
div.page_footer { div.page_footer {
height: 17px; height: 17px;
padding: 4px 8px; padding: 4px 8px;
background-color: #d9d8d1; background-color: #d9d8d1;
} }
   
   
/* /*
* Attribution footer * Attribution footer
* (bottommost footer) * (bottommost footer)
*/ */
div.attr_footer { div.attr_footer {
text-align: center; text-align: center;
padding: 4px 8px; padding: 4px 8px;
color: #888888; color: #888888;
font-style: italic; font-style: italic;
} }
   
div.attr_footer a { div.attr_footer a {
color: #888888; color: #888888;
font-style: italic; font-style: italic;
text-decoration: none; text-decoration: none;
} }
   
div.attr_footer a:hover { div.attr_footer a:hover {
text-decoration: underline; text-decoration: underline;
} }
   
   
div.page_footer_text { div.page_footer_text {
float: left; float: left;
color: #555555; color: #555555;
font-style: italic; font-style: italic;
} }
   
   
/* /*
* Page body * Page body
*/ */
div.page_body { div.page_body {
padding: 8px; padding: 8px;
} }
   
   
/* /*
* Table displays * Table displays
*/ */
table { table {
padding: 8px 4px; padding: 8px 4px;
} }
   
th { th {
padding: 2px 5px; padding: 2px 5px;
font-size: 12px; font-size: 12px;
text-align: left; text-align: left;
} }
   
tr.light:hover { tr.light:hover {
/* odd rows */ /* odd rows */
background-color: #edece6; background-color: #edece6;
} }
   
tr.dark { tr.dark {
/* even rows */ /* even rows */
background-color: #f6f6f0; background-color: #f6f6f0;
} }
   
tr.dark:hover { tr.dark:hover {
background-color: #edece6; background-color: #edece6;
} }
   
td { td {
padding: 2px 5px; padding: 2px 5px;
font-size: 12px; font-size: 12px;
vertical-align: top; vertical-align: top;
} }
   
td.link { td.link {
/* navigation links on the right side of each row */ /* navigation links on the right side of each row */
padding: 2px 5px; padding: 2px 5px;
font-family: sans-serif; font-family: sans-serif;
font-size: 10px; font-size: 10px;
} }
   
   
/* /*
* Messages * Messages
*/ */
div.message { div.message {
/* used to display information/error message to user */ /* used to display information/error message to user */
padding: 12px; padding: 12px;
} }
   
div.error { div.error {
/* highlights error messages */ /* highlights error messages */
color: #ff0000; color: #ff0000;
} }
   
   
/* /*
* Badges * Badges
*/ */
a.rss_logo { a.rss_logo {
/* the rss/atom/opml/txt buttons */ /* the rss/atom/opml/txt buttons */
padding: 3px 0px; padding: 3px 0px;
width: 35px; width: 35px;
line-height: 10px; line-height: 10px;
border: 1px solid; border: 1px solid;
border-color: #fcc7a5 #7d3302 #3e1a01 #ff954e; border-color: #fcc7a5 #7d3302 #3e1a01 #ff954e;
color: #ffffff; color: #ffffff;
background-color: #ff6600; background-color: #ff6600;
font-weight: bold; font-weight: bold;
font-family: sans-serif; font-family: sans-serif;
font-size: 10px; font-size: 10px;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
} }
   
a.rss_logo:hover { a.rss_logo:hover {
background-color: #ee5500; background-color: #ee5500;
} }
   
span.refs a { span.refs a {
/* for both tag and head badges */ /* for both tag and head badges */
color: #000000; color: #000000;
text-decoration: none; text-decoration: none;
} }
   
span.refs a:hover { span.refs a:hover {
color: #880000; color: #880000;
text-decoration: underline; text-decoration: underline;
} }
   
span.tag { span.tag {
/* tag badge */ /* tag badge */
padding: 0px 4px; padding: 0px 4px;
font-size: 10px; font-size: 10px;
font-weight: normal; font-weight: normal;
background-color: #ffffaa; background-color: #ffffaa;
border: 1px solid; border: 1px solid;
border-color: #ffffcc #ffee00 #ffee00 #ffffcc; border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
} }
   
span.head { span.head {
/* head badge */ /* head badge */
padding: 0px 4px; padding: 0px 4px;
font-size: 10px; font-size: 10px;
font-weight: normal; font-weight: normal;
background-color: #aaffaa; background-color: #aaffaa;
border: 1px solid; border: 1px solid;
border-color: #ccffcc #00cc33 #00cc33 #ccffcc; border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
} }
   
img.logo { img.logo {
/* the git logo */ /* the git logo */
border-width: 0px; border-width: 0px;
} }
   
   
/* /*
* Title bar * Title bar
* (main header with commit message) * (main header with commit message)
*/ */
div.title { div.title {
padding: 6px 8px; padding: 6px 8px;
background-color: #edece6; background-color: #edece6;
} }
   
div.title a.title { div.title a.title {
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
color: #000000; color: #000000;
} }
   
div.title:hover { div.title:hover {
background-color: #d9d8d1; background-color: #d9d8d1;
} }
   
div.title_text { div.title_text {
padding: 6px 0px; padding: 6px 0px;
border: solid #d9d8d1; border: solid #d9d8d1;
border-width: 0px 0px 1px; border-width: 0px 0px 1px;
} }
   
   
/* /*
* Search box * Search box
*/ */
div.search { div.search {
font-size: 12px; font-size: 12px;
font-weight: normal; font-weight: normal;
} }
   
   
/* /*
* Language selector * Language selector
*/ */
div.lang_select { div.lang_select {
font-size: 12px; font-size: 12px;
font-weight: normal; font-weight: normal;
} }
   
   
/* /*
* Full log view * Full log view
*/ */
span.age { span.age {
/* Age display by each log commit */ /* Age display by each log commit */
font-style: italic; font-style: italic;
} }
   
div.log_link { div.log_link {
/* Links by each log commit */ /* Links by each log commit */
font-size: 10px; font-size: 10px;
font-family: sans-serif; font-family: sans-serif;
font-style: normal; font-style: normal;
} }
   
   
/* /*
* Commit view * Commit view
*/ */
div.list_head { div.list_head {
/* Header above commit's changed files (shows # of changed files) */ /* Header above commit's changed files (shows # of changed files) */
padding: 6px 8px 4px; padding: 6px 8px 4px;
border: solid #d9d8d1; border: solid #d9d8d1;
border-width: 1px 0px 0px; border-width: 1px 0px 0px;
font-style: italic; font-style: italic;
} }
   
a.list { a.list {
/* Filename in list of changed files */ /* Filename in list of changed files */
text-decoration: none; text-decoration: none;
color: #000000; color: #000000;
} }
   
a.list:hover { a.list:hover {
text-decoration: underline; text-decoration: underline;
color: #880000; color: #880000;
} }
   
  span.commit_title {
  font-weight: bold;
  }
   
  span.merge_title {
  color: #777777;
  }
   
span.newfile { span.newfile {
color: #008000; color: #008000;
} }
   
span.deletedfile { span.deletedfile {
color: #c00000; color: #c00000;
} }
   
span.changedfile { span.changedfile {
color: #777777; color: #777777;
} }
   
span.movedfile { span.movedfile {
color: #777777; color: #777777;
} }
   
span.latenight { span.latenight {
/* highlights the time if it's after hours */ /* highlights the time if it's after hours */
color: #cc0000; color: #cc0000;
} }
   
   
/* /*
* Diff display * Diff display
*/ */
div.pre { div.pre {
/* the entire diff output block */ /* the entire diff output block */
font-family: monospace; font-family: monospace;
font-size: 12px; font-size: 12px;
} }
   
div.diff_info { div.diff_info {
/* the from -> to file header */ /* the from -> to file header */
font-family: monospace; font-family: monospace;
color: #000099; color: #000099;
background-color: #edece6; background-color: #edece6;
font-style: italic; font-style: italic;
} }
   
.diffplus { .diffplus {
color: #008800; color: #008800;
} }
   
.diffminus { .diffminus {
color: #cc0000; color: #cc0000;
} }
   
.diffat { .diffat {
color: #990099; color: #990099;
} }
   
   
/* /*
* side-by-side-diff diff * side-by-side-diff diff
*/ */
table.diffTable { table.diffTable {
font-family: monospace; font-family: monospace;
} }
   
table.diffTable tr.diff-added { table.diffTable tr.diff-added {
background-color: #C1FFC1; background-color: #C1FFC1;
} }
   
table.diffTable tr.diff-modified { table.diffTable tr.diff-modified {
background-color: #DDEEFF; background-color: #DDEEFF;
} }
   
table.diffTable tr.diff-deleted { table.diffTable tr.diff-deleted {
background-color: #FFDDDD; background-color: #FFDDDD;
} }
   
table.diffTable td.diff-left { table.diffTable td.diff-left {
border-right: 1px solid #d9d8d1; border-right: 1px solid #d9d8d1;
} }
   
/* /*
* side-by-side commitdiff * side-by-side commitdiff
*/ */
div.commitDiffSBS div.commitDiffSBS
{ {
width: 100%; width: 100%;
border-top: 2px solid #edece6; border-top: 2px solid #edece6;
} }
   
div.commitDiffSBS div.SBSTOC div.commitDiffSBS div.SBSTOC
{ {
float: left; float: left;
width: 19%; width: 19%;
word-wrap: break-word; word-wrap: break-word;
background-color: #ffffff; background-color: #ffffff;
border-bottom: 1px solid #edece6; border-bottom: 1px solid #edece6;
} }
   
div.commitDiffSBS div.SBSTOC a div.commitDiffSBS div.SBSTOC a
{ {
text-decoration: none; text-decoration: none;
} }
   
div.commitDiffSBS div.SBSTOC ul div.commitDiffSBS div.SBSTOC ul
{ {
margin-left: 8px; margin-left: 8px;
padding-left: 8px; padding-left: 8px;
} }
   
div.commitDiffSBS div.SBSTOC .listcount div.commitDiffSBS div.SBSTOC .listcount
{ {
list-style-type: none; list-style-type: none;
} }
   
div.commitDiffSBS div.SBSTOC .activeItem div.commitDiffSBS div.SBSTOC .activeItem
{ {
background-color: #edece6; background-color: #edece6;
} }
   
div.commitDiffSBS .SBSContent div.commitDiffSBS .SBSContent
{ {
float: right; float: right;
width: 80%; width: 80%;
border-left: 1px solid #edece6; border-left: 1px solid #edece6;
} }
   
div.commitDiffSBS .SBSFooter div.commitDiffSBS .SBSFooter
{ {
clear: both; clear: both;
} }
   
   
/* /*
* Blob/blame display * Blob/blame display
*/ */
a.linenr { a.linenr {
/* Line numbers (non-geshi only) */ /* Line numbers (non-geshi only) */
color: #999999; color: #999999;
text-decoration: none; text-decoration: none;
} }
   
table.code td { table.code td {
/* code table (non-geshi only) */ /* code table (non-geshi only) */
padding: 0px 0px; padding: 0px 0px;
} }
   
table.code td.num { table.code td.num {
text-align: right; text-align: right;
font-family: monospace; font-family: monospace;
font-size: 12px; font-size: 12px;
} }
   
table.code td.codeline { table.code td.codeline {
padding-left: 5px; padding-left: 5px;
font-family: monospace; font-family: monospace;
font-size: 12px; font-size: 12px;
} }
   
table.code tr.light:hover { table.code tr.light:hover {
background-color: #ffffff; background-color: #ffffff;
} }
   
table.code tr.dark:hover { table.code tr.dark:hover {
background-color: #f6f6f0; background-color: #f6f6f0;
} }
   
td#blameData { td#blameData {
/* the blame info column */ /* the blame info column */
text-align: left; text-align: left;
} }
   
td#blameData div.light:hover { td#blameData div.light:hover {
background-color: #edece6; background-color: #edece6;
} }
   
td#blameData div.dark { td#blameData div.dark {
background-color: #f6f6f0; background-color: #f6f6f0;
} }
   
td#blameData div.dark:hover { td#blameData div.dark:hover {
background-color: #edece6; background-color: #edece6;
} }
   
   
/* /*
* Project list page * Project list page
*/ */
div.index_header { div.index_header {
/* the customizable info header above the list of projects */ /* the customizable info header above the list of projects */
border: solid #d9d8d1; border: solid #d9d8d1;
border-width: 0px 0px 1px; border-width: 0px 0px 1px;
padding: 12px 8px; padding: 12px 8px;
} }
   
span.agehighlight { span.agehighlight {
/* highlights recently changed project ages */ /* highlights recently changed project ages */
color: #009900; color: #009900;
} }
   
div.projectSearch { div.projectSearch {
/* the project search box */ /* the project search box */
padding: 8px; padding: 8px;
border: solid #d9d8d1; border: solid #d9d8d1;
border-width: 0px 0px 1px; border-width: 0px 0px 1px;
} }
   
.projectName .indent { .projectName .indent {
/* indents projects underneath a category */ /* indents projects underneath a category */
margin-left: 8px; margin-left: 8px;
} }
   
   
/* /*
* Tree view * Tree view
*/ */
table.treeTable td.filesize { table.treeTable td.filesize {
/* the file size column */ /* the file size column */
text-align: right; text-align: right;
} }
   
table.treeTable td.expander { table.treeTable td.expander {
/* the javascript tree expander cell */ /* the javascript tree expander cell */
padding-right: 0px; padding-right: 0px;
} }
   
   
/* /*
* Tag view * Tag view
*/ */
table.tagTable td.link { table.tagTable td.link {
/* links at the right end of each tag */ /* links at the right end of each tag */
text-align: right; text-align: right;
} }
   
   
/* /*
* Search view * Search view
*/ */
span.searchmatch { span.searchmatch {
/* highlights string matches */ /* highlights string matches */
color: #e00000; color: #e00000;
} }
   
   
<?php <?php
/** /**
* GitPHP Util * GitPHP Util
* *
* Utility functions * Utility functions
* *
* @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
*/ */
   
/** /**
* Util class * Util class
* *
* @package GitPHP * @package GitPHP
*/ */
class GitPHP_Util class GitPHP_Util
{ {
   
/** /**
* AddSlash * AddSlash
* *
* Adds a trailing slash to a directory path if necessary * Adds a trailing slash to a directory path if necessary
* *
* @access public * @access public
* @static * @static
* @param string $path path to add slash to * @param string $path path to add slash to
* @param $filesystem true if this is a filesystem path (to also check for backslash for windows paths) * @param $filesystem true if this is a filesystem path (to also check for backslash for windows paths)
* @return string $path with a trailing slash * @return string $path with a trailing slash
*/ */
public static function AddSlash($path, $filesystem = true) public static function AddSlash($path, $filesystem = true)
{ {
if (empty($path)) if (empty($path))
return $path; return $path;
   
$end = substr($path, -1); $end = substr($path, -1);
   
if (!(( ($end == '/') || ($end == ':')) || ($filesystem && GitPHP_Util::IsWindows() && ($end == '\\')))) { if (!(( ($end == '/') || ($end == ':')) || ($filesystem && GitPHP_Util::IsWindows() && ($end == '\\')))) {
if (GitPHP_Util::IsWindows() && $filesystem) { if (GitPHP_Util::IsWindows() && $filesystem) {
$path .= '\\'; $path .= '\\';
} else { } else {
$path .= '/'; $path .= '/';
} }
} }
   
return $path; return $path;
} }
   
/** /**
* IsWindows * IsWindows
* *
* Tests if this is running on windows * Tests if this is running on windows
* *
* @access public * @access public
* @static * @static
* @return bool true if on windows * @return bool true if on windows
*/ */
public static function IsWindows() public static function IsWindows()
{ {
return (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); return (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
} }
   
/** /**
* Is64Bit * Is64Bit
* *
* Tests if this is a 64 bit machine * Tests if this is a 64 bit machine
* *
* @access public * @access public
* @static * @static
* @return bool true if on 64 bit * @return bool true if on 64 bit
*/ */
public function Is64Bit() public static function Is64Bit()
{ {
return (strpos(php_uname('m'), '64') !== false); return (strpos(php_uname('m'), '64') !== false);
} }
   
/** /**
* MakeSlug * MakeSlug
* *
* Turn a string into a filename-friendly slug * Turn a string into a filename-friendly slug
* *
* @access public * @access public
* @param string $str string to slugify * @param string $str string to slugify
* @static * @static
* @return string slug * @return string slug
*/ */
public static function MakeSlug($str) public static function MakeSlug($str)
{ {
$from = array( $from = array(
'/' '/'
); );
$to = array( $to = array(
'-' '-'
); );
return str_replace($from, $to, $str); return str_replace($from, $to, $str);
} }
   
} }
   
<?php <?php
/** /**
* GitPHP Blob * GitPHP Blob
* *
* Represents a single blob * Represents a single blob
* *
* @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 . 'FilesystemObject.class.php'); require_once(GITPHP_GITOBJECTDIR . 'FilesystemObject.class.php');
require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php'); require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php');
   
/** /**
* Commit class * Commit class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Blob extends GitPHP_FilesystemObject class GitPHP_Blob extends GitPHP_FilesystemObject
{ {
   
/** /**
* data * data
* *
* Stores the file data * Stores the file data
* *
* @access protected * @access protected
*/ */
protected $data; protected $data;
   
/** /**
* dataRead * dataRead
* *
* Stores whether data has been read * Stores whether data has been read
* *
* @access protected * @access protected
*/ */
protected $dataRead = false; protected $dataRead = false;
   
/** /**
* size * size
* *
* Stores the size * Stores the size
* *
* @access protected * @access protected
*/ */
protected $size = null; protected $size = null;
   
/** /**
* history * history
* *
* Stores the history * Stores the history
* *
* @access protected * @access protected
*/ */
protected $history = array(); protected $history = array();
   
/** /**
* historyRead * historyRead
* *
* Stores whether the history has been read * Stores whether the history has been read
* *
* @access protected * @access protected
*/ */
protected $historyRead = false; protected $historyRead = false;
   
/** /**
* blame * blame
* *
* Stores blame info * Stores blame info
* *
* @access protected * @access protected
*/ */
protected $blame = array(); protected $blame = array();
   
/** /**
* blameRead * blameRead
* *
* Stores whether blame was read * Stores whether blame was read
* *
* @access protected * @access protected
*/ */
protected $blameRead = false; protected $blameRead = false;
   
/** /**
* __construct * __construct
* *
* Instantiates object * Instantiates object
* *
* @access public * @access public
* @param mixed $project the project * @param mixed $project the project
* @param string $hash object hash * @param string $hash object hash
* @return mixed blob object * @return mixed blob object
* @throws Exception exception on invalid hash * @throws Exception exception on invalid hash
*/ */
public function __construct($project, $hash) public function __construct($project, $hash)
{ {
parent::__construct($project, $hash); parent::__construct($project, $hash);
} }
   
/** /**
* GetData * GetData
* *
* Gets the blob data * Gets the blob data
* *
* @access public * @access public
* @param boolean $explode true to explode data into an array of lines * @param boolean $explode true to explode data into an array of lines
* @return string blob data * @return string blob data
*/ */
public function GetData($explode = false) public function GetData($explode = false)
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($explode) if ($explode)
return explode("\n", $this->data); return explode("\n", $this->data);
else else
return $this->data; return $this->data;
} }
   
/** /**
* ReadData * ReadData
* *
* Reads the blob data * Reads the blob data
* *
* @access private * @access private
*/ */
private function ReadData() private function ReadData()
{ {
$this->dataRead = true; $this->dataRead = true;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
   
$args = array(); $args = array();
$args[] = 'blob'; $args[] = 'blob';
$args[] = $this->hash; $args[] = $this->hash;
   
$this->data = $exe->Execute(GIT_CAT_FILE, $args); $this->data = $exe->Execute(GIT_CAT_FILE, $args);
} else { } else {
$this->data = $this->GetProject()->GetObject($this->hash); $this->data = $this->GetProject()->GetObject($this->hash);
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* FileType * FileType
* *
* Gets a file type from its octal mode * Gets a file type from its octal mode
* *
* @access public * @access public
* @static * @static
* @param string $octMode octal mode * @param string $octMode octal mode
* @param boolean $local true if caller wants localized type * @param boolean $local true if caller wants localized type
* @return string file type * @return string file type
*/ */
public static function FileType($octMode, $local = false) public static function FileType($octMode, $local = false)
{ {
$mode = octdec($octMode); $mode = octdec($octMode);
if (($mode & 0x4000) == 0x4000) { if (($mode & 0x4000) == 0x4000) {
if ($local) { if ($local) {
return __('directory'); return __('directory');
} else { } else {
return 'directory'; return 'directory';
} }
} else if (($mode & 0xA000) == 0xA000) { } else if (($mode & 0xA000) == 0xA000) {
if ($local) { if ($local) {
return __('symlink'); return __('symlink');
} else { } else {
return 'symlink'; return 'symlink';
} }
} else if (($mode & 0x8000) == 0x8000) { } else if (($mode & 0x8000) == 0x8000) {
if ($local) { if ($local) {
return __('file'); return __('file');
} else { } else {
return 'file'; return 'file';
} }
} }
   
if ($local) { if ($local) {
return __('unknown'); return __('unknown');
} else { } else {
return 'unknown'; return 'unknown';
} }
} }
   
/** /**
* GetSize * GetSize
* *
* Gets the blob size * Gets the blob size
* *
* @access public * @access public
* @return integer size * @return integer size
*/ */
public function GetSize() public function GetSize()
{ {
if ($this->size !== null) { if ($this->size !== null) {
return $this->size; return $this->size;
} }
   
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return strlen($this->data); return strlen($this->data);
} }
   
/** /**
* SetSize * SetSize
* *
* Sets the blob size * Sets the blob size
* *
* @access public * @access public
* @param integer $size size * @param integer $size size
*/ */
public function SetSize($size) public function SetSize($size)
{ {
$this->size = $size; $this->size = $size;
} }
   
/** /**
  * IsBinary
  *
  * Tests if this blob is a binary file
  *
  * @access public
  * @return boolean true if binary file
  */
  public function IsBinary()
  {
  if (!$this->dataRead)
  $this->ReadData();
   
  $data = $this->data;
  if (strlen($this->data) > 8000)
  $data = substr($data, 0, 8000);
   
  return strpos($data, chr(0)) !== false;
  }
   
  /**
* FileMime * FileMime
* *
* Get the file mimetype * Get the file mimetype
* *
* @access public * @access public
* @param boolean $short true to only the type group * @param boolean $short true to only the type group
* @return string mime * @return string mime
*/ */
public function FileMime($short = false) public function FileMime($short = false)
{ {
$mime = $this->FileMime_Fileinfo(); $mime = $this->FileMime_Fileinfo();
   
if (empty($mime)) if (empty($mime))
$mime = $this->FileMime_File(); $mime = $this->FileMime_File();
   
if (empty($mime)) if (empty($mime))
$mime = $this->FileMime_Extension(); $mime = $this->FileMime_Extension();
   
if ((!empty($mime)) && $short) { if ((!empty($mime)) && $short) {
$mime = strtok($mime, '/'); $mime = strtok($mime, '/');
} }
   
return $mime; return $mime;
} }
   
/** /**
* FileMime_Fileinfo * FileMime_Fileinfo
* *
* Get the file mimetype using fileinfo * Get the file mimetype using fileinfo
* *
* @access private * @access private
* @return string mimetype * @return string mimetype
*/ */
private function FileMime_Fileinfo() private function FileMime_Fileinfo()
{ {
if (!function_exists('finfo_buffer')) if (!function_exists('finfo_buffer'))
return ''; return '';
   
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if (!$this->data) if (!$this->data)
return ''; return '';
   
$mime = ''; $mime = '';
   
$magicdb = GitPHP_Config::GetInstance()->GetValue('magicdb', null); $magicdb = GitPHP_Config::GetInstance()->GetValue('magicdb', null);
if (empty($magicdb)) { if (empty($magicdb)) {
if (GitPHP_Util::IsWindows()) { if (GitPHP_Util::IsWindows()) {
$magicdb = 'C:\\wamp\\php\\extras\\magic'; $magicdb = 'C:\\wamp\\php\\extras\\magic';
} else { } else {
$magicdb = '/usr/share/misc/magic'; $magicdb = '/usr/share/misc/magic';
} }
} }
   
$finfo = finfo_open(FILEINFO_MIME, $magicdb); $finfo = @finfo_open(FILEINFO_MIME, $magicdb);
if ($finfo) { if ($finfo) {
$mime = finfo_buffer($finfo, $this->data, FILEINFO_MIME); $mime = finfo_buffer($finfo, $this->data, FILEINFO_MIME);
if ($mime && strpos($mime, '/')) { if ($mime && strpos($mime, '/')) {
if (strpos($mime, ';')) { if (strpos($mime, ';')) {
$mime = strtok($mime, ';'); $mime = strtok($mime, ';');
} }
} }
finfo_close($finfo); finfo_close($finfo);
} }
   
return $mime; return $mime;
} }
   
/** /**
* FileMime_File * FileMime_File
* *
* Get the file mimetype using file command * Get the file mimetype using file command
* *
* @access private * @access private
* @return string mimetype * @return string mimetype
*/ */
private function FileMime_File() private function FileMime_File()
{ {
if (GitPHP_Util::IsWindows()) { if (GitPHP_Util::IsWindows()) {
return ''; return '';
} }
   
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if (!$this->data) if (!$this->data)
return ''; return '';
   
$descspec = array( $descspec = array(
0 => array('pipe', 'r'), 0 => array('pipe', 'r'),
1 => array('pipe', 'w') 1 => array('pipe', 'w')
); );
   
$proc = proc_open('file -b --mime -', $descspec, $pipes); $proc = proc_open('file -b --mime -', $descspec, $pipes);
if (is_resource($proc)) { if (is_resource($proc)) {
fwrite($pipes[0], $this->data); fwrite($pipes[0], $this->data);
fclose($pipes[0]); fclose($pipes[0]);
$mime = stream_get_contents($pipes[1]); $mime = stream_get_contents($pipes[1]);
fclose($pipes[1]); fclose($pipes[1]);
proc_close($proc); proc_close($proc);
   
if ($mime && strpos($mime, '/')) { if ($mime && strpos($mime, '/')) {
if (strpos($mime, ';')) { if (strpos($mime, ';')) {
$mime = strtok($mime, ';'); $mime = strtok($mime, ';');
} }
return $mime; return $mime;
} }
} }
   
return ''; return '';
} }
   
/** /**
* FileMime_Extension * FileMime_Extension
* *
* Get the file mimetype using the file extension * Get the file mimetype using the file extension
* *
* @access private * @access private
* @return string mimetype * @return string mimetype
*/ */
private function FileMime_Extension() private function FileMime_Extension()
{ {
$file = $this->GetName(); $file = $this->GetName();
   
if (empty($file)) if (empty($file))
return ''; return '';
   
$dotpos = strrpos($file, '.'); $dotpos = strrpos($file, '.');
if ($dotpos !== FALSE) if ($dotpos !== FALSE)
$file = substr($file, $dotpos+1); $file = substr($file, $dotpos+1);
switch ($file) { switch ($file) {
case 'jpg': case 'jpg':
case 'jpeg': case 'jpeg':
case 'jpe': case 'jpe':
return 'image/jpeg'; return 'image/jpeg';
break; break;
case 'gif': case 'gif':
return 'image/gif'; return 'image/gif';
break; break;
case 'png'; case 'png';
return 'image/png'; return 'image/png';
break; break;
} }
   
return ''; return '';
} }
   
/** /**
* GetHistory * GetHistory
* *
* Gets the history of this file * Gets the history of this file
* *
* @access public * @access public
* @return array array of filediff changes * @return array array of filediff changes
*/ */
public function GetHistory() public function GetHistory()
{ {
if (!$this->historyRead) if (!$this->historyRead)
$this->ReadHistory(); $this->ReadHistory();
   
return $this->history; return $this->history;
} }
   
/** /**
* ReadHistory * ReadHistory
* *
* Reads the file history * Reads the file history
* *
* @access private * @access private
*/ */
private function ReadHistory() private function ReadHistory()
{ {
$this->historyRead = true; $this->historyRead = true;
   
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
if (isset($this->commit)) if (isset($this->commit))
$args[] = $this->commit->GetHash(); $args[] = $this->commit->GetHash();
else else
$args[] = 'HEAD'; $args[] = 'HEAD';
$args[] = '|'; $args[] = '|';
$args[] = $exe->GetBinary(); $args[] = $exe->GetBinary();
$args[] = '--git-dir=' . $this->GetProject()->GetPath(); $args[] = '--git-dir=' . $this->GetProject()->GetPath();
$args[] = GIT_DIFF_TREE; $args[] = GIT_DIFF_TREE;
$args[] = '-r'; $args[] = '-r';
$args[] = '--stdin'; $args[] = '--stdin';
$args[] = '--'; $args[] = '--';
$args[] = $this->GetPath(); $args[] = $this->GetPath();
$historylines = explode("\n", $exe->Execute(GIT_REV_LIST, $args)); $historylines = explode("\n", $exe->Execute(GIT_REV_LIST, $args));
   
$commit = null; $commit = null;
foreach ($historylines as $line) { foreach ($historylines as $line) {
if (preg_match('/^([0-9a-fA-F]{40})/', $line, $regs)) { if (preg_match('/^([0-9a-fA-F]{40})/', $line, $regs)) {
$commit = $this->GetProject()->GetCommit($regs[1]); $commit = $this->GetProject()->GetCommit($regs[1]);
} else if ($commit) { } else if ($commit) {
try { try {
$history = new GitPHP_FileDiff($this->GetProject(), $line); $history = new GitPHP_FileDiff($this->GetProject(), $line);
$history->SetCommit($commit); $history->SetCommit($commit);
$this->history[] = $history; $this->history[] = $history;
} catch (Exception $e) { } catch (Exception $e) {
} }
unset ($commit); unset ($commit);
} }
} }
} }
   
/** /**
* GetBlame * GetBlame
* *
* Gets blame info * Gets blame info
* *
* @access public * @access public
* @return array blame array (line to commit mapping) * @return array blame array (line to commit mapping)
*/ */
public function GetBlame() public function GetBlame()
{ {
if (!$this->blameRead) if (!$this->blameRead)
$this->ReadBlame(); $this->ReadBlame();
   
return $this->blame; return $this->blame;
} }
   
/** /**
* ReadBlame * ReadBlame
* *
* Read blame info * Read blame info
* *
* @access private * @access private
*/ */
private function ReadBlame() private function ReadBlame()
{ {
$this->blameRead = true; $this->blameRead = true;
   
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
   
$args = array(); $args = array();
$args[] = '-s'; $args[] = '-s';
$args[] = '-l'; $args[] = '-l';
if ($this->commit) if ($this->commit)
$args[] = $this->commit->GetHash(); $args[] = $this->commit->GetHash();
else else
$args[] = 'HEAD'; $args[] = 'HEAD';
$args[] = '--'; $args[] = '--';
$args[] = $this->GetPath(); $args[] = $this->GetPath();
   
$blamelines = explode("\n", $exe->Execute(GIT_BLAME, $args)); $blamelines = explode("\n", $exe->Execute(GIT_BLAME, $args));
   
$lastcommit = ''; $lastcommit = '';
foreach ($blamelines as $line) { foreach ($blamelines as $line) {
if (preg_match('/^([0-9a-fA-F]{40})(\s+.+)?\s+([0-9]+)\)/', $line, $regs)) { if (preg_match('/^([0-9a-fA-F]{40})(\s+.+)?\s+([0-9]+)\)/', $line, $regs)) {
if ($regs[1] != $lastcommit) { if ($regs[1] != $lastcommit) {
$this->blame[(int)($regs[3])] = $this->GetProject()->GetCommit($regs[1]); $this->blame[(int)($regs[3])] = $this->GetProject()->GetCommit($regs[1]);
$lastcommit = $regs[1]; $lastcommit = $regs[1];
} }
} }
} }
} }
   
/** /**
* __sleep * __sleep
* *
* Called to prepare the object for serialization * Called to prepare the object for serialization
* *
* @access public * @access public
* @return array list of properties to serialize * @return array list of properties to serialize
*/ */
public function __sleep() public function __sleep()
{ {
$properties = array('data', 'dataRead'); $properties = array('data', 'dataRead');
   
return array_merge($properties, parent::__sleep()); return array_merge($properties, parent::__sleep());
} }
   
/** /**
* GetCacheKey * GetCacheKey
* *
* Gets the cache key to use for this object * Gets the cache key to use for this object
* *
* @access public * @access public
* @return string cache key * @return string cache key
*/ */
public function GetCacheKey() public function GetCacheKey()
{ {
$key = parent::GetCacheKey(); $key = parent::GetCacheKey();
if (!empty($key)) if (!empty($key))
$key .= '|'; $key .= '|';
   
$key .= 'blob|' . $this->hash; $key .= 'blob|' . $this->hash;
   
return $key; return $key;
} }
   
} }
   
<?php <?php
/** /**
* GitPHP Commit * GitPHP Commit
* *
* Represents a single commit * Represents a single commit
* *
* @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 . 'GitObject.class.php'); require_once(GITPHP_GITOBJECTDIR . 'GitObject.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Tree.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Tree.class.php');
require_once(GITPHP_GITOBJECTDIR . 'TreeDiff.class.php'); require_once(GITPHP_GITOBJECTDIR . 'TreeDiff.class.php');
   
/** /**
* Commit class * Commit class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Commit extends GitPHP_GitObject class GitPHP_Commit extends GitPHP_GitObject
{ {
   
/** /**
* dataRead * dataRead
* *
* Indicates whether data for this commit has been read * Indicates whether data for this commit has been read
* *
* @access protected * @access protected
*/ */
protected $dataRead = false; protected $dataRead = false;
   
/** /**
* parents * parents
* *
* Array of parent commits * Array of parent commits
* *
* @access protected * @access protected
*/ */
protected $parents = array(); protected $parents = array();
   
/** /**
* tree * tree
* *
* Tree object for this commit * Tree object for this commit
* *
* @access protected * @access protected
*/ */
protected $tree; protected $tree;
   
/** /**
* author * author
* *
* Author for this commit * Author for this commit
* *
* @access protected * @access protected
*/ */
protected $author; protected $author;
   
/** /**
* authorEpoch * authorEpoch
* *
* Author's epoch * Author's epoch
* *
* @access protected * @access protected
*/ */
protected $authorEpoch; protected $authorEpoch;
   
/** /**
* authorTimezone * authorTimezone
* *
* Author's timezone * Author's timezone
* *
* @access protected * @access protected
*/ */
protected $authorTimezone; protected $authorTimezone;
   
/** /**
* committer * committer
* *
* Committer for this commit * Committer for this commit
* *
* @access protected * @access protected
*/ */
protected $committer; protected $committer;
   
/** /**
* committerEpoch * committerEpoch
* *
* Committer's epoch * Committer's epoch
* *
* @access protected * @access protected
*/ */
protected $committerEpoch; protected $committerEpoch;
   
/** /**
* committerTimezone * committerTimezone
* *
* Committer's timezone * Committer's timezone
* *
* @access protected * @access protected
*/ */
protected $committerTimezone; protected $committerTimezone;
   
/** /**
* title * title
* *
* Stores the commit title * Stores the commit title
* *
* @access protected * @access protected
*/ */
protected $title; protected $title;
   
/** /**
* comment * comment
* *
* Stores the commit comment * Stores the commit comment
* *
* @access protected * @access protected
*/ */
protected $comment = array(); protected $comment = array();
   
/** /**
* readTree * readTree
* *
* Stores whether tree filenames have been read * Stores whether tree filenames have been read
* *
* @access protected * @access protected
*/ */
protected $readTree = false; protected $readTree = false;
   
/** /**
* blobPaths * blobPaths
* *
* Stores blob hash to path mappings * Stores blob hash to path mappings
* *
* @access protected * @access protected
*/ */
protected $blobPaths = array(); protected $blobPaths = array();
   
/** /**
* treePaths * treePaths
* *
* Stores tree hash to path mappings * Stores tree hash to path mappings
* *
* @access protected * @access protected
*/ */
protected $treePaths = array(); protected $treePaths = array();
   
/** /**
* hashPathsRead * hashPathsRead
* *
* Stores whether hash paths have been read * Stores whether hash paths have been read
* *
* @access protected * @access protected
*/ */
protected $hashPathsRead = false; protected $hashPathsRead = false;
   
/** /**
* containingTag * containingTag
* *
* Stores the tag containing the changes in this commit * Stores the tag containing the changes in this commit
* *
* @access protected * @access protected
*/ */
protected $containingTag = null; protected $containingTag = null;
   
/** /**
* containingTagRead * containingTagRead
* *
* Stores whether the containing tag has been looked up * Stores whether the containing tag has been looked up
* *
* @access public * @access public
*/ */
protected $containingTagRead = false; protected $containingTagRead = false;
   
/** /**
* parentsReferenced * parentsReferenced
* *
* Stores whether the parents have been referenced into pointers * Stores whether the parents have been referenced into pointers
* *
* @access private * @access private
*/ */
private $parentsReferenced = false; private $parentsReferenced = false;
   
/** /**
* treeReferenced * treeReferenced
* *
* Stores whether the tree has been referenced into a pointer * Stores whether the tree has been referenced into a pointer
* *
* @access private * @access private
*/ */
private $treeReferenced = false; private $treeReferenced = false;
   
/** /**
* __construct * __construct
* *
* Instantiates object * Instantiates object
* *
* @access public * @access public
* @param mixed $project the project * @param mixed $project the project
* @param string $hash object hash * @param string $hash object hash
* @return mixed git object * @return mixed git object
* @throws Exception exception on invalid hash * @throws Exception exception on invalid hash
*/ */
public function __construct($project, $hash) public function __construct($project, $hash)
{ {
parent::__construct($project, $hash); parent::__construct($project, $hash);
} }
   
/** /**
* GetParent * GetParent
* *
* Gets the main parent of this commit * Gets the main parent of this commit
* *
* @access public * @access public
* @return mixed commit object for parent * @return mixed commit object for parent
*/ */
public function GetParent() public function GetParent()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->parentsReferenced) if ($this->parentsReferenced)
$this->DereferenceParents(); $this->DereferenceParents();
   
if (isset($this->parents[0])) if (isset($this->parents[0]))
return $this->parents[0]; return $this->parents[0];
return null; return null;
} }
   
/** /**
* GetParents * GetParents
* *
* Gets an array of parent objects for this commit * Gets an array of parent objects for this commit
* *
* @access public * @access public
* @return mixed array of commit objects * @return mixed array of commit objects
*/ */
public function GetParents() public function GetParents()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->parentsReferenced) if ($this->parentsReferenced)
$this->DereferenceParents(); $this->DereferenceParents();
   
return $this->parents; return $this->parents;
} }
   
/** /**
* GetTree * GetTree
* *
* Gets the tree for this commit * Gets the tree for this commit
* *
* @access public * @access public
* @return mixed tree object * @return mixed tree object
*/ */
public function GetTree() public function GetTree()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->treeReferenced) if ($this->treeReferenced)
$this->DereferenceTree(); $this->DereferenceTree();
   
return $this->tree; return $this->tree;
} }
   
/** /**
* GetAuthor * GetAuthor
* *
* Gets the author for this commit * Gets the author for this commit
* *
* @access public * @access public
* @return string author * @return string author
*/ */
public function GetAuthor() public function GetAuthor()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->author; return $this->author;
} }
   
/** /**
* GetAuthorName * GetAuthorName
* *
* Gets the author's name only * Gets the author's name only
* *
* @access public * @access public
* @return string author name * @return string author name
*/ */
public function GetAuthorName() public function GetAuthorName()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return preg_replace('/ <.*/', '', $this->author); return preg_replace('/ <.*/', '', $this->author);
} }
   
/** /**
* GetAuthorEpoch * GetAuthorEpoch
* *
* Gets the author's epoch * Gets the author's epoch
* *
* @access public * @access public
* @return string author epoch * @return string author epoch
*/ */
public function GetAuthorEpoch() public function GetAuthorEpoch()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->authorEpoch; return $this->authorEpoch;
} }
   
/** /**
* GetAuthorLocalEpoch * GetAuthorLocalEpoch
* *
* Gets the author's local epoch * Gets the author's local epoch
* *
* @access public * @access public
* @return string author local epoch * @return string author local epoch
*/ */
public function GetAuthorLocalEpoch() public function GetAuthorLocalEpoch()
{ {
$epoch = $this->GetAuthorEpoch(); $epoch = $this->GetAuthorEpoch();
$tz = $this->GetAuthorTimezone(); $tz = $this->GetAuthorTimezone();
if (preg_match('/^([+\-][0-9][0-9])([0-9][0-9])$/', $tz, $regs)) { if (preg_match('/^([+\-][0-9][0-9])([0-9][0-9])$/', $tz, $regs)) {
$local = $epoch + ((((int)$regs[1]) + ($regs[2]/60)) * 3600); $local = $epoch + ((((int)$regs[1]) + ($regs[2]/60)) * 3600);
return $local; return $local;
} }
return $epoch; return $epoch;
} }
   
/** /**
* GetAuthorTimezone * GetAuthorTimezone
* *
* Gets the author's timezone * Gets the author's timezone
* *
* @access public * @access public
* @return string author timezone * @return string author timezone
*/ */
public function GetAuthorTimezone() public function GetAuthorTimezone()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->authorTimezone; return $this->authorTimezone;
} }
   
/** /**
* GetCommitter * GetCommitter
* *
* Gets the author for this commit * Gets the author for this commit
* *
* @access public * @access public
* @return string author * @return string author
*/ */
public function GetCommitter() public function GetCommitter()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->committer; return $this->committer;
} }
   
/** /**
* GetCommitterName * GetCommitterName
* *
* Gets the author's name only * Gets the author's name only
* *
* @access public * @access public
* @return string author name * @return string author name
*/ */
public function GetCommitterName() public function GetCommitterName()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return preg_replace('/ <.*/', '', $this->committer); return preg_replace('/ <.*/', '', $this->committer);
} }
   
/** /**
* GetCommitterEpoch * GetCommitterEpoch
* *
* Gets the committer's epoch * Gets the committer's epoch
* *
* @access public * @access public
* @return string committer epoch * @return string committer epoch
*/ */
public function GetCommitterEpoch() public function GetCommitterEpoch()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->committerEpoch; return $this->committerEpoch;
} }
   
/** /**
* GetCommitterLocalEpoch * GetCommitterLocalEpoch
* *
* Gets the committer's local epoch * Gets the committer's local epoch
* *
* @access public * @access public
* @return string committer local epoch * @return string committer local epoch
*/ */
public function GetCommitterLocalEpoch() public function GetCommitterLocalEpoch()
{ {
$epoch = $this->GetCommitterEpoch(); $epoch = $this->GetCommitterEpoch();
$tz = $this->GetCommitterTimezone(); $tz = $this->GetCommitterTimezone();
if (preg_match('/^([+\-][0-9][0-9])([0-9][0-9])$/', $tz, $regs)) { if (preg_match('/^([+\-][0-9][0-9])([0-9][0-9])$/', $tz, $regs)) {
$local = $epoch + ((((int)$regs[1]) + ($regs[2]/60)) * 3600); $local = $epoch + ((((int)$regs[1]) + ($regs[2]/60)) * 3600);
return $local; return $local;
} }
return $epoch; return $epoch;
} }
   
/** /**
* GetCommitterTimezone * GetCommitterTimezone
* *
* Gets the author's timezone * Gets the author's timezone
* *
* @access public * @access public
* @return string author timezone * @return string author timezone
*/ */
public function GetCommitterTimezone() public function GetCommitterTimezone()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->committerTimezone; return $this->committerTimezone;
} }
   
/** /**
* GetTitle * GetTitle
* *
* Gets the commit title * Gets the commit title
* *
* @access public * @access public
* @param integer $trim length to trim to (0 for no trim) * @param integer $trim length to trim to (0 for no trim)
* @return string title * @return string title
*/ */
public function GetTitle($trim = 0) public function GetTitle($trim = 0)
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
if (($trim > 0) && (strlen($this->title) > $trim)) { if (($trim > 0) && (strlen($this->title) > $trim)) {
return substr($this->title, 0, $trim) . '…'; return substr($this->title, 0, $trim) . '…';
} }
   
return $this->title; return $this->title;
} }
   
/** /**
* GetComment * GetComment
* *
* Gets the lines of comment * Gets the lines of comment
* *
* @access public * @access public
* @return array lines of comment * @return array lines of comment
*/ */
public function GetComment() public function GetComment()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->comment; return $this->comment;
} }
   
/** /**
* SearchComment * SearchComment
* *
* Gets the lines of the comment matching the given pattern * Gets the lines of the comment matching the given pattern
* *
* @access public * @access public
* @param string $pattern pattern to find * @param string $pattern pattern to find
* @return array matching lines of comment * @return array matching lines of comment
*/ */
public function SearchComment($pattern) public function SearchComment($pattern)
{ {
if (empty($pattern)) if (empty($pattern))
return $this->GetComment(); return $this->GetComment();
   
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return preg_grep('/' . $pattern . '/i', $this->comment); return preg_grep('/' . $pattern . '/i', $this->comment);
} }
   
/** /**
* GetAge * GetAge
* *
* Gets the age of the commit * Gets the age of the commit
* *
* @access public * @access public
* @return string age * @return string age
*/ */
public function GetAge() public function GetAge()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if (!empty($this->committerEpoch)) if (!empty($this->committerEpoch))
return time() - $this->committerEpoch; return time() - $this->committerEpoch;
   
return ''; return '';
} }
   
/** /**
  * IsMergeCommit
  *
  * Returns whether this is a merge commit
  *
  * @access pubilc
  * @return boolean true if merge commit
  */
  public function IsMergeCommit()
  {
  if (!$this->dataRead)
  $this->ReadData();
   
  return count($this->parents) > 1;
  }
   
  /**
* ReadData * ReadData
* *
* Read the data for the commit * Read the data for the commit
* *
* @access protected * @access protected
*/ */
protected function ReadData() protected function ReadData()
{ {
$this->dataRead = true; $this->dataRead = true;
   
$lines = null; $lines = null;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
   
/* get data from git_rev_list */ /* get data from git_rev_list */
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
$args[] = '--header'; $args[] = '--header';
$args[] = '--parents'; $args[] = '--parents';
$args[] = '--max-count=1'; $args[] = '--max-count=1';
$args[] = $this->hash; $args[] = $this->hash;
$ret = $exe->Execute(GIT_REV_LIST, $args); $ret = $exe->Execute(GIT_REV_LIST, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
if (!isset($lines[0])) if (!isset($lines[0]))
return; return;
   
/* In case we returned something unexpected */ /* In case we returned something unexpected */
$tok = strtok($lines[0], ' '); $tok = strtok($lines[0], ' ');
if ($tok != $this->hash) if ($tok != $this->hash)
return; return;
   
array_shift($lines); array_shift($lines);
   
} else { } else {
$data = $this->GetProject()->GetObject($this->hash); $data = $this->GetProject()->GetObject($this->hash);
if (empty($data)) if (empty($data))
return; return;
   
$lines = explode("\n", $data); $lines = explode("\n", $data);
   
} }
   
  $header = true;
   
foreach ($lines as $i => $line) { foreach ($lines as $i => $line) {
if (preg_match('/^tree ([0-9a-fA-F]{40})$/', $line, $regs)) { if ($header && preg_match('/^tree ([0-9a-fA-F]{40})$/', $line, $regs)) {
/* Tree */ /* Tree */
try { try {
$tree = $this->GetProject()->GetTree($regs[1]); $tree = $this->GetProject()->GetTree($regs[1]);
if ($tree) { if ($tree) {
$tree->SetCommit($this); $tree->SetCommit($this);
$this->tree = $tree; $this->tree = $tree;
} }
} catch (Exception $e) { } catch (Exception $e) {
} }
} else if (preg_match('/^parent ([0-9a-fA-F]{40})$/', $line, $regs)) { } else if ($header && preg_match('/^parent ([0-9a-fA-F]{40})$/', $line, $regs)) {
/* Parent */ /* Parent */
try { try {
$this->parents[] = $this->GetProject()->GetCommit($regs[1]); $this->parents[] = $this->GetProject()->GetCommit($regs[1]);
} catch (Exception $e) { } catch (Exception $e) {
} }
} else if (preg_match('/^author (.*) ([0-9]+) (.*)$/', $line, $regs)) { } else if ($header && preg_match('/^author (.*) ([0-9]+) (.*)$/', $line, $regs)) {
/* author data */ /* author data */
$this->author = $regs[1]; $this->author = $regs[1];
$this->authorEpoch = $regs[2]; $this->authorEpoch = $regs[2];
$this->authorTimezone = $regs[3]; $this->authorTimezone = $regs[3];
} else if (preg_match('/^committer (.*) ([0-9]+) (.*)$/', $line, $regs)) { } else if ($header && preg_match('/^committer (.*) ([0-9]+) (.*)$/', $line, $regs)) {
/* committer data */ /* committer data */
$this->committer = $regs[1]; $this->committer = $regs[1];
$this->committerEpoch = $regs[2]; $this->committerEpoch = $regs[2];
$this->committerTimezone = $regs[3]; $this->committerTimezone = $regs[3];
} else { } else {
/* commit comment */ /* commit comment */
  $header = false;
$trimmed = trim($line); $trimmed = trim($line);
if (empty($this->title) && (strlen($trimmed) > 0)) if (empty($this->title) && (strlen($trimmed) > 0))
$this->title = $trimmed; $this->title = $trimmed;
if (!empty($this->title)) { if (!empty($this->title)) {
if ((strlen($trimmed) > 0) || ($i < (count($lines)-1))) if ((strlen($trimmed) > 0) || ($i < (count($lines)-1)))
$this->comment[] = $trimmed; $this->comment[] = $trimmed;
} }
} }
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* GetHeads * GetHeads
* *
* Gets heads that point to this commit * Gets heads that point to this commit
* *
* @access public * @access public
* @return array array of heads * @return array array of heads
*/ */
public function GetHeads() public function GetHeads()
{ {
$heads = array(); $heads = array();
   
$projectRefs = $this->GetProject()->GetRefs('heads'); $projectRefs = $this->GetProject()->GetRefs('heads');
   
foreach ($projectRefs as $ref) { foreach ($projectRefs as $ref) {
if ($ref->GetHash() == $this->hash) { if ($ref->GetHash() == $this->hash) {
$heads[] = $ref; $heads[] = $ref;
} }
} }
   
return $heads; return $heads;
} }
   
/** /**
* GetTags * GetTags
* *
* Gets tags that point to this commit * Gets tags that point to this commit
* *
* @access public * @access public
* @return array array of tags * @return array array of tags
*/ */
public function GetTags() public function GetTags()
{ {
$tags = array(); $tags = array();
   
$projectRefs = $this->GetProject()->GetRefs('tags'); $projectRefs = $this->GetProject()->GetRefs('tags');
   
foreach ($projectRefs as $ref) { foreach ($projectRefs as $ref) {
if ($ref->GetCommit()->GetHash() === $this->hash) { if ($ref->GetCommit()->GetHash() === $this->hash) {
$tags[] = $ref; $tags[] = $ref;
} }
} }
   
return $tags; return $tags;
} }
   
/** /**
* GetContainingTag * GetContainingTag
* *
* Gets the tag that contains the changes in this commit * Gets the tag that contains the changes in this commit
* *
* @access public * @access public
* @return tag object * @return tag object
*/ */
public function GetContainingTag() public function GetContainingTag()
{ {
if (!$this->containingTagRead) if (!$this->containingTagRead)
$this->ReadContainingTag(); $this->ReadContainingTag();
   
return $this->containingTag; return $this->containingTag;
} }
   
/** /**
* ReadContainingTag * ReadContainingTag
* *
* Looks up the tag that contains the changes in this commit * Looks up the tag that contains the changes in this commit
* *
* @access private * @access private
*/ */
public function ReadContainingTag() public function ReadContainingTag()
{ {
$this->containingTagRead = true; $this->containingTagRead = true;
   
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
$args[] = '--tags'; $args[] = '--tags';
$args[] = $this->hash; $args[] = $this->hash;
$revs = explode("\n", $exe->Execute(GIT_NAME_REV, $args)); $revs = explode("\n", $exe->Execute(GIT_NAME_REV, $args));
   
foreach ($revs as $revline) { foreach ($revs as $revline) {
if (preg_match('/^([0-9a-fA-F]{40})\s+tags\/(.+)(\^[0-9]+|\~[0-9]+)$/', $revline, $regs)) { if (preg_match('/^([0-9a-fA-F]{40})\s+tags\/(.+)(\^[0-9]+|\~[0-9]+)$/', $revline, $regs)) {
if ($regs[1] == $this->hash) { if ($regs[1] == $this->hash) {
$this->containingTag = $this->GetProject()->GetTag($regs[2]); $this->containingTag = $this->GetProject()->GetTag($regs[2]);
break; break;
} }
} }
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* DiffToParent * DiffToParent
* *
* Diffs this commit with its immediate parent * Diffs this commit with its immediate parent
* *
* @access public * @access public
* @return mixed Tree diff * @return mixed Tree diff
*/ */
public function DiffToParent() public function DiffToParent()
{ {
return new GitPHP_TreeDiff($this->GetProject(), $this->hash); return new GitPHP_TreeDiff($this->GetProject(), $this->hash);
} }
   
/** /**
* PathToHash * PathToHash
* *
* Given a filepath, get its hash * Given a filepath, get its hash
* *
* @access public * @access public
* @param string $path path * @param string $path path
* @return string hash * @return string hash
*/ */
public function PathToHash($path) public function PathToHash($path)
{ {
if (empty($path)) if (empty($path))
return ''; return '';
   
if (!$this->hashPathsRead) if (!$this->hashPathsRead)
$this->ReadHashPaths(); $this->ReadHashPaths();
   
if (isset($this->blobPaths[$path])) { if (isset($this->blobPaths[$path])) {
return $this->blobPaths[$path]; return $this->blobPaths[$path];
} }
   
if (isset($this->treePaths[$path])) { if (isset($this->treePaths[$path])) {
return $this->treePaths[$path]; return $this->treePaths[$path];
} }
   
return ''; return '';
} }
   
/** /**
* ReadHashPaths * ReadHashPaths
* *
* Read hash to path mappings * Read hash to path mappings
* *
* @access private * @access private
*/ */
private function ReadHashPaths() private function ReadHashPaths()
{ {
$this->hashPathsRead = true; $this->hashPathsRead = true;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
$this->ReadHashPathsGit(); $this->ReadHashPathsGit();
} else { } else {
$this->ReadHashPathsRaw($this->GetTree()); $this->ReadHashPathsRaw($this->GetTree());
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* ReadHashPathsGit * ReadHashPathsGit
* *
* Reads hash to path mappings using git exe * Reads hash to path mappings using git exe
* *
* @access private * @access private
*/ */
private function ReadHashPathsGit() private function ReadHashPathsGit()
{ {
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
   
$args = array(); $args = array();
$args[] = '--full-name'; $args[] = '--full-name';
$args[] = '-r'; $args[] = '-r';
$args[] = '-t'; $args[] = '-t';
$args[] = $this->hash; $args[] = $this->hash;
   
$lines = explode("\n", $exe->Execute(GIT_LS_TREE, $args)); $lines = explode("\n", $exe->Execute(GIT_LS_TREE, $args));
   
foreach ($lines as $line) { foreach ($lines as $line) {
if (preg_match("/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/", $line, $regs)) { if (preg_match("/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/", $line, $regs)) {
switch ($regs[2]) { switch ($regs[2]) {
case 'tree': case 'tree':
$this->treePaths[trim($regs[4])] = $regs[3]; $this->treePaths[trim($regs[4])] = $regs[3];
break; break;
case 'blob'; case 'blob';
$this->blobPaths[trim($regs[4])] = $regs[3]; $this->blobPaths[trim($regs[4])] = $regs[3];
break; break;
} }
} }
} }
} }
   
/** /**
* ReadHashPathsRaw * ReadHashPathsRaw
* *
* Reads hash to path mappings using raw objects * Reads hash to path mappings using raw objects
* *
* @access private * @access private
*/ */
private function ReadHashPathsRaw($tree) private function ReadHashPathsRaw($tree)
{ {
if (!$tree) { if (!$tree) {
return; return;
} }
   
$contents = $tree->GetContents(); $contents = $tree->GetContents();
   
foreach ($contents as $obj) { foreach ($contents as $obj) {
if ($obj instanceof GitPHP_Blob) { if ($obj instanceof GitPHP_Blob) {
$hash = $obj->GetHash(); $hash = $obj->GetHash();
$path = $obj->GetPath(); $path = $obj->GetPath();
$this->blobPaths[trim($path)] = $hash; $this->blobPaths[trim($path)] = $hash;
} else if ($obj instanceof GitPHP_Tree) { } else if ($obj instanceof GitPHP_Tree) {
$hash = $obj->GetHash(); $hash = $obj->GetHash();
$path = $obj->GetPath(); $path = $obj->GetPath();
$this->treePaths[trim($path)] = $hash; $this->treePaths[trim($path)] = $hash;
$this->ReadHashPathsRaw($obj); $this->ReadHashPathsRaw($obj);
} }
} }
} }
   
/** /**
* SearchFilenames * SearchFilenames
* *
* Returns array of objects matching pattern * Returns array of objects matching pattern
* *
* @access public * @access public
* @param string $pattern pattern to find * @param string $pattern pattern to find
* @return array array of objects * @return array array of objects
*/ */
public function SearchFilenames($pattern) public function SearchFilenames($pattern)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
if (!$this->hashPathsRead) if (!$this->hashPathsRead)
$this->ReadHashPaths(); $this->ReadHashPaths();
   
$results = array(); $results = array();
   
foreach ($this->treePaths as $path => $hash) { foreach ($this->treePaths as $path => $hash) {
if (preg_match('/' . $pattern . '/i', $path)) { if (preg_match('/' . $pattern . '/i', $path)) {
$obj = $this->GetProject()->GetTree($hash); $obj = $this->GetProject()->GetTree($hash);
$obj->SetCommit($this); $obj->SetCommit($this);
$results[$path] = $obj; $results[$path] = $obj;
} }
} }
   
foreach ($this->blobPaths as $path => $hash) { foreach ($this->blobPaths as $path => $hash) {
if (preg_match('/' . $pattern . '/i', $path)) { if (preg_match('/' . $pattern . '/i', $path)) {
$obj = $this->GetProject()->GetBlob($hash); $obj = $this->GetProject()->GetBlob($hash);
$obj->SetCommit($this); $obj->SetCommit($this);
$results[$path] = $obj; $results[$path] = $obj;
} }
} }
   
ksort($results); ksort($results);
   
return $results; return $results;
} }
   
/** /**
* SearchFileContents * SearchFileContents
* *
* Searches for a pattern in file contents * Searches for a pattern in file contents
* *
* @access public * @access public
* @param string $pattern pattern to search for * @param string $pattern pattern to search for
* @return array multidimensional array of results * @return array multidimensional array of results
*/ */
public function SearchFileContents($pattern) public function SearchFileContents($pattern)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
   
$args = array(); $args = array();
$args[] = '-I'; $args[] = '-I';
$args[] = '--full-name'; $args[] = '--full-name';
$args[] = '--ignore-case'; $args[] = '--ignore-case';
$args[] = '-n'; $args[] = '-n';
$args[] = '-e'; $args[] = '-e';
$args[] = $pattern; $args[] = $pattern;
$args[] = $this->hash; $args[] = $this->hash;
   
$lines = explode("\n", $exe->Execute(GIT_GREP, $args)); $lines = explode("\n", $exe->Execute(GIT_GREP, $args));
   
$results = array(); $results = array();
   
foreach ($lines as $line) { foreach ($lines as $line) {
if (preg_match('/^[^:]+:([^:]+):([0-9]+):(.+)$/', $line, $regs)) { if (preg_match('/^[^:]+:([^:]+):([0-9]+):(.+)$/', $line, $regs)) {
if (!isset($results[$regs[1]]['object'])) { if (!isset($results[$regs[1]]['object'])) {
$hash = $this->PathToHash($regs[1]); $hash = $this->PathToHash($regs[1]);
if (!empty($hash)) { if (!empty($hash)) {
$obj = $this->GetProject()->GetBlob($hash); $obj = $this->GetProject()->GetBlob($hash);
$obj->SetCommit($this); $obj->SetCommit($this);
$results[$regs[1]]['object'] = $obj; $results[$regs[1]]['object'] = $obj;
} }
} }
$results[$regs[1]]['lines'][(int)($regs[2])] = $regs[3]; $results[$regs[1]]['lines'][(int)($regs[2])] = $regs[3];
} }
} }
   
return $results; return $results;
} }
   
/** /**
* SearchFiles * SearchFiles
* *
* Searches filenames and file contents for a pattern * Searches filenames and file contents for a pattern
* *
* @access public * @access public
* @param string $pattern pattern to search * @param string $pattern pattern to search
* @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 results * @return array array of results
*/ */
public function SearchFiles($pattern, $count = 100, $skip = 0) public function SearchFiles($pattern, $count = 100, $skip = 0)
{ {
if (empty($pattern)) if (empty($pattern))
return; return;
   
$grepresults = $this->SearchFileContents($pattern); $grepresults = $this->SearchFileContents($pattern);
   
$nameresults = $this->SearchFilenames($pattern); $nameresults = $this->SearchFilenames($pattern);
   
/* Merge the results together */ /* Merge the results together */
foreach ($nameresults as $path => $obj) { foreach ($nameresults as $path => $obj) {
if (!isset($grepresults[$path]['object'])) { if (!isset($grepresults[$path]['object'])) {
$grepresults[$path]['object'] = $obj; $grepresults[$path]['object'] = $obj;
} }
} }
   
ksort($grepresults); ksort($grepresults);
   
return array_slice($grepresults, $skip, $count, true); return array_slice($grepresults, $skip, $count, true);
} }
   
/** /**
* ReferenceParents * ReferenceParents
* *
* Turns the list of parents into reference pointers * Turns the list of parents into reference pointers
* *
* @access private * @access private
*/ */
private function ReferenceParents() private function ReferenceParents()
{ {
if ($this->parentsReferenced) if ($this->parentsReferenced)
return; return;
   
if ((!isset($this->parents)) || (count($this->parents) < 1)) if ((!isset($this->parents)) || (count($this->parents) < 1))
return; return;
   
for ($i = 0; $i < count($this->parents); $i++) { for ($i = 0; $i < count($this->parents); $i++) {
$this->parents[$i] = $this->parents[$i]->GetHash(); $this->parents[$i] = $this->parents[$i]->GetHash();
} }
   
$this->parentsReferenced = true; $this->parentsReferenced = true;
} }
   
/** /**
* DereferenceParents * DereferenceParents
* *
* Turns the list of parent pointers back into objects * Turns the list of parent pointers back into objects
* *
* @access private * @access private
*/ */
private function DereferenceParents() private function DereferenceParents()
{ {
if (!$this->parentsReferenced) if (!$this->parentsReferenced)
return; return;
   
if ((!$this->parents) || (count($this->parents) < 1)) if ((!$this->parents) || (count($this->parents) < 1))
return; return;
   
for ($i = 0; $i < count($this->parents); $i++) { for ($i = 0; $i < count($this->parents); $i++) {
$this->parents[$i] = $this->GetProject()->GetCommit($this->parents[$i]); $this->parents[$i] = $this->GetProject()->GetCommit($this->parents[$i]);
} }
   
$this->parentsReferenced = false; $this->parentsReferenced = false;
} }
   
/** /**
* ReferenceTree * ReferenceTree
* *
* Turns the tree into a reference pointer * Turns the tree into a reference pointer
* *
* @access private * @access private
*/ */
private function ReferenceTree() private function ReferenceTree()
{ {
if ($this->treeReferenced) if ($this->treeReferenced)
return; return;
   
if (!$this->tree) if (!$this->tree)
return; return;
   
$this->tree = $this->tree->GetHash(); $this->tree = $this->tree->GetHash();
   
$this->treeReferenced = true; $this->treeReferenced = true;
} }
   
/** /**
* DereferenceTree * DereferenceTree
* *
* Turns the tree pointer back into an object * Turns the tree pointer back into an object
* *
* @access private * @access private
*/ */
private function DereferenceTree() private function DereferenceTree()
{ {
if (!$this->treeReferenced) if (!$this->treeReferenced)
return; return;
   
if (empty($this->tree)) if (empty($this->tree))
return; return;
   
$this->tree = $this->GetProject()->GetTree($this->tree); $this->tree = $this->GetProject()->GetTree($this->tree);
   
if ($this->tree) if ($this->tree)
$this->tree->SetCommit($this); $this->tree->SetCommit($this);
   
$this->treeReferenced = false; $this->treeReferenced = false;
} }
   
/** /**
* __sleep * __sleep
* *
* Called to prepare the object for serialization * Called to prepare the object for serialization
* *
* @access public * @access public
* @return array list of properties to serialize * @return array list of properties to serialize
*/ */
public function __sleep() public function __sleep()
{ {
if (!$this->parentsReferenced) if (!$this->parentsReferenced)
$this->ReferenceParents(); $this->ReferenceParents();
   
if (!$this->treeReferenced) if (!$this->treeReferenced)
$this->ReferenceTree(); $this->ReferenceTree();
   
$properties = array('dataRead', 'parents', 'tree', 'author', 'authorEpoch', 'authorTimezone', 'committer', 'committerEpoch', 'committerTimezone', 'title', 'comment', 'readTree', 'blobPaths', 'treePaths', 'hashPathsRead', 'parentsReferenced', 'treeReferenced'); $properties = array('dataRead', 'parents', 'tree', 'author', 'authorEpoch', 'authorTimezone', 'committer', 'committerEpoch', 'committerTimezone', 'title', 'comment', 'readTree', 'blobPaths', 'treePaths', 'hashPathsRead', 'parentsReferenced', 'treeReferenced');
return array_merge($properties, parent::__sleep()); return array_merge($properties, parent::__sleep());
} }
   
/** /**
* GetCacheKey * GetCacheKey
* *
* Gets the cache key to use for this object * Gets the cache key to use for this object
* *
* @access public * @access public
* @return string cache key * @return string cache key
*/ */
public function GetCacheKey() public function GetCacheKey()
{ {
$key = parent::GetCacheKey(); $key = parent::GetCacheKey();
if (!empty($key)) if (!empty($key))
$key .= '|'; $key .= '|';
   
$key .= 'commit|' . $this->hash; $key .= 'commit|' . $this->hash;
   
return $key; return $key;
} }
   
/** /**
* CompareAge * CompareAge
* *
* Compares two commits by age * Compares two commits by age
* *
* @access public * @access public
* @static * @static
* @param mixed $a first commit * @param mixed $a first commit
* @param mixed $b second commit * @param mixed $b second commit
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareAge($a, $b) public static function CompareAge($a, $b)
{ {
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);
} }
   
} }
   
<?php <?php
/** /**
* GitPHP File Diff * GitPHP File Diff
* *
* Represents a single file difference * Represents a single file difference
* *
* @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 . 'Blob.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Blob.class.php');
require_once(GITPHP_GITOBJECTDIR . 'TmpDir.class.php'); require_once(GITPHP_GITOBJECTDIR . 'TmpDir.class.php');
require_once(GITPHP_GITOBJECTDIR . 'DiffExe.class.php'); require_once(GITPHP_GITOBJECTDIR . 'DiffExe.class.php');
   
/** /**
* Commit class * Commit class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_FileDiff class GitPHP_FileDiff
{ {
/** /**
* diffInfoRead * diffInfoRead
* *
* Stores whether diff info has been read * Stores whether diff info has been read
* *
* @access protected * @access protected
*/ */
protected $diffInfoRead = false; protected $diffInfoRead = false;
   
/** /**
* diffDataRead * diffDataRead
* *
* Stores whether diff data has been read * Stores whether diff data has been read
* *
* @access protected * @access protected
*/ */
protected $diffDataRead = false; protected $diffDataRead = false;
   
/** /**
* diffData * diffData
* *
* Stores the diff data * Stores the diff data
* *
* @access protected * @access protected
*/ */
protected $diffData; protected $diffData;
   
/** /**
* diffDataSplitRead * diffDataSplitRead
* *
* Stores whether split diff data has been read * Stores whether split diff data has been read
* *
* @access protected * @access protected
*/ */
protected $diffDataSplitRead = false; protected $diffDataSplitRead = false;
   
/** /**
* diffDataSplit * diffDataSplit
* *
* Stores the diff data split up by left/right changes * Stores the diff data split up by left/right changes
* *
* @access protected * @access protected
*/ */
protected $diffDataSplit; protected $diffDataSplit;
   
/** /**
* diffDataName * diffDataName
* *
* Filename used on last data diff * Filename used on last data diff
* *
* @access protected * @access protected
*/ */
protected $diffDataName; protected $diffDataName;
   
/** /**
* fromMode * fromMode
* *
* Stores the from file mode * Stores the from file mode
* *
* @access protected * @access protected
*/ */
protected $fromMode; protected $fromMode;
   
/** /**
* toMode * toMode
* *
* Stores the to file mode * Stores the to file mode
* *
* @access protected * @access protected
*/ */
protected $toMode; protected $toMode;
   
/** /**
* fromHash * fromHash
* *
* Stores the from hash * Stores the from hash
* *
* @access protected * @access protected
*/ */
protected $fromHash; protected $fromHash;
   
/** /**
* toHash * toHash
* *
* Stores the to hash * Stores the to hash
* *
* @access protected * @access protected
*/ */
protected $toHash; protected $toHash;
   
/** /**
* status * status
* *
* Stores the status * Stores the status
* *
* @access protected * @access protected
*/ */
protected $status; protected $status;
   
/** /**
* similarity * similarity
* *
* Stores the similarity * Stores the similarity
* *
* @access protected * @access protected
*/ */
protected $similarity; protected $similarity;
   
/** /**
* fromFile * fromFile
* *
* Stores the from filename * Stores the from filename
* *
* @access protected * @access protected
*/ */
protected $fromFile; protected $fromFile;
   
/** /**
* toFile * toFile
* *
* Stores the to filename * Stores the to filename
* *
* @access protected * @access protected
*/ */
protected $toFile; protected $toFile;
   
/** /**
* fromFileType * fromFileType
* *
* Stores the from file type * Stores the from file type
* *
* @access protected * @access protected
*/ */
protected $fromFileType; protected $fromFileType;
   
/** /**
* toFileType * toFileType
* *
* Stores the to file type * Stores the to file type
* *
* @access protected * @access protected
*/ */
protected $toFileType; protected $toFileType;
   
/** /**
* project * project
* *
* Stores the project * Stores the project
* *
* @access protected * @access protected
*/ */
protected $project; protected $project;
   
/** /**
* commit * commit
* *
* Stores the commit that caused this filediff * Stores the commit that caused this filediff
* *
* @access protected * @access protected
*/ */
protected $commit; protected $commit;
   
/** /**
* __construct * __construct
* *
* Constructor * Constructor
* *
* @access public * @access public
* @param mixed $project project * @param mixed $project project
* @param string $fromHash source hash, can also be a diff-tree info line * @param string $fromHash source hash, can also be a diff-tree info line
* @param string $toHash target hash, required if $fromHash is a hash * @param string $toHash target hash, required if $fromHash is a hash
* @return mixed FileDiff object * @return mixed FileDiff object
* @throws Exception on invalid parameters * @throws Exception on invalid parameters
*/ */
public function __construct($project, $fromHash, $toHash = '') public function __construct($project, $fromHash, $toHash = '')
{ {
$this->project = $project; $this->project = $project;
   
if ($this->ParseDiffTreeLine($fromHash)) if ($this->ParseDiffTreeLine($fromHash))
return; return;
   
if (!(preg_match('/^[0-9a-fA-F]{40}$/', $fromHash) && preg_match('/^[0-9a-fA-F]{40}$/', $toHash))) { if (!(preg_match('/^[0-9a-fA-F]{40}$/', $fromHash) && preg_match('/^[0-9a-fA-F]{40}$/', $toHash))) {
throw new Exception('Invalid parameters for FileDiff'); throw new Exception('Invalid parameters for FileDiff');
} }
   
$this->fromHash = $fromHash; $this->fromHash = $fromHash;
$this->toHash = $toHash; $this->toHash = $toHash;
} }
   
/** /**
* ParseDiffTreeLine * ParseDiffTreeLine
* *
* @access private * @access private
* @param string $diffTreeLine line from difftree * @param string $diffTreeLine line from difftree
* @return boolean true if data was read from line * @return boolean true if data was read from line
*/ */
private function ParseDiffTreeLine($diffTreeLine) private function ParseDiffTreeLine($diffTreeLine)
{ {
if (preg_match('/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/', $diffTreeLine, $regs)) { if (preg_match('/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/', $diffTreeLine, $regs)) {
$this->diffInfoRead = true; $this->diffInfoRead = true;
   
$this->fromMode = $regs[1]; $this->fromMode = $regs[1];
$this->toMode = $regs[2]; $this->toMode = $regs[2];
$this->fromHash = $regs[3]; $this->fromHash = $regs[3];
$this->toHash = $regs[4]; $this->toHash = $regs[4];
$this->status = $regs[5]; $this->status = $regs[5];
$this->similarity = ltrim($regs[6], '0'); $this->similarity = ltrim($regs[6], '0');
$this->fromFile = strtok($regs[7], "\t"); $this->fromFile = strtok($regs[7], "\t");
$this->toFile = strtok("\t"); $this->toFile = strtok("\t");
if ($this->toFile === false) { if ($this->toFile === false) {
/* no filename change */ /* no filename change */
$this->toFile = $this->fromFile; $this->toFile = $this->fromFile;
} }
   
return true; return true;
} }
   
return false; return false;
} }
   
/** /**
* ReadDiffInfo * ReadDiffInfo
* *
* Reads file diff info * Reads file diff info
* *
* @access protected * @access protected
*/ */
protected function ReadDiffInfo() protected function ReadDiffInfo()
{ {
$this->diffInfoRead = true; $this->diffInfoRead = true;
   
/* TODO: read a single difftree line on-demand */ /* TODO: read a single difftree line on-demand */
} }
   
/** /**
* GetFromMode * GetFromMode
* *
* Gets the from file mode * Gets the from file mode
* (full a/u/g/o) * (full a/u/g/o)
* *
* @access public * @access public
* @return string from file mode * @return string from file mode
*/ */
public function GetFromMode() public function GetFromMode()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return $this->fromMode; return $this->fromMode;
} }
   
/** /**
* GetFromModeShort * GetFromModeShort
* *
* Gets the from file mode in short form * Gets the from file mode in short form
* (standard u/g/o) * (standard u/g/o)
* *
* @access public * @access public
* @return string short from file mode * @return string short from file mode
*/ */
public function GetFromModeShort() public function GetFromModeShort()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return substr($this->fromMode, -4); return substr($this->fromMode, -4);
} }
   
/** /**
* GetToMode * GetToMode
* *
* Gets the to file mode * Gets the to file mode
* (full a/u/g/o) * (full a/u/g/o)
* *
* @access public * @access public
* @return string to file mode * @return string to file mode
*/ */
public function GetToMode() public function GetToMode()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return $this->toMode; return $this->toMode;
} }
   
/** /**
* GetToModeShort * GetToModeShort
* *
* Gets the to file mode in short form * Gets the to file mode in short form
* (standard u/g/o) * (standard u/g/o)
* *
* @access public * @access public
* @return string short to file mode * @return string short to file mode
*/ */
public function GetToModeShort() public function GetToModeShort()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return substr($this->toMode, -4); return substr($this->toMode, -4);
} }
   
/** /**
* GetFromHash * GetFromHash
* *
* Gets the from hash * Gets the from hash
* *
* @access public * @access public
* @return string from hash * @return string from hash
*/ */
public function GetFromHash() public function GetFromHash()
{ {
return $this->fromHash; return $this->fromHash;
} }
   
/** /**
* GetToHash * GetToHash
* *
* Gets the to hash * Gets the to hash
* *
* @access public * @access public
* @return string to hash * @return string to hash
*/ */
public function GetToHash() public function GetToHash()
{ {
return $this->toHash; return $this->toHash;
} }
   
/** /**
* GetFromBlob * GetFromBlob
* *
* Gets the from file blob * Gets the from file blob
* *
* @access public * @access public
* @return mixed blob object * @return mixed blob object
*/ */
public function GetFromBlob() public function GetFromBlob()
{ {
if (empty($this->fromHash)) if (empty($this->fromHash))
return null; return null;
   
return $this->project->GetBlob($this->fromHash); return $this->project->GetBlob($this->fromHash);
} }
   
/** /**
* GetToBlob * GetToBlob
* *
* Gets the to file blob * Gets the to file blob
* *
* @access public * @access public
* @return mixed blob object * @return mixed blob object
*/ */
public function GetToBlob() public function GetToBlob()
{ {
if (empty($this->toHash)) if (empty($this->toHash))
return null; return null;
   
return $this->project->GetBlob($this->toHash); return $this->project->GetBlob($this->toHash);
} }
   
/** /**
* GetStatus * GetStatus
* *
* Gets the status of the change * Gets the status of the change
* *
* @access public * @access public
* @return string status * @return string status
*/ */
public function GetStatus() public function GetStatus()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return $this->status; return $this->status;
} }
   
/** /**
* GetSimilarity * GetSimilarity
* *
* Gets the similarity * Gets the similarity
* *
* @access public * @access public
* @return string similarity * @return string similarity
*/ */
public function GetSimilarity() public function GetSimilarity()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return $this->similarity; return $this->similarity;
} }
   
/** /**
* GetFromFile * GetFromFile
* *
* Gets the from file name * Gets the from file name
* *
* @access public * @access public
* @return string from file * @return string from file
*/ */
public function GetFromFile() public function GetFromFile()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return $this->fromFile; return $this->fromFile;
} }
   
/** /**
* GetToFile * GetToFile
* *
* Gets the to file name * Gets the to file name
* *
* @access public * @access public
* @return string to file * @return string to file
*/ */
public function GetToFile() public function GetToFile()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return $this->toFile; return $this->toFile;
} }
   
/** /**
* GetFromFileType * GetFromFileType
* *
* Gets the from file type * Gets the from file type
* *
* @access public * @access public
* @param boolean $local true if caller wants localized type * @param boolean $local true if caller wants localized type
* @return string from file type * @return string from file type
*/ */
public function GetFromFileType($local = false) public function GetFromFileType($local = false)
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return GitPHP_Blob::FileType($this->fromMode, $local); return GitPHP_Blob::FileType($this->fromMode, $local);
} }
   
/** /**
* GetToFileType * GetToFileType
* *
* Gets the to file type * Gets the to file type
* *
* @access public * @access public
* @param boolean $local true if caller wants localized type * @param boolean $local true if caller wants localized type
* @return string to file type * @return string to file type
*/ */
public function GetToFileType($local = false) public function GetToFileType($local = false)
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return GitPHP_Blob::FileType($this->toMode, $local); return GitPHP_Blob::FileType($this->toMode, $local);
} }
   
/** /**
* FileTypeChanged * FileTypeChanged
* *
* Tests if filetype changed * Tests if filetype changed
* *
* @access public * @access public
* @return boolean true if file type changed * @return boolean true if file type changed
*/ */
public function FileTypeChanged() public function FileTypeChanged()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return (octdec($this->fromMode) & 0x17000) != (octdec($this->toMode) & 0x17000); return (octdec($this->fromMode) & 0x17000) != (octdec($this->toMode) & 0x17000);
} }
   
/** /**
* FileModeChanged * FileModeChanged
* *
* Tests if file mode changed * Tests if file mode changed
* *
* @access public * @access public
* @return boolean true if file mode changed * @return boolean true if file mode changed
*/ */
public function FileModeChanged() public function FileModeChanged()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return (octdec($this->fromMode) & 0777) != (octdec($this->toMode) & 0777); return (octdec($this->fromMode) & 0777) != (octdec($this->toMode) & 0777);
} }
   
/** /**
* FromFileIsRegular * FromFileIsRegular
* *
* Tests if the from file is a regular file * Tests if the from file is a regular file
* *
* @access public * @access public
* @return boolean true if from file is regular * @return boolean true if from file is regular
*/ */
public function FromFileIsRegular() public function FromFileIsRegular()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return (octdec($this->fromMode) & 0x8000) == 0x8000; return (octdec($this->fromMode) & 0x8000) == 0x8000;
} }
   
/** /**
* ToFileIsRegular * ToFileIsRegular
* *
* Tests if the to file is a regular file * Tests if the to file is a regular file
* *
* @access public * @access public
* @return boolean true if to file is regular * @return boolean true if to file is regular
*/ */
public function ToFileIsRegular() public function ToFileIsRegular()
{ {
if (!$this->diffInfoRead) if (!$this->diffInfoRead)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
return (octdec($this->toMode) & 0x8000) == 0x8000; return (octdec($this->toMode) & 0x8000) == 0x8000;
} }
   
/** /**
* GetDiff * GetDiff
* *
* Gets the diff output * Gets the diff output
* *
* @access public * @access public
* @param string $file override the filename on the diff * @param string $file override the filename on the diff
* @return string diff output * @return string diff output
*/ */
public function GetDiff($file = '', $readFileData = true, $explode = false) public function GetDiff($file = '', $readFileData = true, $explode = false)
{ {
if ($this->diffDataRead && ($file == $this->diffDataName)) { if ($this->diffDataRead && ($file == $this->diffDataName)) {
if ($explode) if ($explode)
return explode("\n", $this->diffData); return explode("\n", $this->diffData);
else else
return $this->diffData; return $this->diffData;
} }
   
if ((!$this->diffInfoRead) && $readFileData) if ((!$this->diffInfoRead) && $readFileData)
$this->ReadDiffInfo(); $this->ReadDiffInfo();
   
$this->diffDataName = $file; $this->diffDataName = $file;
$this->diffDataRead = true; $this->diffDataRead = true;
   
if ((!empty($this->status)) && ($this->status != 'A') && ($this->status != 'D') && ($this->status != 'M')) { if ((!empty($this->status)) && ($this->status != 'A') && ($this->status != 'D') && ($this->status != 'M')) {
$this->diffData = ''; $this->diffData = '';
return; return;
} }
   
$tmpdir = GitPHP_TmpDir::GetInstance(); if (function_exists('xdiff_string_diff')) {
   
$pid = 0; $this->diffData = $this->GetXDiff(3, true, $file);
if (function_exists('posix_getpid'))  
$pid = posix_getpid(); } else {
else  
$pid = rand(); $tmpdir = GitPHP_TmpDir::GetInstance();
   
$fromTmpFile = null; $pid = 0;
$toTmpFile = null; if (function_exists('posix_getpid'))
  $pid = posix_getpid();
$fromName = null; else
$toName = null; $pid = rand();
   
if ((empty($this->status)) || ($this->status == 'D') || ($this->status == 'M')) { $fromTmpFile = null;
$fromBlob = $this->GetFromBlob(); $toTmpFile = null;
$fromTmpFile = 'gitphp_' . $pid . '_from';  
$tmpdir->AddFile($fromTmpFile, $fromBlob->GetData()); $fromName = null;
  $toName = null;
$fromName = 'a/';  
if (!empty($file)) { if ((empty($this->status)) || ($this->status == 'D') || ($this->status == 'M')) {
$fromName .= $file; $fromBlob = $this->GetFromBlob();
} else if (!empty($this->fromFile)) { $fromTmpFile = 'gitphp_' . $pid . '_from';
$fromName .= $this->fromFile; $tmpdir->AddFile($fromTmpFile, $fromBlob->GetData());
} else {  
$fromName .= $this->fromHash; $fromName = 'a/';
} if (!empty($file)) {
} $fromName .= $file;
  } else if (!empty($this->fromFile)) {
if ((empty($this->status)) || ($this->status == 'A') || ($this->status == 'M')) { $fromName .= $this->fromFile;
$toBlob = $this->GetToBlob(); } else {
$toTmpFile = 'gitphp_' . $pid . '_to'; $fromName .= $this->fromHash;
$tmpdir->AddFile($toTmpFile, $toBlob->GetData()); }
  }
$toName = 'b/';  
if (!empty($file)) { if ((empty($this->status)) || ($this->status == 'A') || ($this->status == 'M')) {
$toName .= $file; $toBlob = $this->GetToBlob();
} else if (!empty($this->toFile)) { $toTmpFile = 'gitphp_' . $pid . '_to';
$toName .= $this->toFile; $tmpdir->AddFile($toTmpFile, $toBlob->GetData());
} else {  
$toName .= $this->toHash; $toName = 'b/';
} if (!empty($file)) {
} $toName .= $file;
  } else if (!empty($this->toFile)) {
$this->diffData = GitPHP_DiffExe::Diff((empty($fromTmpFile) ? null : escapeshellarg($tmpdir->GetDir() . $fromTmpFile)), $fromName, (empty($toTmpFile) ? null : escapeshellarg($tmpdir->GetDir() . $toTmpFile)), $toName); $toName .= $this->toFile;
  } else {
if (!empty($fromTmpFile)) { $toName .= $this->toHash;
$tmpdir->RemoveFile($fromTmpFile); }
} }
   
if (!empty($toTmpFile)) { $this->diffData = GitPHP_DiffExe::Diff((empty($fromTmpFile) ? null : escapeshellarg($tmpdir->GetDir() . $fromTmpFile)), $fromName, (empty($toTmpFile) ? null : escapeshellarg($tmpdir->GetDir() . $toTmpFile)), $toName);
$tmpdir->RemoveFile($toTmpFile);  
  if (!empty($fromTmpFile)) {
  $tmpdir->RemoveFile($fromTmpFile);
  }
   
  if (!empty($toTmpFile)) {
  $tmpdir->RemoveFile($toTmpFile);
  }
   
} }
   
if ($explode) if ($explode)
return explode("\n", $this->diffData); return explode("\n", $this->diffData);
else else
return $this->diffData; return $this->diffData;
} }
   
/** /**
* GetDiffSplit * GetDiffSplit
* *
* construct the side by side diff data from the git data * construct the side by side diff data from the git data
* The result is an array of ternary arrays with 3 elements each: * The result is an array of ternary arrays with 3 elements each:
* First the mode ("" or "-added" or "-deleted" or "-modified"), * First the mode ("" or "-added" or "-deleted" or "-modified"),
* then the first column, then the second. * then the first column, then the second.
* *
* @author Mattias Ulbrich * @author Mattias Ulbrich
* *
* @access public * @access public
* @return an array of line elements (see above) * @return an array of line elements (see above)
*/ */
public function GetDiffSplit() public function GetDiffSplit()
{ {
if ($this->diffDataSplitRead) { if ($this->diffDataSplitRead) {
return $this->diffDataSplit; return $this->diffDataSplit;
} }
   
$this->diffDataSplitRead = true; $this->diffDataSplitRead = true;
   
$exe = new GitPHP_GitExe($this->project); $exe = new GitPHP_GitExe($this->project);
   
$fromBlob = $this->GetFromBlob(); $fromBlob = $this->GetFromBlob();
$blob = $fromBlob->GetData(true); $blob = $fromBlob->GetData(true);
   
$diffLines = explode("\n", $exe->Execute(GIT_DIFF, $diffLines = '';
array("-U0", $this->fromHash, if (function_exists('xdiff_string_diff')) {
$this->toHash))); $diffLines = explode("\n", $this->GetXDiff(0, false));
  } else {
  $diffLines = explode("\n", $exe->Execute(GIT_DIFF,
  array("-U0", $this->fromHash,
  $this->toHash)));
  }
   
unset($exe); unset($exe);
   
// //
// parse diffs // parse diffs
$diffs = array(); $diffs = array();
$currentDiff = FALSE; $currentDiff = FALSE;
foreach($diffLines as $d) { foreach($diffLines as $d) {
if(strlen($d) == 0) if(strlen($d) == 0)
continue; continue;
switch($d[0]) { switch($d[0]) {
case '@': case '@':
if($currentDiff) { if($currentDiff) {
if (count($currentDiff['left']) == 0 && count($currentDiff['right']) > 0) if (count($currentDiff['left']) == 0 && count($currentDiff['right']) > 0)
$currentDiff['line']++; // HACK to make added blocks align correctly $currentDiff['line']++; // HACK to make added blocks align correctly
$diffs[] = $currentDiff; $diffs[] = $currentDiff;
} }
$comma = strpos($d, ","); $comma = strpos($d, ",");
$line = -intval(substr($d, 2, $comma-2)); $line = -intval(substr($d, 2, $comma-2));
$currentDiff = array("line" => $line, $currentDiff = array("line" => $line,
"left" => array(), "right" => array()); "left" => array(), "right" => array());
break; break;
case '+': case '+':
if($currentDiff) if($currentDiff)
$currentDiff["right"][] = substr($d, 1); $currentDiff["right"][] = substr($d, 1);
break; break;
case '-': case '-':
if($currentDiff) if($currentDiff)
$currentDiff["left"][] = substr($d, 1); $currentDiff["left"][] = substr($d, 1);
break; break;
case ' ': case ' ':
echo "should not happen!"; echo "should not happen!";
if($currentDiff) { if($currentDiff) {
$currentDiff["left"][] = substr($d, 1); $currentDiff["left"][] = substr($d, 1);
$currentDiff["right"][] = substr($d, 1); $currentDiff["right"][] = substr($d, 1);
} }
break; break;
} }
} }
if($currentDiff) { if($currentDiff) {
if (count($currentDiff['left']) == 0 && count($currentDiff['right']) > 0) if (count($currentDiff['left']) == 0 && count($currentDiff['right']) > 0)
$currentDiff['line']++; // HACK to make added blocks align correctly $currentDiff['line']++; // HACK to make added blocks align correctly
$diffs[] = $currentDiff; $diffs[] = $currentDiff;
} }
   
// //
// iterate over diffs // iterate over diffs
$output = array(); $output = array();
$idx = 0; $idx = 0;
foreach($diffs as $d) { foreach($diffs as $d) {
while($idx+1 < $d['line']) { while($idx+1 < $d['line']) {
$h = $blob[$idx]; $h = $blob[$idx];
$output[] = array('', $h, $h); $output[] = array('', $h, $h);
$idx ++; $idx ++;
} }
   
if(count($d['left']) == 0) { if(count($d['left']) == 0) {
$mode = 'added'; $mode = 'added';
} elseif(count($d['right']) == 0) { } elseif(count($d['right']) == 0) {
$mode = 'deleted'; $mode = 'deleted';
} else { } else {
$mode = 'modified'; $mode = 'modified';
} }
   
for($i = 0; $i < count($d['left']) || $i < count($d['right']); $i++) { for($i = 0; $i < count($d['left']) || $i < count($d['right']); $i++) {
$left = $i < count($d['left']) ? $d['left'][$i] : FALSE; $left = $i < count($d['left']) ? $d['left'][$i] : FALSE;
$right = $i < count($d['right']) ? $d['right'][$i] : FALSE; $right = $i < count($d['right']) ? $d['right'][$i] : FALSE;
$output[] = array($mode, $left, $right); $output[] = array($mode, $left, $right);
} }
   
$idx += count($d['left']); $idx += count($d['left']);
} }
   
while($idx < count($blob)) { while($idx < count($blob)) {
$h = $blob[$idx]; $h = $blob[$idx];
$output[] = array('', $h, $h); $output[] = array('', $h, $h);
$idx ++; $idx ++;
} }
   
$this->diffDataSplit = $output; $this->diffDataSplit = $output;
return $output; return $output;
} }
   
/** /**
  * GetXDiff
  *
  * Get diff using xdiff
  *
  * @access private
  * @param int $context number of context lines
  * @param boolean $header true to include standard diff header
  * @param string $file override the file name
  * @return string diff content
  */
  private function GetXDiff($context = 3, $header = true, $file = null)
  {
  if (!function_exists('xdiff_string_diff'))
  return '';
   
  $fromData = '';
  $toData = '';
  $isBinary = false;
  $fromName = '/dev/null';
  $toName = '/dev/null';
  if (empty($this->status) || ($this->status == 'M') || ($this->status == 'D')) {
  $fromBlob = $this->GetFromBlob();
  $isBinary = $isBinary || $fromBlob->IsBinary();
  $fromData = $fromBlob->GetData(false);
  $fromName = 'a/';
  if (!empty($file)) {
  $fromName .= $file;
  } else if (!empty($this->fromFile)) {
  $fromName .= $this->fromFile;
  } else {
  $fromName .= $this->fromHash;
  }
  }
  if (empty($this->status) || ($this->status == 'M') || ($this->status == 'A')) {
  $toBlob = $this->GetToBlob();
  $isBinary = $isBinary || $toBlob->IsBinary();
  $toData = $toBlob->GetData(false);
  $toName = 'b/';
  if (!empty($file)) {
  $toName .= $file;
  } else if (!empty($this->toFile)) {
  $toName .= $this->toFile;
  } else {
  $toName .= $this->toHash;
  }
  }
  $output = '';
  if ($isBinary) {
  $output = sprintf(__('Binary files %1$s and %2$s differ'), $fromName, $toName) . "\n";
  } else {
  if ($header) {
  $output = '--- ' . $fromName . "\n" . '+++ ' . $toName . "\n";
  }
  $output .= xdiff_string_diff($fromData, $toData, $context);
  }
  return $output;
  }
   
  /**
* GetCommit * GetCommit
* *
* Gets the commit for this filediff * Gets the commit for this filediff
* *
* @access public * @access public
* @return commit object * @return commit object
*/ */
public function GetCommit() public function GetCommit()
{ {
return $this->commit; return $this->commit;
} }
   
/** /**
* SetCommit * SetCommit
* *
* Sets the commit for this filediff * Sets the commit for this filediff
* *
* @access public * @access public
* @param mixed $commit commit object * @param mixed $commit commit object
*/ */
public function SetCommit($commit) public function SetCommit($commit)
{ {
$this->commit = $commit; $this->commit = $commit;
} }
} }
   
<?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');
  }
} }
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 > 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;
  }
 
  }
 
<?php <?php
/** /**
* GitPHP Tag * GitPHP Tag
* *
* Represents a single tag object * Represents a single tag object
* *
* @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 . 'Ref.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Ref.class.php');
   
/** /**
* Tag class * Tag class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Tag extends GitPHP_Ref class GitPHP_Tag extends GitPHP_Ref
{ {
/** /**
* dataRead * dataRead
* *
* Indicates whether data for this tag has been read * Indicates whether data for this tag has been read
* *
* @access protected * @access protected
*/ */
protected $dataRead = false; protected $dataRead = false;
   
/** /**
* object * object
* *
* Stores the object internally * Stores the object internally
* *
* @access protected * @access protected
*/ */
protected $object; protected $object;
   
/** /**
* commit * commit
* *
* Stores the commit internally * Stores the commit internally
* *
* @access protected * @access protected
*/ */
protected $commit; protected $commit;
   
/** /**
* type * type
* *
* Stores the type internally * Stores the type internally
* *
* @access protected * @access protected
*/ */
protected $type; protected $type;
   
/** /**
* tagger * tagger
* *
* Stores the tagger internally * Stores the tagger internally
* *
* @access protected * @access protected
*/ */
protected $tagger; protected $tagger;
   
/** /**
* taggerEpoch * taggerEpoch
* *
* Stores the tagger epoch internally * Stores the tagger epoch internally
* *
* @access protected * @access protected
*/ */
protected $taggerEpoch; protected $taggerEpoch;
   
/** /**
* taggerTimezone * taggerTimezone
* *
* Stores the tagger timezone internally * Stores the tagger timezone internally
* *
* @access protected * @access protected
*/ */
protected $taggerTimezone; protected $taggerTimezone;
   
/** /**
* comment * comment
* *
* Stores the tag comment internally * Stores the tag comment internally
* *
* @access protected * @access protected
*/ */
protected $comment = array(); protected $comment = array();
   
/** /**
* objectReferenced * objectReferenced
* *
* Stores whether the object has been referenced into a pointer * Stores whether the object has been referenced into a pointer
* *
* @access private * @access private
*/ */
private $objectReferenced = false; private $objectReferenced = false;
   
/** /**
* commitReferenced * commitReferenced
* *
* Stores whether the commit has been referenced into a pointer * Stores whether the commit has been referenced into a pointer
* *
* @access private * @access private
*/ */
private $commitReferenced = false; private $commitReferenced = false;
   
/** /**
* __construct * __construct
* *
* Instantiates tag * Instantiates tag
* *
* @access public * @access public
* @param mixed $project the project * @param mixed $project the project
* @param string $tag tag name * @param string $tag tag name
* @param string $tagHash tag hash * @param string $tagHash tag hash
* @return mixed tag object * @return mixed tag object
* @throws Exception exception on invalid tag or hash * @throws Exception exception on invalid tag or hash
*/ */
public function __construct($project, $tag, $tagHash = '') public function __construct($project, $tag, $tagHash = '')
{ {
parent::__construct($project, 'tags', $tag, $tagHash); parent::__construct($project, 'tags', $tag, $tagHash);
} }
   
/** /**
* GetObject * GetObject
* *
* Gets the object this tag points to * Gets the object this tag points to
* *
* @access public * @access public
* @return mixed object for this tag * @return mixed object for this tag
*/ */
public function GetObject() public function GetObject()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->objectReferenced) if ($this->objectReferenced)
$this->DereferenceObject(); $this->DereferenceObject();
   
return $this->object; return $this->object;
} }
   
/** /**
* GetCommit * GetCommit
* *
* Gets the commit this tag points to * Gets the commit this tag points to
* *
* @access public * @access public
* @return mixed commit for this tag * @return mixed commit for this tag
*/ */
public function GetCommit() public function GetCommit()
{ {
if ($this->commitReferenced) if ($this->commitReferenced)
$this->DereferenceCommit(); $this->DereferenceCommit();
   
if ($this->commit) if ($this->commit)
return $this->commit; return $this->commit;
   
if (!$this->dataRead) { if (!$this->dataRead) {
$this->ReadData(); $this->ReadData();
if ($this->commitReferenced) if ($this->commitReferenced)
$this->DereferenceCommit(); $this->DereferenceCommit();
  }
   
  if (!$this->commit) {
  if ($this->object instanceof GitPHP_Commit) {
  $this->commit = $this->object;
  } else if ($this->object instanceof GitPHP_Tag) {
  $this->commit = $this->object->GetCommit();
  }
} }
   
return $this->commit; return $this->commit;
} }
   
/** /**
* SetCommit * SetCommit
* *
* Sets the commit this tag points to * Sets the commit this tag points to
* *
* @access public * @access public
* @param mixed $commit commit object * @param mixed $commit commit object
*/ */
public function SetCommit($commit) public function SetCommit($commit)
{ {
if ($this->commitReferenced) if ($this->commitReferenced)
$this->DereferenceCommit(); $this->DereferenceCommit();
   
if (!$this->commit) if (!$this->commit)
$this->commit = $commit; $this->commit = $commit;
} }
   
/** /**
* GetType * GetType
* *
* Gets the tag type * Gets the tag type
* *
* @access public * @access public
* @return string tag type * @return string tag type
*/ */
public function GetType() public function GetType()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->type; return $this->type;
} }
   
/** /**
* GetTagger * GetTagger
* *
* Gets the tagger * Gets the tagger
* *
* @access public * @access public
* @return string tagger * @return string tagger
*/ */
public function GetTagger() public function GetTagger()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->tagger; return $this->tagger;
} }
   
/** /**
* GetTaggerEpoch * GetTaggerEpoch
* *
* Gets the tagger epoch * Gets the tagger epoch
* *
* @access public * @access public
* @return string tagger epoch * @return string tagger epoch
*/ */
public function GetTaggerEpoch() public function GetTaggerEpoch()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->taggerEpoch; return $this->taggerEpoch;
} }
   
/** /**
* GetTaggerLocalEpoch * GetTaggerLocalEpoch
* *
* Gets the tagger local epoch * Gets the tagger local epoch
* *
* @access public * @access public
* @return string tagger local epoch * @return string tagger local epoch
*/ */
public function GetTaggerLocalEpoch() public function GetTaggerLocalEpoch()
{ {
$epoch = $this->GetTaggerEpoch(); $epoch = $this->GetTaggerEpoch();
$tz = $this->GetTaggerTimezone(); $tz = $this->GetTaggerTimezone();
if (preg_match('/^([+\-][0-9][0-9])([0-9][0-9])$/', $tz, $regs)) { if (preg_match('/^([+\-][0-9][0-9])([0-9][0-9])$/', $tz, $regs)) {
$local = $epoch + ((((int)$regs[1]) + ($regs[2]/60)) * 3600); $local = $epoch + ((((int)$regs[1]) + ($regs[2]/60)) * 3600);
return $local; return $local;
} }
return $epoch; return $epoch;
} }
   
/** /**
* GetTaggerTimezone * GetTaggerTimezone
* *
* Gets the tagger timezone * Gets the tagger timezone
* *
* @access public * @access public
* @return string tagger timezone * @return string tagger timezone
*/ */
public function GetTaggerTimezone() public function GetTaggerTimezone()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->taggerTimezone; return $this->taggerTimezone;
} }
   
/** /**
* GetComment * GetComment
* *
* Gets the tag comment * Gets the tag comment
* *
* @access public * @access public
* @return array comment lines * @return array comment lines
*/ */
public function GetComment() public function GetComment()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
return $this->comment; return $this->comment;
} }
   
/** /**
* LightTag * LightTag
* *
* Tests if this is a light tag (tag without tag object) * Tests if this is a light tag (tag without tag object)
* *
* @access public * @access public
* @return boolean true if tag is light (has no object) * @return boolean true if tag is light (has no object)
*/ */
public function LightTag() public function LightTag()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->objectReferenced) if ($this->objectReferenced)
$this->DereferenceObject(); $this->DereferenceObject();
   
if (!$this->object) if (!$this->object)
return true; return true;
   
return $this->object->GetHash() === $this->GetHash(); return $this->object->GetHash() === $this->GetHash();
} }
   
/** /**
* ReadData * ReadData
* *
* Reads the tag data * Reads the tag data
* *
* @access protected * @access protected
*/ */
protected function ReadData() protected function ReadData()
{ {
$this->dataRead = true; $this->dataRead = true;
   
if (GitPHP_Config::GetInstance()->GetValue('compat', false)) { if (GitPHP_Config::GetInstance()->GetValue('compat', false)) {
$this->ReadDataGit(); $this->ReadDataGit();
} else { } else {
$this->ReadDataRaw(); $this->ReadDataRaw();
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* ReadDataGit * ReadDataGit
* *
* Reads the tag data using the git executable * Reads the tag data using the git executable
* *
* @access private * @access private
*/ */
private function ReadDataGit() private function ReadDataGit()
{ {
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
$args[] = '-t'; $args[] = '-t';
$args[] = $this->GetHash(); $args[] = $this->GetHash();
$ret = trim($exe->Execute(GIT_CAT_FILE, $args)); $ret = trim($exe->Execute(GIT_CAT_FILE, $args));
if ($ret === 'commit') { if ($ret === 'commit') {
/* light tag */ /* light tag */
$this->object = $this->GetProject()->GetCommit($this->GetHash()); $this->object = $this->GetProject()->GetCommit($this->GetHash());
$this->commit = $this->object; $this->commit = $this->object;
$this->type = 'commit'; $this->type = 'commit';
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
return; return;
} }
   
/* get data from tag object */ /* get data from tag object */
$args = array(); $args = array();
$args[] = 'tag'; $args[] = 'tag';
$args[] = $this->GetName(); $args[] = $this->GetName();
$ret = $exe->Execute(GIT_CAT_FILE, $args); $ret = $exe->Execute(GIT_CAT_FILE, $args);
unset($exe); unset($exe);
   
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
   
if (!isset($lines[0])) if (!isset($lines[0]))
return; return;
   
$objectHash = null; $objectHash = null;
   
$readInitialData = false; $readInitialData = false;
foreach ($lines as $i => $line) { foreach ($lines as $i => $line) {
if (!$readInitialData) { if (!$readInitialData) {
if (preg_match('/^object ([0-9a-fA-F]{40})$/', $line, $regs)) { if (preg_match('/^object ([0-9a-fA-F]{40})$/', $line, $regs)) {
$objectHash = $regs[1]; $objectHash = $regs[1];
continue; continue;
} else if (preg_match('/^type (.+)$/', $line, $regs)) { } else if (preg_match('/^type (.+)$/', $line, $regs)) {
$this->type = $regs[1]; $this->type = $regs[1];
continue; continue;
} else if (preg_match('/^tag (.+)$/', $line, $regs)) { } else if (preg_match('/^tag (.+)$/', $line, $regs)) {
continue; continue;
} else if (preg_match('/^tagger (.*) ([0-9]+) (.*)$/', $line, $regs)) { } else if (preg_match('/^tagger (.*) ([0-9]+) (.*)$/', $line, $regs)) {
$this->tagger = $regs[1]; $this->tagger = $regs[1];
$this->taggerEpoch = $regs[2]; $this->taggerEpoch = $regs[2];
$this->taggerTimezone = $regs[3]; $this->taggerTimezone = $regs[3];
continue; continue;
} }
} }
   
$trimmed = trim($line); $trimmed = trim($line);
   
if ((strlen($trimmed) > 0) || ($readInitialData === true)) { if ((strlen($trimmed) > 0) || ($readInitialData === true)) {
$this->comment[] = $line; $this->comment[] = $line;
} }
$readInitialData = true; $readInitialData = true;
   
} }
   
switch ($this->type) { switch ($this->type) {
case 'commit': case 'commit':
try { try {
$this->object = $this->GetProject()->GetCommit($objectHash); $this->object = $this->GetProject()->GetCommit($objectHash);
$this->commit = $this->object; $this->commit = $this->object;
} catch (Exception $e) { } catch (Exception $e) {
} }
break; break;
case 'tag': case 'tag':
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
$args[] = 'tag'; $args[] = 'tag';
$args[] = $objectHash; $args[] = $objectHash;
$ret = $exe->Execute(GIT_CAT_FILE, $args); $ret = $exe->Execute(GIT_CAT_FILE, $args);
unset($exe); unset($exe);
$lines = explode("\n", $ret); $lines = explode("\n", $ret);
foreach ($lines as $i => $line) { foreach ($lines as $i => $line) {
if (preg_match('/^tag (.+)$/', $line, $regs)) { if (preg_match('/^tag (.+)$/', $line, $regs)) {
$name = trim($regs[1]); $name = trim($regs[1]);
$this->object = $this->GetProject()->GetTag($name); $this->object = $this->GetProject()->GetTag($name);
if ($this->object) { if ($this->object) {
$this->object->SetHash($objectHash); $this->object->SetHash($objectHash);
} }
} }
} }
break; break;
} }
} }
   
/** /**
* ReadDataRaw * ReadDataRaw
* *
* Reads the tag data using the raw git object * Reads the tag data using the raw git object
* *
* @access private * @access private
*/ */
private function ReadDataRaw() private function ReadDataRaw()
{ {
$data = $this->GetProject()->GetObject($this->GetHash(), $type); $data = $this->GetProject()->GetObject($this->GetHash(), $type);
if ($type == GitPHP_Pack::OBJ_COMMIT) { if ($type == GitPHP_Pack::OBJ_COMMIT) {
/* light tag */ /* light tag */
$this->object = $this->GetProject()->GetCommit($this->GetHash()); $this->object = $this->GetProject()->GetCommit($this->GetHash());
$this->commit = $this->object; $this->commit = $this->object;
$this->type = 'commit'; $this->type = 'commit';
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
return; return;
} }
   
$lines = explode("\n", $data); $lines = explode("\n", $data);
   
if (!isset($lines[0])) if (!isset($lines[0]))
return; return;
   
$objectHash = null; $objectHash = null;
   
$readInitialData = false; $readInitialData = false;
foreach ($lines as $i => $line) { foreach ($lines as $i => $line) {
if (!$readInitialData) { if (!$readInitialData) {
if (preg_match('/^object ([0-9a-fA-F]{40})$/', $line, $regs)) { if (preg_match('/^object ([0-9a-fA-F]{40})$/', $line, $regs)) {
$objectHash = $regs[1]; $objectHash = $regs[1];
continue; continue;
} else if (preg_match('/^type (.+)$/', $line, $regs)) { } else if (preg_match('/^type (.+)$/', $line, $regs)) {
$this->type = $regs[1]; $this->type = $regs[1];
continue; continue;
} else if (preg_match('/^tag (.+)$/', $line, $regs)) { } else if (preg_match('/^tag (.+)$/', $line, $regs)) {
continue; continue;
} else if (preg_match('/^tagger (.*) ([0-9]+) (.*)$/', $line, $regs)) { } else if (preg_match('/^tagger (.*) ([0-9]+) (.*)$/', $line, $regs)) {
$this->tagger = $regs[1]; $this->tagger = $regs[1];
$this->taggerEpoch = $regs[2]; $this->taggerEpoch = $regs[2];
$this->taggerTimezone = $regs[3]; $this->taggerTimezone = $regs[3];
continue; continue;
} }
} }
   
$trimmed = trim($line); $trimmed = trim($line);
   
if ((strlen($trimmed) > 0) || ($readInitialData === true)) { if ((strlen($trimmed) > 0) || ($readInitialData === true)) {
$this->comment[] = $line; $this->comment[] = $line;
} }
$readInitialData = true; $readInitialData = true;
} }
   
switch ($this->type) { switch ($this->type) {
case 'commit': case 'commit':
try { try {
$this->object = $this->GetProject()->GetCommit($objectHash); $this->object = $this->GetProject()->GetCommit($objectHash);
$this->commit = $this->object; $this->commit = $this->object;
} catch (Exception $e) { } catch (Exception $e) {
} }
break; break;
case 'tag': case 'tag':
$objectData = $this->GetProject()->GetObject($objectHash); $objectData = $this->GetProject()->GetObject($objectHash);
$lines = explode("\n", $objectData); $lines = explode("\n", $objectData);
foreach ($lines as $i => $line) { foreach ($lines as $i => $line) {
if (preg_match('/^tag (.+)$/', $line, $regs)) { if (preg_match('/^tag (.+)$/', $line, $regs)) {
$name = trim($regs[1]); $name = trim($regs[1]);
$this->object = $this->GetProject()->GetTag($name); $this->object = $this->GetProject()->GetTag($name);
if ($this->object) { if ($this->object) {
$this->object->SetHash($objectHash); $this->object->SetHash($objectHash);
} }
} }
} }
break; break;
} }
} }
   
/** /**
* ReadCommit * ReadCommit
* *
* Attempts to dereference the commit for this tag * Attempts to dereference the commit for this tag
* *
* @access private * @access private
*/ */
private function ReadCommit() private function ReadCommit()
{ {
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
$args = array(); $args = array();
$args[] = '--tags'; $args[] = '--tags';
$args[] = '--dereference'; $args[] = '--dereference';
$args[] = $this->refName; $args[] = $this->refName;
$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\/' . preg_quote($this->refName) . '(\^{})$/', $line, $regs)) { if (preg_match('/^([0-9a-fA-F]{40}) refs\/tags\/' . preg_quote($this->refName) . '(\^{})$/', $line, $regs)) {
$this->commit = $this->GetProject()->GetCommit($regs[1]); $this->commit = $this->GetProject()->GetCommit($regs[1]);
return; return;
} }
} }
   
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this); GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
} }
   
/** /**
* ReferenceObject * ReferenceObject
* *
* Turns the object into a reference pointer * Turns the object into a reference pointer
* *
* @access private * @access private
*/ */
private function ReferenceObject() private function ReferenceObject()
{ {
if ($this->objectReferenced) if ($this->objectReferenced)
return; return;
   
if (!$this->object) if (!$this->object)
return; return;
   
if ($this->type == 'commit') { if ($this->type == 'commit') {
$this->object = $this->object->GetHash(); $this->object = $this->object->GetHash();
} else if ($this->type == 'tag') { } else if ($this->type == 'tag') {
$this->object = $this->object->GetName(); $this->object = $this->object->GetName();
} }
   
$this->objectReferenced = true; $this->objectReferenced = true;
} }
   
/** /**
* DereferenceObject * DereferenceObject
* *
* Turns the object pointer back into an object * Turns the object pointer back into an object
* *
* @access private * @access private
*/ */
private function DereferenceObject() private function DereferenceObject()
{ {
if (!$this->objectReferenced) if (!$this->objectReferenced)
return; return;
   
if (empty($this->object)) if (empty($this->object))
return; return;
   
if ($this->type == 'commit') { if ($this->type == 'commit') {
$this->object = $this->GetProject()->GetCommit($this->object); $this->object = $this->GetProject()->GetCommit($this->object);
} else if ($this->type == 'tag') { } else if ($this->type == 'tag') {
$this->object = $this->GetProject()->GetTag($this->object); $this->object = $this->GetProject()->GetTag($this->object);
} }
   
$this->objectReferenced = false; $this->objectReferenced = false;
} }
   
/** /**
* ReferenceCommit * ReferenceCommit
* *
* Turns the commit into a reference pointer * Turns the commit into a reference pointer
* *
* @access private * @access private
*/ */
private function ReferenceCommit() private function ReferenceCommit()
{ {
if ($this->commitReferenced) if ($this->commitReferenced)
return; return;
   
if (!$this->commit) if (!$this->commit)
return; return;
   
$this->commit = $this->commit->GetHash(); $this->commit = $this->commit->GetHash();
   
$this->commitReferenced = true; $this->commitReferenced = true;
} }
   
/** /**
* DereferenceCommit * DereferenceCommit
* *
* Turns the commit pointer back into an object * Turns the commit pointer back into an object
* *
* @access private * @access private
*/ */
private function DereferenceCommit() private function DereferenceCommit()
{ {
if (!$this->commitReferenced) if (!$this->commitReferenced)
return; return;
   
if (empty($this->commit)) if (empty($this->commit))
return; return;
   
if ($this->type == 'commit') { if ($this->type == 'commit') {
$obj = $this->GetObject(); $obj = $this->GetObject();
if ($obj && ($obj->GetHash() == $this->commit)) { if ($obj && ($obj->GetHash() == $this->commit)) {
/* /*
* Light tags are type commit and the commit * Light tags are type commit and the commit
* and object are the same, in which case * and object are the same, in which case
* no need to fetch the object again * no need to fetch the object again
*/ */
$this->commit = $obj; $this->commit = $obj;
$this->commitReferenced = false; $this->commitReferenced = false;
return; return;
} }
} }
   
$this->commit = $this->GetProject()->GetCommit($this->commit); $this->commit = $this->GetProject()->GetCommit($this->commit);
   
$this->commitReferenced = false; $this->commitReferenced = false;
} }
   
/** /**
* __sleep * __sleep
* *
* Called to prepare the object for serialization * Called to prepare the object for serialization
* *
* @access public * @access public
* @return array list of properties to serialize * @return array list of properties to serialize
*/ */
public function __sleep() public function __sleep()
{ {
if (!$this->objectReferenced) if (!$this->objectReferenced)
$this->ReferenceObject(); $this->ReferenceObject();
   
if (!$this->commitReferenced) if (!$this->commitReferenced)
$this->ReferenceCommit(); $this->ReferenceCommit();
   
$properties = array('dataRead', 'object', 'commit', 'type', 'tagger', 'taggerEpoch', 'taggerTimezone', 'comment', 'objectReferenced', 'commitReferenced'); $properties = array('dataRead', 'object', 'commit', 'type', 'tagger', 'taggerEpoch', 'taggerTimezone', 'comment', 'objectReferenced', 'commitReferenced');
return array_merge($properties, parent::__sleep()); return array_merge($properties, parent::__sleep());
} }
   
/** /**
* GetCacheKey * GetCacheKey
* *
* Gets the cache key to use for this object * Gets the cache key to use for this object
* *
* @access public * @access public
* @return string cache key * @return string cache key
*/ */
public function GetCacheKey() public function GetCacheKey()
{ {
$key = parent::GetCacheKey(); $key = parent::GetCacheKey();
if (!empty($key)) if (!empty($key))
$key .= '|'; $key .= '|';
   
$key .= 'tag|' . $this->refName; $key .= 'tag|' . $this->refName;
return $key; return $key;
} }
   
/** /**
* GetCreationEpoch * GetCreationEpoch
* *
* Gets tag's creation epoch * Gets tag's creation epoch
* (tagger epoch, or committer epoch for light tags) * (tagger epoch, or committer epoch for light tags)
* *
* @access public * @access public
* @return string creation epoch * @return string creation epoch
*/ */
public function GetCreationEpoch() public function GetCreationEpoch()
{ {
if (!$this->dataRead) if (!$this->dataRead)
$this->ReadData(); $this->ReadData();
   
if ($this->LightTag()) if ($this->LightTag())
return $this->GetCommit()->GetCommitterEpoch(); return $this->GetCommit()->GetCommitterEpoch();
else else
return $this->taggerEpoch; return $this->taggerEpoch;
} }
   
/** /**
* CompareAge * CompareAge
* *
* Compares two tags by age * Compares two tags by age
* *
* @access public * @access public
* @static * @static
* @param mixed $a first tag * @param mixed $a first tag
* @param mixed $b second tag * @param mixed $b second tag
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareAge($a, $b) public static function CompareAge($a, $b)
{ {
$aObj = $a->GetObject(); $aObj = $a->GetObject();
$bObj = $b->GetObject(); $bObj = $b->GetObject();
if (($aObj instanceof GitPHP_Commit) && ($bObj instanceof GitPHP_Commit)) { if (($aObj instanceof GitPHP_Commit) && ($bObj instanceof GitPHP_Commit)) {
return GitPHP_Commit::CompareAge($aObj, $bObj); return GitPHP_Commit::CompareAge($aObj, $bObj);
} }
   
if ($aObj instanceof GitPHP_Commit) if ($aObj instanceof GitPHP_Commit)
return 1; return 1;
   
if ($bObj instanceof GitPHP_Commit) if ($bObj instanceof GitPHP_Commit)
return -1; return -1;
   
return strcmp($a->GetName(), $b->GetName()); return strcmp($a->GetName(), $b->GetName());
} }
   
/** /**
* CompareCreationEpoch * CompareCreationEpoch
* *
* Compares to tags by creation epoch * Compares to tags by creation epoch
* *
* @access public * @access public
* @static * @static
* @param mixed $a first tag * @param mixed $a first tag
* @param mixed $b second tag * @param mixed $b second tag
* @return integer comparison result * @return integer comparison result
*/ */
public static function CompareCreationEpoch($a, $b) public static function CompareCreationEpoch($a, $b)
{ {
$aEpoch = $a->GetCreationEpoch(); $aEpoch = $a->GetCreationEpoch();
$bEpoch = $b->GetCreationEpoch(); $bEpoch = $b->GetCreationEpoch();
   
if ($aEpoch == $bEpoch) { if ($aEpoch == $bEpoch) {
return 0; return 0;
} }
   
return ($aEpoch < $bEpoch ? 1 : -1); return ($aEpoch < $bEpoch ? 1 : -1);
} }
   
} }
   
file:a/index.php -> file:b/index.php
<?php <?php
/** /**
* GitPHP * GitPHP
* *
* Index * Index
* *
* @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
*/ */
   
/** /**
* Define start time / memory for benchmarking * Define start time / memory for benchmarking
*/ */
define('GITPHP_START_TIME', microtime(true)); define('GITPHP_START_TIME', microtime(true));
define('GITPHP_START_MEM', memory_get_usage()); define('GITPHP_START_MEM', memory_get_usage());
   
/** /**
* Define some paths * Define some paths
*/ */
define('GITPHP_BASEDIR', dirname(__FILE__) . '/'); define('GITPHP_BASEDIR', dirname(__FILE__) . '/');
define('GITPHP_CONFIGDIR', GITPHP_BASEDIR . 'config/'); define('GITPHP_CONFIGDIR', GITPHP_BASEDIR . 'config/');
define('GITPHP_INCLUDEDIR', GITPHP_BASEDIR . 'include/'); define('GITPHP_INCLUDEDIR', GITPHP_BASEDIR . 'include/');
define('GITPHP_GITOBJECTDIR', GITPHP_INCLUDEDIR . 'git/'); define('GITPHP_GITOBJECTDIR', GITPHP_INCLUDEDIR . 'git/');
define('GITPHP_CONTROLLERDIR', GITPHP_INCLUDEDIR . 'controller/'); define('GITPHP_CONTROLLERDIR', GITPHP_INCLUDEDIR . 'controller/');
define('GITPHP_CACHEDIR', GITPHP_INCLUDEDIR . 'cache/'); define('GITPHP_CACHEDIR', GITPHP_INCLUDEDIR . 'cache/');
define('GITPHP_LOCALEDIR', GITPHP_BASEDIR . 'locale/'); define('GITPHP_LOCALEDIR', GITPHP_BASEDIR . 'locale/');
   
include_once(GITPHP_INCLUDEDIR . 'version.php'); include_once(GITPHP_INCLUDEDIR . 'version.php');
   
require_once(GITPHP_INCLUDEDIR . 'Util.class.php'); require_once(GITPHP_INCLUDEDIR . 'Util.class.php');
   
require_once(GITPHP_INCLUDEDIR . 'Config.class.php'); require_once(GITPHP_INCLUDEDIR . 'Config.class.php');
   
require_once(GITPHP_INCLUDEDIR . 'Resource.class.php'); require_once(GITPHP_INCLUDEDIR . 'Resource.class.php');
   
require_once(GITPHP_INCLUDEDIR . 'Log.class.php'); require_once(GITPHP_INCLUDEDIR . 'Log.class.php');
   
require_once(GITPHP_GITOBJECTDIR . 'ProjectList.class.php'); require_once(GITPHP_GITOBJECTDIR . 'ProjectList.class.php');
   
require_once(GITPHP_INCLUDEDIR . 'MessageException.class.php'); require_once(GITPHP_INCLUDEDIR . 'MessageException.class.php');
   
require_once(GITPHP_CONTROLLERDIR . 'Controller.class.php'); require_once(GITPHP_CONTROLLERDIR . 'Controller.class.php');
   
require_once(GITPHP_CACHEDIR . 'Cache.class.php'); require_once(GITPHP_CACHEDIR . 'Cache.class.php');
   
// Need this include for the compression constants used in the config file // Need this include for the compression constants used in the config file
require_once(GITPHP_GITOBJECTDIR . 'Archive.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Archive.class.php');
   
// Test these executables early // Test these executables early
require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php'); require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php');
require_once(GITPHP_GITOBJECTDIR . 'DiffExe.class.php'); require_once(GITPHP_GITOBJECTDIR . 'DiffExe.class.php');
   
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
   
   
/* /*
* Set the locale based on the user's preference * Set the locale based on the user's preference
*/ */
if ((!isset($_COOKIE[GITPHP_LOCALE_COOKIE])) || empty($_COOKIE[GITPHP_LOCALE_COOKIE])) { if ((!isset($_COOKIE[GITPHP_LOCALE_COOKIE])) || empty($_COOKIE[GITPHP_LOCALE_COOKIE])) {
   
/* /*
* User's first time here, try by HTTP_ACCEPT_LANGUAGE * User's first time here, try by HTTP_ACCEPT_LANGUAGE
*/ */
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$httpAcceptLang = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); $httpAcceptLang = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
$preferredLocale = GitPHP_Resource::FindPreferredLocale($_SERVER['HTTP_ACCEPT_LANGUAGE']); $preferredLocale = GitPHP_Resource::FindPreferredLocale($_SERVER['HTTP_ACCEPT_LANGUAGE']);
if (!empty($preferredLocale)) { if (!empty($preferredLocale)) {
setcookie(GITPHP_LOCALE_COOKIE, $preferredLocale, time()+GITPHP_LOCALE_COOKIE_LIFETIME); setcookie(GITPHP_LOCALE_COOKIE, $preferredLocale, time()+GITPHP_LOCALE_COOKIE_LIFETIME);
GitPHP_Resource::Instantiate($preferredLocale); GitPHP_Resource::Instantiate($preferredLocale);
} }
} }
   
if (!GitPHP_Resource::Instantiated()) { if (!GitPHP_Resource::Instantiated()) {
/* /*
* Create a dummy cookie to prevent browser delay * Create a dummy cookie to prevent browser delay
*/ */
setcookie(GITPHP_LOCALE_COOKIE, 0, time()+GITPHP_LOCALE_COOKIE_LIFETIME); setcookie(GITPHP_LOCALE_COOKIE, 0, time()+GITPHP_LOCALE_COOKIE_LIFETIME);
} }
   
} else if (isset($_GET['l']) && !empty($_GET['l'])) { } else if (isset($_GET['l']) && !empty($_GET['l'])) {
   
/* /*
* User picked something * User picked something
*/ */
setcookie(GITPHP_LOCALE_COOKIE, $_GET['l'], time()+GITPHP_LOCALE_COOKIE_LIFETIME); setcookie(GITPHP_LOCALE_COOKIE, $_GET['l'], time()+GITPHP_LOCALE_COOKIE_LIFETIME);
GitPHP_Resource::Instantiate($_GET['l']); GitPHP_Resource::Instantiate($_GET['l']);
   
} else if (isset($_COOKIE[GITPHP_LOCALE_COOKIE]) && !empty($_COOKIE[GITPHP_LOCALE_COOKIE])) { } else if (isset($_COOKIE[GITPHP_LOCALE_COOKIE]) && !empty($_COOKIE[GITPHP_LOCALE_COOKIE])) {
   
/* /*
* Returning visitor with a preference * Returning visitor with a preference
*/ */
GitPHP_Resource::Instantiate($_COOKIE[GITPHP_LOCALE_COOKIE]); GitPHP_Resource::Instantiate($_COOKIE[GITPHP_LOCALE_COOKIE]);
   
} }
   
   
try { try {
   
/* /*
* Configuration * Configuration
*/ */
GitPHP_Config::GetInstance()->LoadConfig(GITPHP_CONFIGDIR . 'gitphp.conf.php'); GitPHP_Config::GetInstance()->LoadConfig(GITPHP_CONFIGDIR . 'gitphp.conf.php');
   
/* /*
* Use the default language in the config if user has no preference * Use the default language in the config if user has no preference
* with en_US as the fallback * with en_US as the fallback
*/ */
if (!GitPHP_Resource::Instantiated()) { if (!GitPHP_Resource::Instantiated()) {
GitPHP_Resource::Instantiate(GitPHP_Config::GetInstance()->GetValue('locale', 'en_US')); GitPHP_Resource::Instantiate(GitPHP_Config::GetInstance()->GetValue('locale', 'en_US'));
} }
   
/* /*
* Debug * Debug
*/ */
if (GitPHP_Log::GetInstance()->GetEnabled()) { if (GitPHP_Log::GetInstance()->GetEnabled()) {
GitPHP_Log::GetInstance()->SetStartTime(GITPHP_START_TIME); GitPHP_Log::GetInstance()->SetStartTime(GITPHP_START_TIME);
GitPHP_Log::GetInstance()->SetStartMemory(GITPHP_START_MEM); GitPHP_Log::GetInstance()->SetStartMemory(GITPHP_START_MEM);
} }
   
if (!GitPHP_Config::GetInstance()->GetValue('projectroot', null)) { if (!GitPHP_Config::GetInstance()->GetValue('projectroot', null)) {
throw new GitPHP_MessageException(__('A projectroot must be set in the config'), true, 500); throw new GitPHP_MessageException(__('A projectroot must be set in the config'), true, 500);
} }
   
/* /*
* Check for required executables * Check for required executables
*/ */
$exe = new GitPHP_GitExe(null); $exe = new GitPHP_GitExe(null);
if (!$exe->Valid()) { if (!$exe->Valid()) {
throw new GitPHP_MessageException(sprintf(__('Could not run the git executable "%1$s". You may need to set the "%2$s" config value.'), $exe->GetBinary(), 'gitbin'), true, 500); throw new GitPHP_MessageException(sprintf(__('Could not run the git executable "%1$s". You may need to set the "%2$s" config value.'), $exe->GetBinary(), 'gitbin'), true, 500);
} }
$exe = new GitPHP_DiffExe(); if (!function_exists('xdiff_string_diff')) {
if (!$exe->Valid()) { $exe = new GitPHP_DiffExe();
throw new GitPHP_MessageException(sprintf(__('Could not run the diff executable "%1$s". You may need to set the "%2$s" config value.'), $exe->GetBinary(), 'diffbin'), true, 500); if (!$exe->Valid()) {
  throw new GitPHP_MessageException(sprintf(__('Could not run the diff executable "%1$s". You may need to set the "%2$s" config value.'), $exe->GetBinary(), 'diffbin'), true, 500);
  }
} }
unset($exe); unset($exe);
   
/* /*
* Project list * Project list
*/ */
if (file_exists(GITPHP_CONFIGDIR . 'projects.conf.php')) { if (file_exists(GITPHP_CONFIGDIR . 'projects.conf.php')) {
GitPHP_ProjectList::Instantiate(GITPHP_CONFIGDIR . 'projects.conf.php', false); GitPHP_ProjectList::Instantiate(GITPHP_CONFIGDIR . 'projects.conf.php', false);
} else { } else {
GitPHP_ProjectList::Instantiate(GITPHP_CONFIGDIR . 'gitphp.conf.php', true); GitPHP_ProjectList::Instantiate(GITPHP_CONFIGDIR . 'gitphp.conf.php', true);
} }
   
$controller = GitPHP_Controller::GetController((isset($_GET['a']) ? $_GET['a'] : null)); $controller = GitPHP_Controller::GetController((isset($_GET['a']) ? $_GET['a'] : null));
if ($controller) { if ($controller) {
$controller->RenderHeaders(); $controller->RenderHeaders();
$controller->Render(); $controller->Render();
} }
   
} catch (Exception $e) { } catch (Exception $e) {
   
if (GitPHP_Config::GetInstance()->GetValue('debug', false)) { if (GitPHP_Config::GetInstance()->GetValue('debug', false)) {
throw $e; throw $e;
} }
   
if (!GitPHP_Resource::Instantiated()) { if (!GitPHP_Resource::Instantiated()) {
/* /*
* In case an error was thrown before instantiating * In case an error was thrown before instantiating
* the resource manager * the resource manager
*/ */
GitPHP_Resource::Instantiate('en_US'); GitPHP_Resource::Instantiate('en_US');
} }
   
require_once(GITPHP_CONTROLLERDIR . 'Controller_Message.class.php'); require_once(GITPHP_CONTROLLERDIR . 'Controller_Message.class.php');
$controller = new GitPHP_Controller_Message(); $controller = new GitPHP_Controller_Message();
$controller->SetParam('message', $e->getMessage()); $controller->SetParam('message', $e->getMessage());
if ($e instanceof GitPHP_MessageException) { if ($e instanceof GitPHP_MessageException) {
$controller->SetParam('error', $e->Error); $controller->SetParam('error', $e->Error);
$controller->SetParam('statuscode', $e->StatusCode); $controller->SetParam('statuscode', $e->StatusCode);
} else { } else {
$controller->SetParam('error', true); $controller->SetParam('error', true);
} }
$controller->RenderHeaders(); $controller->RenderHeaders();
$controller->Render(); $controller->Render();
   
} }
   
if (GitPHP_Log::GetInstance()->GetEnabled()) { if (GitPHP_Log::GetInstance()->GetEnabled()) {
$entries = GitPHP_Log::GetInstance()->GetEntries(); $entries = GitPHP_Log::GetInstance()->GetEntries();
foreach ($entries as $logline) { foreach ($entries as $logline) {
echo "\n" . $logline; echo "\n" . $logline;
} }
} }
   
?> ?>
   
# 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 ""
   
{* {*
* log.tpl * log.tpl
* gitphp: A PHP git repository browser * gitphp: A PHP git repository browser
* Component: Log view template * Component: Log view template
* *
* Copyright (C) 2009 Christopher Han <xiphux@gmail.com> * Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
*} *}
{include file='header.tpl'} {include file='header.tpl'}
   
{* Nav *} {* Nav *}
<div class="page_nav"> <div class="page_nav">
{include file='nav.tpl' current='log' logcommit=$commit treecommit=$commit logmark=$mark} {include file='nav.tpl' current='log' logcommit=$commit treecommit=$commit logmark=$mark}
<br /> <br />
{if ($commit && $head) && (($commit->GetHash() != $head->GetHash()) || ($page > 0))} {if ($commit && $head) && (($commit->GetHash() != $head->GetHash()) || ($page > 0))}
<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=log{if $mark}&amp;m={$mark->GetHash()}{/if}">{t}HEAD{/t}</a> <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=log{if $mark}&amp;m={$mark->GetHash()}{/if}">{t}HEAD{/t}</a>
{else} {else}
{t}HEAD{/t} {t}HEAD{/t}
{/if} {/if}
&sdot; &sdot;
{if $page > 0} {if $page > 0}
<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=log&amp;h={$commit->GetHash()}&amp;pg={$page-1}{if $mark}&amp;m={$mark->GetHash()}{/if}" accesskey="p" title="Alt-p">{t}prev{/t}</a> <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=log&amp;h={$commit->GetHash()}&amp;pg={$page-1}{if $mark}&amp;m={$mark->GetHash()}{/if}" accesskey="p" title="Alt-p">{t}prev{/t}</a>
{else} {else}
{t}prev{/t} {t}prev{/t}
{/if} {/if}
&sdot; &sdot;
{if $hasmorerevs} {if $hasmorerevs}
<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=log&amp;h={$commit->GetHash()}&amp;pg={$page+1}{if $mark}&amp;m={$mark->GetHash()}{/if}" accesskey="n" title="Alt-n">{t}next{/t}</a> <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=log&amp;h={$commit->GetHash()}&amp;pg={$page+1}{if $mark}&amp;m={$mark->GetHash()}{/if}" accesskey="n" title="Alt-n">{t}next{/t}</a>
{else} {else}
{t}next{/t} {t}next{/t}
{/if} {/if}
<br /> <br />
{if $mark} {if $mark}
{t}selected{/t} &sdot; {t}selected{/t} &sdot;
<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=commit&amp;h={$mark->GetHash()}" class="list commitTip" {if strlen($mark->GetTitle()) > 30}title="{$mark->GetTitle()}"{/if}><strong>{$mark->GetTitle(30)}</strong></a> <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=commit&amp;h={$mark->GetHash()}" class="list commitTip" {if strlen($mark->GetTitle()) > 30}title="{$mark->GetTitle()}"{/if}><strong>{$mark->GetTitle(30)}</strong></a>
&sdot; &sdot;
<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=log&amp;h={$commit->GetHash()}&amp;pg={$page}">{t}deselect{/t}</a> <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=log&amp;h={$commit->GetHash()}&amp;pg={$page}">{t}deselect{/t}</a>
<br /> <br />
{/if} {/if}
</div> </div>
{foreach from=$revlist item=rev} {foreach from=$revlist item=rev}
<div class="title"> <div class="title">
<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=commit&amp;h={$rev->GetHash()}" class="title"><span class="age">{$rev->GetAge()|agestring}</span>{$rev->GetTitle()}</a> <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=commit&amp;h={$rev->GetHash()}" class="title"><span class="age">{$rev->GetAge()|agestring}</span>{$rev->GetTitle()}</a>
{include file='refbadges.tpl' commit=$rev} {include file='refbadges.tpl' commit=$rev}
</div> </div>
<div class="title_text"> <div class="title_text">
<div class="log_link"> <div class="log_link">