Extract new diff message
Extract new diff message

<?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;
   
   
/* /*
* 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
  *
  * Turn a string into a filename-friendly slug
  *
  * @access public
  * @param string $str string to slugify
  * @static
  * @return string slug
  */
  public static function MakeSlug($str)
  {
  $from = array(
  '/'
  );
  $to = array(
  '-'
  );
  return str_replace($from, $to, $str);
  }
   
} }
   
<?php <?php
/** /**
* GitPHP Archive * GitPHP Archive
* *
* Represents an archive (snapshot) * Represents an archive (snapshot)
* *
* @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');
   
define('GITPHP_COMPRESS_TAR', 'tar'); define('GITPHP_COMPRESS_TAR', 'tar');
define('GITPHP_COMPRESS_BZ2', 'tbz2'); define('GITPHP_COMPRESS_BZ2', 'tbz2');
define('GITPHP_COMPRESS_GZ', 'tgz'); define('GITPHP_COMPRESS_GZ', 'tgz');
define('GITPHP_COMPRESS_ZIP', 'zip'); define('GITPHP_COMPRESS_ZIP', 'zip');
   
/** /**
* Archive class * Archive class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Archive class GitPHP_Archive
{ {
/** /**
* gitObject * gitObject
* *
* Stores the object for this archive internally * Stores the object for this archive internally
* *
* @access protected * @access protected
*/ */
protected $gitObject = null; protected $gitObject = null;
   
/** /**
* project * project
* *
* Stores the project for this archive internally * Stores the project for this archive internally
* *
* @access protected * @access protected
*/ */
protected $project = null; protected $project = null;
   
/** /**
* format * format
* *
* Stores the archive format internally * Stores the archive format internally
* *
* @access protected * @access protected
*/ */
protected $format; protected $format;
   
/** /**
* fileName * fileName
* *
* Stores the archive filename internally * Stores the archive filename internally
* *
* @access protected * @access protected
*/ */
protected $fileName = ''; protected $fileName = '';
   
/** /**
* path * path
* *
* Stores the archive path internally * Stores the archive path internally
* *
* @access protected * @access protected
*/ */
protected $path = ''; protected $path = '';
   
/** /**
* prefix * prefix
* *
* Stores the archive prefix internally * Stores the archive prefix internally
* *
* @access protected * @access protected
*/ */
protected $prefix = ''; protected $prefix = '';
   
/** /**
* __construct * __construct
* *
* Instantiates object * Instantiates object
* *
* @access public * @access public
* @param mixed $gitObject the object * @param mixed $gitObject the object
* @param integer $format the format for the archive * @param integer $format the format for the archive
* @return mixed git archive * @return mixed git archive
*/ */
public function __construct($project, $gitObject, $format = GITPHP_FORMAT_ZIP, $path = '', $prefix = '') public function __construct($project, $gitObject, $format = GITPHP_FORMAT_ZIP, $path = '', $prefix = '')
{ {
$this->SetProject($project); $this->SetProject($project);
$this->SetObject($gitObject); $this->SetObject($gitObject);
$this->SetFormat($format); $this->SetFormat($format);
$this->SetPath($path); $this->SetPath($path);
$this->SetPrefix($prefix); $this->SetPrefix($prefix);
} }
   
/** /**
* GetFormat * GetFormat
* *
* Gets the archive format * Gets the archive format
* *
* @access public * @access public
* @return integer archive format * @return integer archive format
*/ */
public function GetFormat() public function GetFormat()
{ {
return $this->format; return $this->format;
} }
   
/** /**
* SetFormat * SetFormat
* *
* Sets the archive format * Sets the archive format
* *
* @access public * @access public
* @param integer $format archive format * @param integer $format archive format
*/ */
public function SetFormat($format) public function SetFormat($format)
{ {
if ((($format == GITPHP_COMPRESS_BZ2) && (!function_exists('bzcompress'))) || if ((($format == GITPHP_COMPRESS_BZ2) && (!function_exists('bzcompress'))) ||
(($format == GITPHP_COMPRESS_GZ) && (!function_exists('gzencode')))) { (($format == GITPHP_COMPRESS_GZ) && (!function_exists('gzencode')))) {
/* /*
* Trying to set a format but doesn't have the appropriate * Trying to set a format but doesn't have the appropriate
* compression function, fall back to tar * compression function, fall back to tar
*/ */
$format = GITPHP_COMPRESS_TAR; $format = GITPHP_COMPRESS_TAR;
} }
   
$this->format = $format; $this->format = $format;
} }
   
/** /**
* GetObject * GetObject
* *
* Gets the object for this archive * Gets the object for this archive
* *
* @access public * @access public
* @return mixed the git object * @return mixed the git object
*/ */
public function GetObject() public function GetObject()
{ {
return $this->gitObject; return $this->gitObject;
} }
   
/** /**
* SetObject * SetObject
* *
* Sets the object for this archive * Sets the object for this archive
* *
* @access public * @access public
* @param mixed $object the git object * @param mixed $object the git object
*/ */
public function SetObject($object) public function SetObject($object)
{ {
// Archive only works for commits and trees // Archive only works for commits and trees
if (($object != null) && (!(($object instanceof GitPHP_Commit) || ($object instanceof GitPHP_Tree)))) { if (($object != null) && (!(($object instanceof GitPHP_Commit) || ($object instanceof GitPHP_Tree)))) {
throw new Exception('Invalid source object for archive'); throw new Exception('Invalid source object for archive');
} }
   
$this->gitObject = $object; $this->gitObject = $object;
} }
   
/** /**
* GetProject * GetProject
* *
* Gets the project for this archive * Gets the project for this archive
* *
* @access public * @access public
* @return mixed the project * @return mixed the project
*/ */
public function GetProject() public function GetProject()
{ {
if ($this->project) if ($this->project)
return $this->project; return $this->project;
   
if ($this->gitObject) if ($this->gitObject)
return $this->gitObject->GetProject(); return $this->gitObject->GetProject();
   
return null; return null;
} }
   
/** /**
* SetProject * SetProject
* *
* Sets the project for this archive * Sets the project for this archive
* *
* @access public * @access public
* @param mixed $project the project * @param mixed $project the project
*/ */
public function SetProject($project) public function SetProject($project)
{ {
$this->project = $project; $this->project = $project;
} }
   
/** /**
* GetExtension * GetExtension
* *
* Gets the extension to use for this archive * Gets the extension to use for this archive
* *
* @access public * @access public
* @return string extension for the archive * @return string extension for the archive
*/ */
public function GetExtension() public function GetExtension()
{ {
return GitPHP_Archive::FormatToExtension($this->format); return GitPHP_Archive::FormatToExtension($this->format);
} }
   
/** /**
* GetFilename * GetFilename
* *
* Gets the filename for this archive * Gets the filename for this archive
* *
* @access public * @access public
* @return string filename * @return string filename
*/ */
public function GetFilename() public function GetFilename()
{ {
if (!empty($this->fileName)) { if (!empty($this->fileName)) {
return $this->fileName; return $this->fileName;
} }
   
$fname = $this->GetProject()->GetSlug(); $fname = $this->GetProject()->GetSlug();
   
  if (!empty($this->path)) {
  $fname .= '-' . GitPHP_Util::MakeSlug($this->path);
  }
   
$fname .= '.' . $this->GetExtension(); $fname .= '.' . $this->GetExtension();
   
return $fname; return $fname;
} }
   
/** /**
* SetFilename * SetFilename
* *
* Sets the filename for this archive * Sets the filename for this archive
* *
* @access public * @access public
* @param string $name filename * @param string $name filename
*/ */
public function SetFilename($name = '') public function SetFilename($name = '')
{ {
$this->fileName = $name; $this->fileName = $name;
} }
   
/** /**
* GetPath * GetPath
* *
* Gets the path to restrict this archive to * Gets the path to restrict this archive to
* *
* @access public * @access public
* @return string path * @return string path
*/ */
public function GetPath() public function GetPath()
{ {
return $this->path; return $this->path;
} }
   
/** /**
* SetPath * SetPath
* *
* Sets the path to restrict this archive to * Sets the path to restrict this archive to
* *
* @access public * @access public
* @param string $path path to restrict * @param string $path path to restrict
*/ */
public function SetPath($path = '') public function SetPath($path = '')
{ {
$this->path = $path; $this->path = $path;
} }
   
/** /**
* GetPrefix * GetPrefix
* *
* Gets the directory prefix to use for files in this archive * Gets the directory prefix to use for files in this archive
* *
* @access public * @access public
* @return string prefix * @return string prefix
*/ */
public function GetPrefix() public function GetPrefix()
{ {
if (!empty($this->prefix)) { if (!empty($this->prefix)) {
return $this->prefix; return $this->prefix;
} }
   
return $this->GetProject()->GetSlug() . '/'; $pfx = $this->GetProject()->GetSlug() . '/';
   
  if (!empty($this->path))
  $pfx .= $this->path . '/';
   
  return $pfx;
} }
   
/** /**
* SetPrefix * SetPrefix
* *
* Sets the directory prefix to use for files in this archive * Sets the directory prefix to use for files in this archive
* *
* @access public * @access public
* @param string $prefix prefix to use * @param string $prefix prefix to use
*/ */
public function SetPrefix($prefix = '') public function SetPrefix($prefix = '')
{ {
if (empty($prefix)) { if (empty($prefix)) {
$this->prefix = $prefix; $this->prefix = $prefix;
return; return;
} }
   
if (substr($prefix, -1) != '/') { if (substr($prefix, -1) != '/') {
$prefix .= '/'; $prefix .= '/';
} }
   
$this->prefix = $prefix; $this->prefix = $prefix;
} }
   
/** /**
* GetHeaders * GetHeaders
* *
* Gets the headers to send to the browser for this file * Gets the headers to send to the browser for this file
* *
* @access public * @access public
* @return array header strings * @return array header strings
*/ */
public function GetHeaders() public function GetHeaders()
{ {
$headers = array(); $headers = array();
   
switch ($this->format) { switch ($this->format) {
case GITPHP_COMPRESS_TAR: case GITPHP_COMPRESS_TAR:
$headers[] = 'Content-Type: application/x-tar'; $headers[] = 'Content-Type: application/x-tar';
break; break;
case GITPHP_COMPRESS_BZ2: case GITPHP_COMPRESS_BZ2:
$headers[] = 'Content-Type: application/x-bzip2'; $headers[] = 'Content-Type: application/x-bzip2';
break; break;
case GITPHP_COMPRESS_GZ: case GITPHP_COMPRESS_GZ:
$headers[] = 'Content-Type: application/x-gzip'; $headers[] = 'Content-Type: application/x-gzip';
break; break;
case GITPHP_COMPRESS_ZIP: case GITPHP_COMPRESS_ZIP:
$headers[] = 'Content-Type: application/x-zip'; $headers[] = 'Content-Type: application/x-zip';
break; break;
default: default:
throw new Exception('Unknown compression type'); throw new Exception('Unknown compression type');
} }
   
if (count($headers) > 0) { if (count($headers) > 0) {
$headers[] = 'Content-Disposition: attachment; filename=' . $this->GetFilename(); $headers[] = 'Content-Disposition: attachment; filename=' . $this->GetFilename();
} }
   
return $headers; return $headers;
} }
   
/** /**
* GetData * GetData
* *
* Gets the archive data * Gets the archive data
* *
* @access public * @access public
* @return string archive data * @return string archive data
*/ */
public function GetData() public function GetData()
{ {
if (!$this->gitObject) if (!$this->gitObject)
{ {
throw new Exception('Invalid object for archive'); throw new Exception('Invalid object for archive');
} }
   
$exe = new GitPHP_GitExe($this->GetProject()); $exe = new GitPHP_GitExe($this->GetProject());
   
$args = array(); $args = array();
   
switch ($this->format) { switch ($this->format) {
case GITPHP_COMPRESS_ZIP: case GITPHP_COMPRESS_ZIP:
$args[] = '--format=zip'; $args[] = '--format=zip';
break; break;
case GITPHP_COMPRESS_TAR: case GITPHP_COMPRESS_TAR:
case GITPHP_COMPRESS_BZ2: case GITPHP_COMPRESS_BZ2:
case GITPHP_COMPRESS_GZ: case GITPHP_COMPRESS_GZ:
$args[] = '--format=tar'; $args[] = '--format=tar';
break; break;
} }
   
$args[] = '--prefix=' . $this->GetPrefix(); $args[] = '--prefix=' . $this->GetPrefix();
$args[] = $this->gitObject->GetHash(); $args[] = $this->gitObject->GetHash();
   
if (!empty($this->path))  
$args[] = $this->path;  
   
$data = $exe->Execute(GIT_ARCHIVE, $args); $data = $exe->Execute(GIT_ARCHIVE, $args);
unset($exe); unset($exe);
   
switch ($this->format) { switch ($this->format) {
case GITPHP_COMPRESS_BZ2: case GITPHP_COMPRESS_BZ2:
$data = bzcompress($data, GitPHP_Config::GetInstance()->GetValue('compresslevel', 4)); $data = bzcompress($data, GitPHP_Config::GetInstance()->GetValue('compresslevel', 4));
break; break;
case GITPHP_COMPRESS_GZ: case GITPHP_COMPRESS_GZ:
$data = gzencode($data, GitPHP_Config::GetInstance()->GetValue('compresslevel', -1)); $data = gzencode($data, GitPHP_Config::GetInstance()->GetValue('compresslevel', -1));
break; break;
} }
   
return $data; return $data;
} }
   
/** /**
* FormatToExtension * FormatToExtension
* *
* Gets the extension to use for a particular format * Gets the extension to use for a particular format
* *
* @access public * @access public
* @static * @static
* @param string $format format to get extension for * @param string $format format to get extension for
* @return string file extension * @return string file extension
*/ */
public static function FormatToExtension($format) public static function FormatToExtension($format)
{ {
switch ($format) { switch ($format) {
case GITPHP_COMPRESS_TAR: case GITPHP_COMPRESS_TAR:
return 'tar'; return 'tar';
break; break;
case GITPHP_COMPRESS_BZ2: case GITPHP_COMPRESS_BZ2:
return 'tar.bz2'; return 'tar.bz2';
break; break;
case GITPHP_COMPRESS_GZ: case GITPHP_COMPRESS_GZ:
return 'tar.gz'; return 'tar.gz';
break; break;
case GITPHP_COMPRESS_ZIP: case GITPHP_COMPRESS_ZIP:
return 'zip'; return 'zip';
break; break;
} }
} }
   
/** /**
* SupportedFormats * SupportedFormats
* *
* Gets the supported formats for the archiver * Gets the supported formats for the archiver
* *
* @access public * @access public
* @static * @static
* @return array array of formats mapped to extensions * @return array array of formats mapped to extensions
*/ */
public static function SupportedFormats() public static function SupportedFormats()
{ {
$formats = array(); $formats = array();
   
$formats[GITPHP_COMPRESS_TAR] = GitPHP_Archive::FormatToExtension(GITPHP_COMPRESS_TAR); $formats[GITPHP_COMPRESS_TAR] = GitPHP_Archive::FormatToExtension(GITPHP_COMPRESS_TAR);
// TODO check for git > 1.4.3 for zip // TODO check for git > 1.4.3 for zip
$formats[GITPHP_COMPRESS_ZIP] = GitPHP_Archive::FormatToExtension(GITPHP_COMPRESS_ZIP); $formats[GITPHP_COMPRESS_ZIP] = GitPHP_Archive::FormatToExtension(GITPHP_COMPRESS_ZIP);
   
if (function_exists('bzcompress')) if (function_exists('bzcompress'))
$formats[GITPHP_COMPRESS_BZ2] = GitPHP_Archive::FormatToExtension(GITPHP_COMPRESS_BZ2); $formats[GITPHP_COMPRESS_BZ2] = GitPHP_Archive::FormatToExtension(GITPHP_COMPRESS_BZ2);
   
if (function_exists('gzencode')) if (function_exists('gzencode'))
$formats[GITPHP_COMPRESS_GZ] = GitPHP_Archive::FormatToExtension(GITPHP_COMPRESS_GZ); $formats[GITPHP_COMPRESS_GZ] = GitPHP_Archive::FormatToExtension(GITPHP_COMPRESS_GZ);
   
return $formats; return $formats;
} }
   
} }
   
<?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)) {
  $this->ReadHashPathsGit();
  } else {
  $this->ReadHashPathsRaw($this->GetTree());
  }
   
  GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);
  }
   
  /**
  * ReadHashPathsGit
  *
  * Reads hash to path mappings using git exe
  *
  * @access private
  */
  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;
} }
} }
} }
  }
GitPHP_Cache::GetInstance()->Set($this->GetCacheKey(), $this);  
  /**
  * ReadHashPathsRaw
  *
  * Reads hash to path mappings using raw objects
  *
  * @access private
  */
  private function ReadHashPathsRaw($tree)
  {
  if (!$tree) {
  return;
  }
   
  $contents = $tree->GetContents();
   
  foreach ($contents as $obj) {
  if ($obj instanceof GitPHP_Blob) {
  $hash = $obj->GetHash();
  $path = $obj->GetPath();
  $this->blobPaths[trim($path)] = $hash;
  } else if ($obj instanceof GitPHP_Tree) {
  $hash = $obj->GetHash();
  $path = $obj->GetPath();
  $this->treePaths[trim($path)] = $hash;
  $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);
   
$rawBlob = $exe->Execute(GIT_CAT_FILE, $fromBlob = $this->GetFromBlob();
array("blob", $this->fromHash)); $blob = $fromBlob->GetData(true);
$blob = explode("\n", $rawBlob);  
  $diffLines = '';
$diffLines = explode("\n", $exe->Execute(GIT_DIFF, if (function_exists('xdiff_string_diff')) {
array("-U0", $this->fromHash, $diffLines = explode("\n", $this->GetXDiff(0, false));
$this->toHash))); } 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 Pack * GitPHP Pack
* *
* Extracts data from a pack * Extracts data from a pack
* Based on code from Glip by Patrik Fimml * Based on code from Glip by Patrik Fimml
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2011 Christopher Han * @copyright Copyright (c) 2011 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
   
/** /**
* Pack class * Pack class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Pack class GitPHP_Pack
{ {
   
const OBJ_COMMIT = 1; const OBJ_COMMIT = 1;
const OBJ_TREE = 2; const OBJ_TREE = 2;
const OBJ_BLOB = 3; const OBJ_BLOB = 3;
const OBJ_TAG = 4; const OBJ_TAG = 4;
const OBJ_OFS_DELTA = 6; const OBJ_OFS_DELTA = 6;
const OBJ_REF_DELTA = 7; const OBJ_REF_DELTA = 7;
   
/** /**
* project * project
* *
* Stores the project internally * Stores the project internally
* *
* @access protected * @access protected
*/ */
protected $project; protected $project;
   
/** /**
* hash * hash
* *
* Stores the hash of the pack * Stores the hash of the pack
* *
* @access protected * @access protected
*/ */
protected $hash; protected $hash;
   
/** /**
  * offsetCache
  *
  * Caches object offsets
  *
  * @access protected
  */
  protected $offsetCache = array();
   
  /**
  * indexModified
  *
  * Stores the index file last modified time
  *
  * @access protected
  */
  protected $indexModified = 0;
   
  /**
* __construct * __construct
* *
* Instantiates object * Instantiates object
* *
* @access public * @access public
* @param mixed $project the project * @param mixed $project the project
* @param string $hash pack hash * @param string $hash pack hash
* @return mixed pack object * @return mixed pack object
* @throws Exception exception on invalid hash * @throws Exception exception on invalid hash
*/ */
public function __construct($project, $hash) public function __construct($project, $hash)
{ {
if (!(preg_match('/[0-9A-Fa-f]{40}/', $hash))) { if (!(preg_match('/[0-9A-Fa-f]{40}/', $hash))) {
throw new Exception(sprintf(__('Invalid hash %1$s'), $hash)); throw new Exception(sprintf(__('Invalid hash %1$s'), $hash));
} }
$this->hash = $hash; $this->hash = $hash;
$this->project = $project; $this->project = $project;
   
if (!file_exists($project->GetPath() . '/objects/pack/pack-' . $hash . '.idx')) { if (!file_exists($project->GetPath() . '/objects/pack/pack-' . $hash . '.idx')) {
throw new Exception('Pack index does not exist'); throw new Exception('Pack index does not exist');
} }
if (!file_exists($project->GetPath() . '/objects/pack/pack-' . $hash . '.pack')) { if (!file_exists($project->GetPath() . '/objects/pack/pack-' . $hash . '.pack')) {
throw new Exception('Pack file does not exist'); throw new Exception('Pack file does not exist');
} }
} }
   
/** /**
* GetHash * GetHash
* *
* Gets the hash * Gets the hash
* *
* @access public * @access public
* @return string object hash * @return string object hash
*/ */
public function GetHash() public function GetHash()
{ {
return $this->hash; return $this->hash;
} }
   
/** /**
* ContainsObject * ContainsObject
* *
* Checks if an object exists in the pack * Checks if an object exists in the pack
* *
* @access public * @access public
* @param string $hash object hash * @param string $hash object hash
* @return boolean true if object is in pack * @return boolean true if object is in pack
*/ */
public function ContainsObject($hash) public function ContainsObject($hash)
{ {
if (!preg_match('/[0-9a-fA-F]{40}/', $hash)) { if (!preg_match('/[0-9a-fA-F]{40}/', $hash)) {
return false; return false;
} }
   
return $this->FindPackedObject($hash) !== false; return $this->FindPackedObject($hash) !== false;
} }
   
/** /**
* FindPackedObject * FindPackedObject
* *
* Searches for an object's offset in the index * Searches for an object's offset in the index
* *
* @return int offset * @return int offset
* @param string $hash hash * @param string $hash hash
* @access private * @access private
*/ */
private function FindPackedObject($hash) private function FindPackedObject($hash)
{ {
if (!preg_match('/[0-9a-fA-F]{40}/', $hash)) { if (!preg_match('/[0-9a-fA-F]{40}/', $hash)) {
return false; return false;
} }
   
  $indexFile = $this->project->GetPath() . '/objects/pack/pack-' . $this->hash . '.idx';
  $mTime = filemtime($indexFile);
  if ($mTime > $this->indexModified) {
  $this->offsetCache = array();
  $this->indexModified = $mTime;
  }
   
  if (isset($this->offsetCache[$hash])) {
  return $this->offsetCache[$hash];
  }
   
$offset = false; $offset = false;
   
$index = fopen($this->project->GetPath() . '/objects/pack/pack-' . $this->hash . '.idx', 'rb'); $index = fopen($indexFile, 'rb');
flock($index, LOCK_SH); flock($index, LOCK_SH);
   
$magic = fread($index, 4); $magic = fread($index, 4);
if ($magic == "\xFFtOc") { if ($magic == "\xFFtOc") {
$version = GitPHP_Pack::fuint32($index); $version = GitPHP_Pack::fuint32($index);
if ($version == 2) { if ($version == 2) {
$offset = $this->SearchIndexV2($index, $hash); $offset = $this->SearchIndexV2($index, $hash);
} }
} else { } else {
$offset = $this->SearchIndexV1($index, $hash); $offset = $this->SearchIndexV1($index, $hash);
} }
flock($index, LOCK_UN); flock($index, LOCK_UN);
fclose($index); fclose($index);
  $this->offsetCache[$hash] = $offset;
return $offset; return $offset;
} }
   
/** /**
* SearchIndexV1 * SearchIndexV1
* *
* Seraches a version 1 index for a hash * Seraches a version 1 index for a hash
* *
* @access private * @access private
* @param resource $index file pointer to index * @param resource $index file pointer to index
* @param string $hash hash to find * @param string $hash hash to find
* @return int pack offset if found * @return int pack offset if found
*/ */
private function SearchIndexV1($index, $hash) private function SearchIndexV1($index, $hash)
{ {
/* /*
* index v1 struture: * index v1 struture:
* fanout table - 256*4 bytes * fanout table - 256*4 bytes
* offset/sha table - 24*count bytes (4 byte offset + 20 byte sha for each index) * offset/sha table - 24*count bytes (4 byte offset + 20 byte sha for each index)
*/ */
   
$binaryHash = pack('H40', $hash); $binaryHash = pack('H40', $hash);
   
/* /*
* get the start/end indices to search * get the start/end indices to search
* from the fanout table * from the fanout table
*/ */
list($low, $high) = $this->ReadFanout($index, $binaryHash, 0); list($low, $high) = $this->ReadFanout($index, $binaryHash, 0);
   
if ($low == $high) { if ($low == $high) {
return false; return false;
} }
   
/* /*
* binary serach for the index of the hash in the sha/offset listing * binary serach for the index of the hash in the sha/offset listing
* between cur and after from the fanout * between cur and after from the fanout
*/ */
while ($low <= $high) { while ($low <= $high) {
$mid = ($low + $high) >> 1; $mid = ($low + $high) >> 1;
fseek($index, 4*256 + 24*$mid); fseek($index, 4*256 + 24*$mid);
   
$off = GitPHP_Pack::fuint32($index); $off = GitPHP_Pack::fuint32($index);
$binName = fread($index, 20); $binName = fread($index, 20);
$name = bin2hex($binName); $name = bin2hex($binName);
   
  $this->offsetCache[$name] = $off;
   
$cmp = strcmp($hash, $name); $cmp = strcmp($hash, $name);
if ($cmp < 0) { if ($cmp < 0) {
$high = $mid - 1; $high = $mid - 1;
} else if ($cmp > 0) { } else if ($cmp > 0) {
$low = $mid + 1; $low = $mid + 1;
} else { } else {
return $off; return $off;
} }
} }
   
return false; return false;
} }
   
/** /**
* SearchIndexV2 * SearchIndexV2
* *
* Seraches a version 2 index for a hash * Seraches a version 2 index for a hash
* *
* @access private * @access private
* @param resource $index file pointer to index * @param resource $index file pointer to index
* @param string $hash hash to find * @param string $hash hash to find
* @return int pack offset if found * @return int pack offset if found
*/ */
private function SearchIndexV2($index, $hash) private function SearchIndexV2($index, $hash)
{ {
/* /*
* index v2 structure: * index v2 structure:
* magic and version - 2*4 bytes * magic and version - 2*4 bytes
* fanout table - 256*4 bytes * fanout table - 256*4 bytes
* sha listing - 20*count bytes * sha listing - 20*count bytes
* crc checksums - 4*count bytes * crc checksums - 4*count bytes
* offsets - 4*count bytes * offsets - 4*count bytes
*/ */
$binaryHash = pack('H40', $hash); $binaryHash = pack('H40', $hash);
   
/* /*
* get the start/end indices to search * get the start/end indices to search
* from the fanout table * from the fanout table
*/ */
list($low, $high) = $this->ReadFanout($index, $binaryHash, 8); list($low, $high) = $this->ReadFanout($index, $binaryHash, 8);
if ($low == $high) { if ($low == $high) {
return false; return false;
} }
   
/* /*
* get the object count from fanout[255] * get the object count from fanout[255]
*/ */
fseek($index, 8 + 4*255); fseek($index, 8 + 4*255);
$objectCount = GitPHP_Pack::fuint32($index); $objectCount = GitPHP_Pack::fuint32($index);
   
/* /*
* binary search for the index of the hash in the sha listing * binary search for the index of the hash in the sha listing
* between cur and after from the fanout * between cur and after from the fanout
*/ */
$objIndex = false; $objIndex = false;
while ($low <= $high) { while ($low <= $high) {
$mid = ($low + $high) >> 1; $mid = ($low + $high) >> 1;
fseek($index, 8 + 4*256 + 20*$mid); fseek($index, 8 + 4*256 + 20*$mid);
   
$binName = fread($index, 20); $binName = fread($index, 20);
$name = bin2hex($binName); $name = bin2hex($binName);
   
$cmp = strcmp($hash, $name); $cmp = strcmp($hash, $name);
   
if ($cmp < 0) { if ($cmp < 0) {
$high = $mid - 1; $high = $mid - 1;
} else if ($cmp > 0) { } else if ($cmp > 0) {
$low = $mid + 1; $low = $mid + 1;
} else { } else {
$objIndex = $mid; $objIndex = $mid;
break; break;
} }
} }
if ($objIndex === false) { if ($objIndex === false) {
return false; return false;
} }
   
/* /*
* get the offset from the same index in the offset table * get the offset from the same index in the offset table
*/ */
fseek($index, 8 + 4*256 + 24*$objectCount + 4*$objIndex); fseek($index, 8 + 4*256 + 24*$objectCount + 4*$objIndex);
$offset = GitPHP_Pack::fuint32($index); $offset = GitPHP_Pack::fuint32($index);
if ($offset & 0x80000000) { if ($offset & 0x80000000) {
throw new Exception('64-bit offsets not implemented'); throw new Exception('64-bit offsets not implemented');
} }
return $offset; return $offset;
} }
   
/** /**
* ReadFanout * ReadFanout
* *
* Finds the start/end index a hash will be located between, * Finds the start/end index a hash will be located between,
* acconding to the fanout table * acconding to the fanout table
* *
* @access private * @access private
* @param resource $index index file pointer * @param resource $index index file pointer
* @param string $binaryHash binary encoded hash to find * @param string $binaryHash binary encoded hash to find
* @param int $offset offset in the index file where the fanout table is located * @param int $offset offset in the index file where the fanout table is located
* @return array Range where object can be located * @return array Range where object can be located
*/ */
private function ReadFanout($index, $binaryHash, $offset) private function ReadFanout($index, $binaryHash, $offset)
{ {
/* /*
* fanout table has 255 4-byte integers * fanout table has 255 4-byte integers
* indexed by the first byte of the object name. * indexed by the first byte of the object name.
* the value at that index is the index at which objects * the value at that index is the index at which objects
* starting with that byte can be found * starting with that byte can be found
* (first level fan-out) * (first level fan-out)
*/ */
if ($binaryHash{0} == "\x00") { if ($binaryHash{0} == "\x00") {
$low = 0; $low = 0;
fseek($index, $offset); fseek($index, $offset);
$high = GitPHP_Pack::fuint32($index); $high = GitPHP_Pack::fuint32($index);
} else { } else {
fseek($index, $offset + (ord($binaryHash{0}) - 1) * 4); fseek($index, $offset + (ord($binaryHash{0}) - 1) * 4);
$low = GitPHP_Pack::fuint32($index); $low = GitPHP_Pack::fuint32($index);
$high = GitPHP_Pack::fuint32($index); $high = GitPHP_Pack::fuint32($index);
} }
return array($low, $high); return array($low, $high);
} }
   
/** /**
* GetObject * GetObject
* *
* Extracts an object from the pack * Extracts an object from the pack
* *
* @access public * @access public
* @param string $hash hash of object to extract * @param string $hash hash of object to extract
* @param int $type output parameter, returns the type of the object * @param int $type output parameter, returns the type of the object
* @return string object content, or false if not found * @return string object content, or false if not found
*/ */
public function GetObject($hash, &$type = 0) public function GetObject($hash, &$type = 0)
{ {
$offset = $this->FindPackedObject($hash); $offset = $this->FindPackedObject($hash);
if ($offset === false) { if ($offset === false) {
return false; return false;
} }
   
$pack = fopen($this->project->GetPath() . '/objects/pack/pack-' . $this->hash . '.pack', 'rb'); $pack = fopen($this->project->GetPath() . '/objects/pack/pack-' . $this->hash . '.pack', 'rb');
flock($pack, LOCK_SH); flock($pack, LOCK_SH);
   
$magic = fread($pack, 4); $magic = fread($pack, 4);
$version = GitPHP_Pack::fuint32($pack); $version = GitPHP_Pack::fuint32($pack);
if ($magic != 'PACK' || $version != 2) { if ($magic != 'PACK' || $version != 2) {
flock($pack, LOCK_UN); flock($pack, LOCK_UN);
fclose($pack); fclose($pack);
throw new Exception('Unsupported pack format'); throw new Exception('Unsupported pack format');
} }
   
list($type, $data) = $this->UnpackObject($pack, $offset); list($type, $data) = $this->UnpackObject($pack, $offset);
   
flock($pack, LOCK_UN); flock($pack, LOCK_UN);
fclose($pack); fclose($pack);
return $data; return $data;
} }
   
/** /**
* UnpackObject * UnpackObject
* *
* Extracts an object at an offset * Extracts an object at an offset
* *
* @access private * @access private
* @param resource $pack pack file pointer * @param resource $pack pack file pointer
* @param int $offset object offset * @param int $offset object offset
* @return array object type and data * @return array object type and data
*/ */
private function UnpackObject($pack, $offset) private function UnpackObject($pack, $offset)
{ {
fseek($pack, $offset); fseek($pack, $offset);
   
/* /*
* object header: * object header:
* first byte is the type (high 3 bits) and low byte of size (lower 4 bits) * first byte is the type (high 3 bits) and low byte of size (lower 4 bits)
* subsequent bytes each have 7 next higher bits of the size (little endian) * subsequent bytes each have 7 next higher bits of the size (little endian)
* most significant bit is either 1 or 0 to indicate whether the next byte * most significant bit is either 1 or 0 to indicate whether the next byte
* should be read as part of the size. 1 means continue reading the size, * should be read as part of the size. 1 means continue reading the size,
* 0 means the data is starting * 0 means the data is starting
*/ */
$c = ord(fgetc($pack)); $c = ord(fgetc($pack));
$type = ($c >> 4) & 0x07; $type = ($c >> 4) & 0x07;
$size = $c & 0x0F; $size = $c & 0x0F;
for ($i = 4; $c & 0x80; $i += 7) { for ($i = 4; $c & 0x80; $i += 7) {
$c = ord(fgetc($pack)); $c = ord(fgetc($pack));
$size |= (($c & 0x7f) << $i); $size |= (($c & 0x7f) << $i);
} }
   
if ($type == GitPHP_Pack::OBJ_COMMIT || $type == GitPHP_Pack::OBJ_TREE || $type == GitPHP_Pack::OBJ_BLOB || $type == GitPHP_Pack::OBJ_TAG) { if ($type == GitPHP_Pack::OBJ_COMMIT || $type == GitPHP_Pack::OBJ_TREE || $type == GitPHP_Pack::OBJ_BLOB || $type == GitPHP_Pack::OBJ_TAG) {
/* /*
* regular gzipped object data * regular gzipped object data
*/ */
return array($type, gzuncompress(fread($pack, $size+512), $size)); return array($type, gzuncompress(fread($pack, $size+512), $size));
} else if ($type == GitPHP_Pack::OBJ_OFS_DELTA) { } else if ($type == GitPHP_Pack::OBJ_OFS_DELTA) {
/* /*
* delta of an object at offset * delta of an object at offset
*/ */
$buf = fread($pack, $size+512+20); $buf = fread($pack, $size+512+20);
   
/* /*
* read the base object offset * read the base object offset
* each subsequent byte's 7 least significant bits * each subsequent byte's 7 least significant bits
* are part of the offset in decreasing significance per byte * are part of the offset in decreasing significance per byte
* (opposite of other places) * (opposite of other places)
* most significant bit is a flag indicating whether to read the * most significant bit is a flag indicating whether to read the
* next byte as part of the offset * next byte as part of the offset
*/ */
$pos = 0; $pos = 0;
$off = -1; $off = -1;
do { do {
$off++; $off++;
$c = ord($buf{$pos++}); $c = ord($buf{$pos++});
$off = ($off << 7) + ($c & 0x7f); $off = ($off << 7) + ($c & 0x7f);
} while ($c & 0x80); } while ($c & 0x80);
   
/* /*
* next read the compressed delta data * next read the compressed delta data
*/ */
$delta = gzuncompress(substr($buf, $pos), $size); $delta = gzuncompress(substr($buf, $pos), $size);
unset($buf); unset($buf);
   
$baseOffset = $offset - $off; $baseOffset = $offset - $off;
if ($baseOffset > 0) { if ($baseOffset > 0) {
/* /*
* read base object at offset and apply delta to it * read base object at offset and apply delta to it
*/ */
list($type, $base) = $this->UnpackObject($pack, $baseOffset); list($type, $base) = $this->UnpackObject($pack, $baseOffset);
$data = GitPHP_Pack::ApplyDelta($delta, $base); $data = GitPHP_Pack::ApplyDelta($delta, $base);
return array($type, $data); return array($type, $data);
} }
} else if ($type == GitPHP_Pack::OBJ_REF_DELTA) { } else if ($type == GitPHP_Pack::OBJ_REF_DELTA) {
/* /*
* delta of object with hash * delta of object with hash
*/ */
   
/* /*
* first the base object's hash * first the base object's hash
* load that object * load that object
*/ */
$hash = fread($pack, 20); $hash = fread($pack, 20);
$hash = bin2hex($hash); $hash = bin2hex($hash);
$base = $this->project->GetObject($hash, $type); $base = $this->project->GetObject($hash, $type);
   
/* /*
* then the gzipped delta data * then the gzipped delta data
*/ */
$delta = gzuncompress(fread($pack, $size + 512), $size); $delta = gzuncompress(fread($pack, $size + 512), $size);
   
$data = GitPHP_Pack::ApplyDelta($delta, $base); $data = GitPHP_Pack::ApplyDelta($delta, $base);
   
return array($type, $data); return array($type, $data);
} }
   
return false; return false;
} }
   
/** /**
* ApplyDelta * ApplyDelta
* *
* Applies a binary delta to a base object * Applies a binary delta to a base object
* *
* @static * @static
* @access private * @access private
* @param string $delta delta string * @param string $delta delta string
* @param string $base base object data * @param string $base base object data
* @return string patched content * @return string patched content
*/ */
private static function ApplyDelta($delta, $base) private static function ApplyDelta($delta, $base)
{ {
/* /*
* algorithm from patch-delta.c * algorithm from patch-delta.c
*/ */
$pos = 0; $pos = 0;
$baseSize = GitPHP_Pack::ParseVarInt($delta, $pos); $baseSize = GitPHP_Pack::ParseVarInt($delta, $pos);
$resultSize = GitPHP_Pack::ParseVarInt($delta, $pos); $resultSize = GitPHP_Pack::ParseVarInt($delta, $pos);
   
$data = ''; $data = '';
$deltalen = strlen($delta); $deltalen = strlen($delta);
while ($pos < $deltalen) { while ($pos < $deltalen) {
$opcode = ord($delta{$pos++}); $opcode = ord($delta{$pos++});
if ($opcode & 0x80) { if ($opcode & 0x80) {
$off = 0; $off = 0;
if ($opcode & 0x01) $off = ord($delta{$pos++}); if ($opcode & 0x01) $off = ord($delta{$pos++});
if ($opcode & 0x02) $off |= ord($delta{$pos++}) << 8; if ($opcode & 0x02) $off |= ord($delta{$pos++}) << 8;
if ($opcode & 0x04) $off |= ord($delta{$pos++}) << 16; if ($opcode & 0x04) $off |= ord($delta{$pos++}) << 16;
if ($opcode & 0x08) $off |= ord($delta{$pos++}) << 24; if ($opcode & 0x08) $off |= ord($delta{$pos++}) << 24;
$len = 0; $len = 0;
if ($opcode & 0x10) $len = ord($delta{$pos++}); if ($opcode & 0x10) $len = ord($delta{$pos++});
if ($opcode & 0x20) $len |= ord($delta{$pos++}) << 8; if ($opcode & 0x20) $len |= ord($delta{$pos++}) << 8;
if ($opcode & 0x40) $len |= ord($delta{$pos++}) << 16; if ($opcode & 0x40) $len |= ord($delta{$pos++}) << 16;
if ($len == 0) $len = 0x10000; if ($len == 0) $len = 0x10000;
$data .= substr($base, $off, $len); $data .= substr($base, $off, $len);
} else if ($opcode > 0) { } else if ($opcode > 0) {
$data .= substr($delta, $pos, $opcode); $data .= substr($delta, $pos, $opcode);
$pos += $opcode; $pos += $opcode;
} }
} }
return $data; return $data;
} }
   
/** /**
* ParseVarInt * ParseVarInt
* *
* Reads a git-style packed variable length integer * Reads a git-style packed variable length integer
* sequence of bytes, where each byte's 7 less significant bits * sequence of bytes, where each byte's 7 less significant bits
* are pieces of the int in increasing significance for each byte (little endian) * are pieces of the int in increasing significance for each byte (little endian)
* the most significant bit of each byte is a flag whether to continue * the most significant bit of each byte is a flag whether to continue
* reading bytes or not * reading bytes or not
* *
* @access private * @access private
* @static * @static
* @param string $str packed data string * @param string $str packed data string
* @param int $pos position in string to read from * @param int $pos position in string to read from
* @return int parsed integer * @return int parsed integer
*/ */
private static function ParseVarInt($str, &$pos=0) private static function ParseVarInt($str, &$pos=0)
{ {
$ret = 0; $ret = 0;
$byte = 0x80; $byte = 0x80;
for ($shift = 0; $byte & 0x80; $shift += 7) { for ($shift = 0; $byte & 0x80; $shift += 7) {
$byte = ord($str{$pos++}); $byte = ord($str{$pos++});
$ret |= (($byte & 0x7F) << $shift); $ret |= (($byte & 0x7F) << $shift);
} }
return $ret; return $ret;
} }
   
/** /**
* uint32 * uint32
* *
* Unpacks a packed 32 bit integer * Unpacks a packed 32 bit integer
* *
* @static * @static
* @access private * @access private
* @return int integer * @return int integer
* @param string $str binary data * @param string $str binary data
*/ */
private static function uint32($str) private static function uint32($str)
{ {
$a = unpack('Nx', substr($str, 0, 4)); $a = unpack('Nx', substr($str, 0, 4));
return $a['x']; return $a['x'];
} }
   
/** /**
* fuint32 * fuint32
* *
* Reads and unpacks the next 32 bit integer * Reads and unpacks the next 32 bit integer
* *
* @static * @static
* @access private * @access private
* @return int integer * @return int integer
* @param resource $handle file handle * @param resource $handle file handle
*/ */
private static function fuint32($handle) private static function fuint32($handle)
{ {
return GitPHP_Pack::uint32(fread($handle, 4)); return GitPHP_Pack::uint32(fread($handle, 4));
} }
} }
   
<?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
  *
  * Stores the project root internally
  *
  * @access protected
  */
  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 $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($project) public function __construct($projectRoot, $project)
{ {
  $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)
{ {
$projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot')); $realProjectRoot = realpath($this->projectRoot);
  $path = $this->projectRoot . $project;
$realProjectRoot = realpath($projectRoot);  
$path = $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
  *
  * Gets the project root
  *
  * @access public
  * @return string the project root
  */
  public function GetProjectRoot()
  {
  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()
{ {
$from = array( $project = $this->project;
'/',  
'.git' if (substr($project, -4) == '.git')
); $project = substr($project, 0, -4);
$to = array(  
'-', return GitPHP_Util::MakeSlug($project);
''  
);  
return str_replace($from, $to, $this->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()
{ {
$projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot')); return $this->projectRoot . $this->project;
   
return $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'); $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)) {
  return $this->GetTagsGit($count);
  } else {
  return $this->GetTagsRaw($count);
  }
  }
   
  /**
  * GetTagsGit
  *
  * Gets list of tags for this project by age descending using git executable
  *
  * @access private
  * @param integer $count number of tags to load
  * @return array array of tags
  */
  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
  *
  * Gets list of tags for this project by age descending using raw git objects
  *
  * @access private
  * @param integer $count number of tags to load
  * @return array array of tags
  */
  private function GetTagsRaw($count = 0)
  {
  $tags = $this->tags;
  usort($tags, array('GitPHP_Tag', 'CompareCreationEpoch'));
   
  if (($count > 0) && (count($tags) > $count)) {
  $tags = array_slice($tags, 0, $count);
  }
   
  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)) {
  return $this->GetHeadsGit($count);
  } else {
  return $this->GetHeadsRaw($count);
  }
  }
   
  /**
  * GetHeadsGit
  *
  * Gets the list of sorted heads using the git executable
  *
  * @access private
  * @param integer $count number of tags to load
  * @return array array of heads
  */
  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
  *
  * Gets the list of sorted heads using raw git objects
  *
  * @access private
  * @param integer $count number of tags to load
  * @return array array of heads
  */
  private function GetHeadsRaw($count = 0)
  {
  $heads = $this->heads;
  usort($heads, array('GitPHP_Head', 'CompareAge'));
   
  if (($count > 0) && (count($heads) > $count)) {
  $heads = array_slice($heads, 0, $count);
  }
  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 ProjectListArray * GitPHP ProjectListArray
* *
* Lists all projects in a multidimensional array * Lists all projects in a multidimensional array
* *
* @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 . 'ProjectListBase.class.php'); require_once(GITPHP_GITOBJECTDIR . 'ProjectListBase.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Project.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Project.class.php');
   
/** /**
* ProjectListArray class * ProjectListArray class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_ProjectListArray extends GitPHP_ProjectListBase class GitPHP_ProjectListArray extends GitPHP_ProjectListBase
{ {
   
/** /**
* __construct * __construct
* *
* constructor * constructor
* *
* @param mixed $projectArray array to read * @param mixed $projectArray array to read
* @throws Exception if parameter is not an array * @throws Exception if parameter is not an array
* @access public * @access public
*/ */
public function __construct($projectArray) public function __construct($projectArray)
{ {
if (!is_array($projectArray)) { if (!is_array($projectArray)) {
throw new Exception('An array of projects is required'); throw new Exception('An array of projects is required');
} }
   
$this->projectConfig = $projectArray; $this->projectConfig = $projectArray;
   
parent::__construct(); parent::__construct();
} }
   
/** /**
* PopulateProjects * PopulateProjects
* *
* Populates the internal list of projects * Populates the internal list of projects
* *
* @access protected * @access protected
* @throws Exception if file cannot be read * @throws Exception if file cannot be read
*/ */
protected function PopulateProjects() protected function PopulateProjects()
{ {
  $projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot'));
   
foreach ($this->projectConfig as $proj => $projData) { foreach ($this->projectConfig as $proj => $projData) {
try { try {
if (is_string($projData)) { if (is_string($projData)) {
// Just flat array of project paths // Just flat array of project paths
$projObj = new GitPHP_Project($projData); $projObj = new GitPHP_Project($projectRoot, $projData);
$this->projects[$projData] = $projObj; $this->projects[$projData] = $projObj;
} else if (is_array($projData)) { } else if (is_array($projData)) {
if (is_string($proj) && !empty($proj)) { if (is_string($proj) && !empty($proj)) {
// Project key pointing to data array // Project key pointing to data array
$projObj = new GitPHP_Project($proj); $projObj = new GitPHP_Project($projectRoot, $proj);
$this->projects[$proj] = $projObj; $this->projects[$proj] = $projObj;
$this->ApplyProjectSettings($proj, $projData); $this->ApplyProjectSettings($proj, $projData);
} else if (isset($projData['project'])) { } else if (isset($projData['project'])) {
// List of data arrays with projects inside // List of data arrays with projects inside
$projObj = new GitPHP_Project($projData['project']); $projObj = new GitPHP_Project($projectRoot, $projData['project']);
$this->projects[$projData['project']] = $projObj; $this->projects[$projData['project']] = $projObj;
$this->ApplyProjectSettings(null, $projData); $this->ApplyProjectSettings(null, $projData);
} }
} }
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
} }
   
} }
   
<?php <?php
/** /**
* GitPHP ProjectListArrayLegacy * GitPHP ProjectListArrayLegacy
* *
* Lists all projects in a multidimensional array * Lists all projects in a multidimensional array
* Legacy array format * Legacy array format
* *
* @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 . 'ProjectListBase.class.php'); require_once(GITPHP_GITOBJECTDIR . 'ProjectListBase.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Project.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Project.class.php');
   
define('GITPHP_NO_CATEGORY', 'none'); define('GITPHP_NO_CATEGORY', 'none');
   
/** /**
* ProjectListArrayLegacy class * ProjectListArrayLegacy class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_ProjectListArrayLegacy extends GitPHP_ProjectListBase class GitPHP_ProjectListArrayLegacy extends GitPHP_ProjectListBase
{ {
   
/** /**
* __construct * __construct
* *
* constructor * constructor
* *
* @param mixed $projectArray array to read * @param mixed $projectArray array to read
* @throws Exception if parameter is not an array * @throws Exception if parameter is not an array
* @access public * @access public
*/ */
public function __construct($projectArray) public function __construct($projectArray)
{ {
if (!is_array($projectArray)) { if (!is_array($projectArray)) {
throw new Exception('An array of projects is required.'); throw new Exception('An array of projects is required.');
} }
   
$this->projectConfig = $projectArray; $this->projectConfig = $projectArray;
   
parent::__construct(); parent::__construct();
} }
   
/** /**
* PopulateProjects * PopulateProjects
* *
* Populates the internal list of projects * Populates the internal list of projects
* *
* @access protected * @access protected
* @throws Exception if file cannot be read * @throws Exception if file cannot be read
*/ */
protected function PopulateProjects() protected function PopulateProjects()
{ {
  $projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot'));
   
foreach ($this->projectConfig as $cat => $plist) { foreach ($this->projectConfig as $cat => $plist) {
if (is_array($plist)) { if (is_array($plist)) {
foreach ($plist as $pname => $ppath) { foreach ($plist as $pname => $ppath) {
try { try {
$projObj = new GitPHP_Project($ppath); $projObj = new GitPHP_Project($projectRoot, $ppath);
if ($cat != GITPHP_NO_CATEGORY) if ($cat != GITPHP_NO_CATEGORY)
$projObj->SetCategory($cat); $projObj->SetCategory($cat);
$this->projects[$ppath] = $projObj; $this->projects[$ppath] = $projObj;
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
} }
} }
} }
   
} }
   
<?php <?php
/** /**
* GitPHP ProjectListDirectory * GitPHP ProjectListDirectory
* *
* Lists all projects in a given directory * Lists all projects in a given directory
* *
* @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_INCLUDEDIR . 'Config.class.php'); require_once(GITPHP_INCLUDEDIR . 'Config.class.php');
require_once(GITPHP_GITOBJECTDIR . 'ProjectListBase.class.php'); require_once(GITPHP_GITOBJECTDIR . 'ProjectListBase.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Project.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Project.class.php');
   
/** /**
* ProjectListDirectory class * ProjectListDirectory class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_ProjectListDirectory extends GitPHP_ProjectListBase class GitPHP_ProjectListDirectory extends GitPHP_ProjectListBase
{ {
/** /**
* projectDir * projectDir
* *
* Stores projectlist directory internally * Stores projectlist directory internally
* *
* @access protected * @access protected
*/ */
protected $projectDir; protected $projectDir;
   
/** /**
* __construct * __construct
* *
* constructor * constructor
* *
* @param string $projectDir directory to search * @param string $projectDir directory to search
* @throws Exception if parameter is not a directory * @throws Exception if parameter is not a directory
* @access public * @access public
*/ */
public function __construct($projectDir) public function __construct($projectDir)
{ {
if (!is_dir($projectDir)) { if (!is_dir($projectDir)) {
throw new Exception(sprintf(__('%1$s is not a directory'), $projectDir)); throw new Exception(sprintf(__('%1$s is not a directory'), $projectDir));
} }
   
$this->projectDir = GitPHP_Util::AddSlash($projectDir); $this->projectDir = GitPHP_Util::AddSlash($projectDir);
   
parent::__construct(); parent::__construct();
} }
   
/** /**
* PopulateProjects * PopulateProjects
* *
* Populates the internal list of projects * Populates the internal list of projects
* *
* @access protected * @access protected
*/ */
protected function PopulateProjects() protected function PopulateProjects()
{ {
$this->RecurseDir($this->projectDir); $this->RecurseDir($this->projectDir);
} }
   
/** /**
* RecurseDir * RecurseDir
* *
* Recursively searches for projects * Recursively searches for projects
* *
* @param string $dir directory to recurse into * @param string $dir directory to recurse into
*/ */
private function RecurseDir($dir) private function RecurseDir($dir)
{ {
if (!is_dir($dir)) if (!is_dir($dir))
return; return;
   
if ($dh = opendir($dir)) { if ($dh = opendir($dir)) {
$trimlen = strlen($this->projectDir) + 1; $trimlen = strlen($this->projectDir) + 1;
while (($file = readdir($dh)) !== false) { while (($file = readdir($dh)) !== false) {
$fullPath = $dir . '/' . $file; $fullPath = $dir . '/' . $file;
if ((strpos($file, '.') !== 0) && is_dir($fullPath)) { if ((strpos($file, '.') !== 0) && is_dir($fullPath)) {
if (is_file($fullPath . '/HEAD')) { if (is_file($fullPath . '/HEAD')) {
$projectPath = substr($fullPath, $trimlen); $projectPath = substr($fullPath, $trimlen);
try { try {
$proj = new GitPHP_Project($projectPath); $proj = new GitPHP_Project($this->projectDir, $projectPath);
$proj->SetCategory(trim(substr($dir, strlen($this->projectDir)), '/')); $proj->SetCategory(trim(substr($dir, strlen($this->projectDir)), '/'));
if ((!GitPHP_Config::GetInstance()->GetValue('exportedonly', false)) || $proj->GetDaemonEnabled()) { if ((!GitPHP_Config::GetInstance()->GetValue('exportedonly', false)) || $proj->GetDaemonEnabled()) {
$this->projects[$projectPath] = $proj; $this->projects[$projectPath] = $proj;
} }
} catch (Exception $e) { } catch (Exception $e) {
} }
} else { } else {
$this->RecurseDir($fullPath); $this->RecurseDir($fullPath);
} }
} }
} }
closedir($dh); closedir($dh);
} }
} }
   
} }
   
<?php <?php
/** /**
* GitPHP ProjectListFile * GitPHP ProjectListFile
* *
* Lists all projects in a given file * Lists all projects in a given file
* *
* @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_INCLUDEDIR . 'Config.class.php'); require_once(GITPHP_INCLUDEDIR . 'Config.class.php');
require_once(GITPHP_GITOBJECTDIR . 'ProjectListBase.class.php'); require_once(GITPHP_GITOBJECTDIR . 'ProjectListBase.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Project.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Project.class.php');
   
/** /**
* ProjectListFile class * ProjectListFile class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_ProjectListFile extends GitPHP_ProjectListBase class GitPHP_ProjectListFile extends GitPHP_ProjectListBase
{ {
/** /**
* __construct * __construct
* *
* constructor * constructor
* *
* @param string $projectFile file to read * @param string $projectFile file to read
* @throws Exception if parameter is not a readable file * @throws Exception if parameter is not a readable file
* @access public * @access public
*/ */
public function __construct($projectFile) public function __construct($projectFile)
{ {
if (!(is_string($projectFile) && is_file($projectFile))) { if (!(is_string($projectFile) && is_file($projectFile))) {
throw new Exception(sprintf(__('%1$s is not a file'), $projectFile)); throw new Exception(sprintf(__('%1$s is not a file'), $projectFile));
} }
   
$this->projectConfig = $projectFile; $this->projectConfig = $projectFile;
   
parent::__construct(); parent::__construct();
} }
   
/** /**
* PopulateProjects * PopulateProjects
* *
* Populates the internal list of projects * Populates the internal list of projects
* *
* @access protected * @access protected
* @throws Exception if file cannot be read * @throws Exception if file cannot be read
*/ */
protected function PopulateProjects() protected function PopulateProjects()
{ {
if (!($fp = fopen($this->projectConfig, 'r'))) { if (!($fp = fopen($this->projectConfig, 'r'))) {
throw new Exception(sprintf(__('Failed to open project list file %1$s'), $this->projectConfig)); throw new Exception(sprintf(__('Failed to open project list file %1$s'), $this->projectConfig));
} }
   
$projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot')); $projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot'));
   
while (!feof($fp) && ($line = fgets($fp))) { while (!feof($fp) && ($line = fgets($fp))) {
if (preg_match('/^([^\s]+)(\s.+)?$/', $line, $regs)) { if (preg_match('/^([^\s]+)(\s.+)?$/', $line, $regs)) {
if (is_file($projectRoot . $regs[1] . '/HEAD')) { if (is_file($projectRoot . $regs[1] . '/HEAD')) {
try { try {
$projObj = new GitPHP_Project($regs[1]); $projObj = new GitPHP_Project($projectRoot, $regs[1]);
if (isset($regs[2]) && !empty($regs[2])) { if (isset($regs[2]) && !empty($regs[2])) {
$projOwner = trim($regs[2]); $projOwner = trim($regs[2]);
if (!empty($projOwner)) { if (!empty($projOwner)) {
$projObj->SetOwner($projOwner); $projObj->SetOwner($projOwner);
} }
} }
$this->projects[$regs[1]] = $projObj; $this->projects[$regs[1]] = $projObj;
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
} }
} }
   
fclose($fp); fclose($fp);
} }
   
} }
   
<?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
  *
  * Gets tag's creation epoch
  * (tagger epoch, or committer epoch for light tags)
  *
  * @access public
  * @return string creation epoch
  */
  public function GetCreationEpoch()
  {
  if (!$this->dataRead)
  $this->ReadData();
   
  if ($this->LightTag())
  return $this->GetCommit()->GetCommitterEpoch();
  else
  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
  *
  * Compares to tags by creation epoch
  *
  * @access public
  * @static
  * @param mixed $a first tag
  * @param mixed $b second tag
  * @return integer comparison result
  */
  public static function CompareCreationEpoch($a, $b)
  {
  $aEpoch = $a->GetCreationEpoch();
  $bEpoch = $b->GetCreationEpoch();
   
  if ($aEpoch == $bEpoch) {
  return 0;
  }
   
  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&