Allow side by side commitdiff TOC to show the chosen diff and hide the
Allow side by side commitdiff TOC to show the chosen diff and hide the
rest

/* /*
* gitphp.css * gitphp.css
* *
* GitPHP layout functional stylesheet * GitPHP layout functional 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
*/ */
.monospace { .monospace {
font-family: monospace; font-family: monospace;
} }
   
   
/* /*
* Title bar * Title bar
* (main header with commit message) * (main header with commit message)
*/ */
div.title a.title { div.title a.title {
display: block; display: block;
} }
   
   
/* /*
* Full log view * Full log view
*/ */
div.log_body { div.log_body {
padding: 8px 8px 8px 150px; padding: 8px 8px 8px 150px;
} }
span.age { span.age {
position: relative; position: relative;
float: left; float: left;
width: 142px; width: 142px;
} }
div.log_link { div.log_link {
margin: -5px 0px; margin: -5px 0px;
padding: 0px 8px; padding: 0px 8px;
position: relative; position: relative;
float: left; float: left;
width: 136px; width: 136px;
} }
   
   
/* /*
* Diff display * Diff display
*/ */
.pre { .pre {
white-space: pre; white-space: pre;
} }
   
   
/* /*
* Search box * Search box
*/ */
div.search { div.search {
margin: 4px 8px; margin: 4px 8px;
position: absolute; position: absolute;
top: 56px; top: 56px;
right: 12px right: 12px
} }
   
   
/* /*
* Badges * Badges
*/ */
a.rss_logo { a.rss_logo {
float: right; float: right;
} }
   
img.logo { img.logo {
float: right; float: right;
} }
   
   
/* /*
* Language selector * Language selector
*/ */
div.lang_select { div.lang_select {
width: 300px; width: 300px;
float: right; float: right;
text-align: right; text-align: right;
padding-right: 10px; padding-right: 10px;
} }
   
   
/* /*
* Blob/blame display * Blob/blame display
*/ */
table.code td.num { table.code td.num {
white-space: pre; white-space: pre;
} }
   
table.code td.codeline { table.code td.codeline {
white-space: pre; white-space: pre;
} }
   
table.code td.author, table.code td.date { table.code td.author, table.code td.date {
white-space: nowrap; white-space: nowrap;
} }
   
   
/* /*
* Blob view * Blob view
*/ */
td#blameData { td#blameData {
white-space: nowrap; white-space: nowrap;
} }
   
/* /*
* File search view * File search view
*/ */
span.matchline { span.matchline {
white-space: pre; white-space: pre;
} }
   
/* /*
* side-by-side-diff diff * side-by-side-diff diff
*/ */
table.diffTable { table.diffTable {
border-spacing:0px; border-spacing:0px;
width: 100%; width: 100%;
white-space: pre-wrap; white-space: pre-wrap;
} }
   
table.diffTable td { table.diffTable td {
width: 50%; width: 50%;
} }
   
  /*
  * side-by-side commitdiff
  */
  div.commitDiffSBS div.SBSTOC .showAll
  {
  display: none;
  }
   
   
/* /*
* Geshi styles * Geshi styles
* These are generated by Geshi, don't change them * These are generated by Geshi, don't change them
*/ */
#blobData .de1, #blobData .de2 { #blobData .de1, #blobData .de2 {
font: normal normal 1em/1.2em monospace; font: normal normal 1em/1.2em monospace;
margin: 0; margin: 0;
padding: 0; padding: 0;
background: none; background: none;
vertical-align: top; vertical-align: top;
} }
   
#blobData { #blobData {
font-family: monospace; font-family: monospace;
} }
   
#blobData li, #blobData .li1 { #blobData li, #blobData .li1 {
font-weight: normal; font-weight: normal;
vertical-align: top; vertical-align: top;
} }
   
/* /*
* 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.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
  */
  div.commitDiffSBS
  {
  width: 100%;
  border-top: 2px solid #edece6;
  }
   
  div.commitDiffSBS div.SBSTOC
  {
  float: left;
  width: 19%;
  word-wrap: break-word;
  }
   
  div.commitDiffSBS div.SBSTOC a
  {
  text-decoration: none;
  }
   
  div.commitDiffSBS div.SBSTOC ul
  {
  margin-left: 8px;
  padding-left: 8px;
  }
   
  div.commitDiffSBS div.SBSTOC .listcount
  {
  list-style-type: none;
  }
   
  div.commitDiffSBS div.SBSTOC .activeItem
  {
  background-color: #edece6;
  }
   
  div.commitDiffSBS .SBSContent
  {
  float: right;
  width: 80%;
  border-left: 1px solid #edece6;
  }
   
  div.commitDiffSBS .SBSFooter
  {
  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 $backslash true to also check for backslash (windows paths) * @param $backslash true to also check for backslash (windows paths)
* @return string $path with a trailing slash * @return string $path with a trailing slash
*/ */
public static function AddSlash($path, $backslash = true) public static function AddSlash($path, $backslash = true)
{ {
if (empty($path)) if (empty($path))
return $path; return $path;
   
$end = substr($path, -1); $end = substr($path, -1);
   
if (!(($end == '/') || ($backslash && (strtoupper(substr(PHP_OS, 0, 3))) && ($end == '\\')))) if (!(( ($end == '/') || ($end == ':')) || ($backslash && (strtoupper(substr(PHP_OS, 0, 3))) && ($end == '\\'))))
$path .= '/'; $path .= '/';
   
return $path; return $path;
} }
   
} }
   
<?php <?php
/** /**
* GitPHP Controller Blob * GitPHP Controller Blob
* *
* Controller for displaying a blob * Controller for displaying a 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 Controller * @subpackage Controller
*/ */
   
/** /**
* Blob controller class * Blob controller class
* *
* @package GitPHP * @package GitPHP
* @subpackage Controller * @subpackage Controller
*/ */
class GitPHP_Controller_Blob extends GitPHP_ControllerBase class GitPHP_Controller_Blob extends GitPHP_ControllerBase
{ {
   
/** /**
* __construct * __construct
* *
* Constructor * Constructor
* *
* @access public * @access public
* @return controller * @return controller
*/ */
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
if (!$this->project) { if (!$this->project) {
throw new GitPHP_MessageException(__('Project is required'), true); throw new GitPHP_MessageException(__('Project is required'), true);
} }
} }
   
/** /**
* GetTemplate * GetTemplate
* *
* Gets the template for this controller * Gets the template for this controller
* *
* @access protected * @access protected
* @return string template filename * @return string template filename
*/ */
protected function GetTemplate() protected function GetTemplate()
{ {
if (isset($this->params['plain']) && $this->params['plain']) if (isset($this->params['plain']) && $this->params['plain'])
return 'blobplain.tpl'; return 'blobplain.tpl';
return 'blob.tpl'; return 'blob.tpl';
} }
   
/** /**
* GetCacheKey * GetCacheKey
* *
* Gets the cache key for this controller * Gets the cache key for this controller
* *
* @access protected * @access protected
* @return string cache key * @return string cache key
*/ */
protected function GetCacheKey() protected function GetCacheKey()
{ {
return (isset($this->params['hashbase']) ? $this->params['hashbase'] : '') . '|' . (isset($this->params['hash']) ? $this->params['hash'] : '') . '|' . (isset($this->params['file']) ? sha1($this->params['file']) : ''); return (isset($this->params['hashbase']) ? $this->params['hashbase'] : '') . '|' . (isset($this->params['hash']) ? $this->params['hash'] : '') . '|' . (isset($this->params['file']) ? sha1($this->params['file']) : '');
} }
   
/** /**
* GetName * GetName
* *
* Gets the name of this controller's action * Gets the name of this controller's action
* *
* @access public * @access public
* @param boolean $local true if caller wants the localized action name * @param boolean $local true if caller wants the localized action name
* @return string action name * @return string action name
*/ */
public function GetName($local = false) public function GetName($local = false)
{ {
if ($local) { if ($local) {
return __('blob'); return __('blob');
} }
return 'blob'; return 'blob';
} }
   
/** /**
* ReadQuery * ReadQuery
* *
* Read query into parameters * Read query into parameters
* *
* @access protected * @access protected
*/ */
protected function ReadQuery() protected function ReadQuery()
{ {
if (isset($_GET['hb'])) if (isset($_GET['hb']))
$this->params['hashbase'] = $_GET['hb']; $this->params['hashbase'] = $_GET['hb'];
else else
$this->params['hashbase'] = 'HEAD'; $this->params['hashbase'] = 'HEAD';
if (isset($_GET['f'])) if (isset($_GET['f']))
$this->params['file'] = $_GET['f']; $this->params['file'] = $_GET['f'];
if (isset($_GET['h'])) { if (isset($_GET['h'])) {
$this->params['hash'] = $_GET['h']; $this->params['hash'] = $_GET['h'];
} }
} }
   
/** /**
* LoadHeaders * LoadHeaders
* *
* Loads headers for this template * Loads headers for this template
* *
* @access protected * @access protected
*/ */
protected function LoadHeaders() protected function LoadHeaders()
{ {
if (isset($this->params['plain']) && $this->params['plain']) { if (isset($this->params['plain']) && $this->params['plain']) {
   
GitPHP_Log::GetInstance()->SetEnabled(false); GitPHP_Log::GetInstance()->SetEnabled(false);
   
// XXX: Nasty hack to cache headers // XXX: Nasty hack to cache headers
if (!$this->tpl->is_cached('blobheaders.tpl', $this->GetFullCacheKey())) { if (!$this->tpl->is_cached('blobheaders.tpl', $this->GetFullCacheKey())) {
if (isset($this->params['file'])) if (isset($this->params['file']))
$saveas = $this->params['file']; $saveas = $this->params['file'];
else else
$saveas = $this->params['hash'] . ".txt"; $saveas = $this->params['hash'] . ".txt";
   
$blob = $this->project->GetBlob($this->params['hash']);  
$blob->SetPath($this->params['file']);  
   
$headers = array(); $headers = array();
   
$mime = null; $mime = null;
if (GitPHP_Config::GetInstance()->GetValue('filemimetype', true)) if (GitPHP_Config::GetInstance()->GetValue('filemimetype', true)) {
  if ((!isset($this->params['hash'])) && (isset($this->params['file']))) {
  $commit = $this->project->GetCommit($this->params['hashbase']);
  $this->params['hash'] = $commit->PathToHash($this->params['file']);
  }
   
  $blob = $this->project->GetBlob($this->params['hash']);
  $blob->SetPath($this->params['file']);
   
$mime = $blob->FileMime(); $mime = $blob->FileMime();
  }
   
if ($mime) if ($mime)
$headers[] = "Content-type: " . $mime; $headers[] = "Content-type: " . $mime;
else else
$headers[] = "Content-type: text/plain; charset=UTF-8"; $headers[] = "Content-type: text/plain; charset=UTF-8";
   
$headers[] = "Content-disposition: inline; filename=\"" . $saveas . "\""; $headers[] = "Content-disposition: inline; filename=\"" . $saveas . "\"";
   
$this->tpl->assign("blobheaders", serialize($headers)); $this->tpl->assign("blobheaders", serialize($headers));
} }
$out = $this->tpl->fetch('blobheaders.tpl', $this->GetFullCacheKey()); $out = $this->tpl->fetch('blobheaders.tpl', $this->GetFullCacheKey());
   
$this->headers = unserialize($out); $this->headers = unserialize($out);
} }
   
} }
   
/** /**
* LoadData * LoadData
* *
* Loads data for this template * Loads data for this template
* *
* @access protected * @access protected
*/ */
protected function LoadData() protected function LoadData()
{ {
$commit = $this->project->GetCommit($this->params['hashbase']); $commit = $this->project->GetCommit($this->params['hashbase']);
$this->tpl->assign('commit', $commit); $this->tpl->assign('commit', $commit);
   
if ((!isset($this->params['hash'])) && (isset($this->params['file']))) { if ((!isset($this->params['hash'])) && (isset($this->params['file']))) {
$this->params['hash'] = $commit->PathToHash($this->params['file']); $this->params['hash'] = $commit->PathToHash($this->params['file']);
} }
   
$blob = $this->project->GetBlob($this->params['hash']); $blob = $this->project->GetBlob($this->params['hash']);
if ($this->params['file']) if ($this->params['file'])
$blob->SetPath($this->params['file']); $blob->SetPath($this->params['file']);
$blob->SetCommit($commit); $blob->SetCommit($commit);
$this->tpl->assign('blob', $blob); $this->tpl->assign('blob', $blob);
   
if (isset($this->params['plain']) && $this->params['plain']) { if (isset($this->params['plain']) && $this->params['plain']) {
return; return;
} }
   
$head = $this->project->GetHeadCommit(); $head = $this->project->GetHeadCommit();
$this->tpl->assign('head', $head); $this->tpl->assign('head', $head);
   
$this->tpl->assign('tree', $commit->GetTree()); $this->tpl->assign('tree', $commit->GetTree());
   
if (GitPHP_Config::GetInstance()->GetValue('filemimetype', true)) { if (GitPHP_Config::GetInstance()->GetValue('filemimetype', true)) {
$mime = $blob->FileMime(); $mime = $blob->FileMime();
if ($mime) { if ($mime) {
$mimetype = strtok($mime, '/'); $mimetype = strtok($mime, '/');
if ($mimetype == 'image') { if ($mimetype == 'image') {
$this->tpl->assign('datatag', true); $this->tpl->assign('datatag', true);
$this->tpl->assign('mime', $mime); $this->tpl->assign('mime', $mime);
$this->tpl->assign('data', base64_encode($blob->GetData())); $this->tpl->assign('data', base64_encode($blob->GetData()));
return; return;
} }
} }
} }
   
$this->tpl->assign('extrascripts', array('blame')); $this->tpl->assign('extrascripts', array('blame'));
   
if (GitPHP_Config::GetInstance()->GetValue('geshi', true)) { if (GitPHP_Config::GetInstance()->GetValue('geshi', true)) {
include_once(GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('geshiroot', 'lib/geshi/')) . "geshi.php"); include_once(GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('geshiroot', 'lib/geshi/')) . "geshi.php");
if (class_exists('GeSHi')) { if (class_exists('GeSHi')) {
$geshi = new GeSHi("",'php'); $geshi = new GeSHi("",'php');
if ($geshi) { if ($geshi) {
$lang = $geshi->get_language_name_from_extension(substr(strrchr($blob->GetName(),'.'),1)); $lang = $geshi->get_language_name_from_extension(substr(strrchr($blob->GetName(),'.'),1));
if (!empty($lang)) { if (!empty($lang)) {
$geshi->enable_classes(); $geshi->enable_classes();
$geshi->enable_strict_mode(GESHI_MAYBE); $geshi->enable_strict_mode(GESHI_MAYBE);
$geshi->set_source($blob->GetData()); $geshi->set_source($blob->GetData());
$geshi->set_language($lang); $geshi->set_language($lang);
$geshi->set_header_type(GESHI_HEADER_PRE_TABLE); $geshi->set_header_type(GESHI_HEADER_PRE_TABLE);
$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS); $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
$geshi->set_overall_id('blobData'); $geshi->set_overall_id('blobData');
$this->tpl->assign('geshiout', $geshi->parse_code()); $this->tpl->assign('geshiout', $geshi->parse_code());
$this->tpl->assign('extracss', $geshi->get_stylesheet()); $this->tpl->assign('extracss', $geshi->get_stylesheet());
$this->tpl->assign('geshi', true); $this->tpl->assign('geshi', true);
return; return;
} }
} }
} }
} }
   
$this->tpl->assign('bloblines', $blob->GetData(true)); $this->tpl->assign('bloblines', $blob->GetData(true));
} }
   
} }
   
<?php <?php
/** /**
* GitPHP Controller Blobdiff * GitPHP Controller Blobdiff
* *
* Controller for displaying a blobdiff * Controller for displaying a blobdiff
* *
* @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 Controller * @subpackage Controller
*/ */
   
  require_once(GITPHP_CONTROLLERDIR . 'Controller_DiffBase.class.php');
   
/** /**
* Blobdiff controller class * Blobdiff controller class
* *
* @package GitPHP * @package GitPHP
* @subpackage Controller * @subpackage Controller
*/ */
class GitPHP_Controller_Blobdiff extends GitPHP_ControllerBase class GitPHP_Controller_Blobdiff extends GitPHP_Controller_DiffBase
{ {
   
/** /**
* __construct * __construct
* *
* Constructor * Constructor
* *
* @access public * @access public
* @return controller * @return controller
*/ */
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
if (!$this->project) { if (!$this->project) {
throw new GitPHP_MessageException(__('Project is required'), true); throw new GitPHP_MessageException(__('Project is required'), true);
} }
} }
   
/** /**
* GetTemplate * GetTemplate
* *
* Gets the template for this controller * Gets the template for this controller
* *
* @access protected * @access protected
* @return string template filename * @return string template filename
*/ */
protected function GetTemplate() protected function GetTemplate()
{ {
if (isset($this->params['plain']) && ($this->params['plain'] === true)) { if (isset($this->params['plain']) && ($this->params['plain'] === true)) {
return 'blobdiffplain.tpl'; return 'blobdiffplain.tpl';
} }
return 'blobdiff.tpl'; return 'blobdiff.tpl';
} }
   
/** /**
* GetCacheKey * GetCacheKey
* *
* Gets the cache key for this controller * Gets the cache key for this controller
* *
* @access protected * @access protected
* @return string cache key * @return string cache key
*/ */
protected function GetCacheKey() protected function GetCacheKey()
{ {
return (isset($this->params['hashbase']) ? $this->params['hashbase'] : '') . '|' . (isset($this->params['hash']) ? $this->params['hash'] : '') . '|' . (isset($this->params['hashparent']) ? $this->params['hashparent'] : '') . '|' . (isset($this->params['file']) ? sha1($this->params['file']) : '') . '|' . (isset($this->params['sidebyside']) && ($this->params['sidebyside'] === true) ? '1' : ''); return (isset($this->params['hashbase']) ? $this->params['hashbase'] : '') . '|' . (isset($this->params['hash']) ? $this->params['hash'] : '') . '|' . (isset($this->params['hashparent']) ? $this->params['hashparent'] : '') . '|' . (isset($this->params['file']) ? sha1($this->params['file']) : '') . '|' . (isset($this->params['sidebyside']) && ($this->params['sidebyside'] === true) ? '1' : '');
} }
   
/** /**
* GetName * GetName
* *
* Gets the name of this controller's action * Gets the name of this controller's action
* *
* @access public * @access public
* @param boolean $local true if caller wants the localized action name * @param boolean $local true if caller wants the localized action name
* @return string action name * @return string action name
*/ */
public function GetName($local = false) public function GetName($local = false)
{ {
if ($local) { if ($local) {
return __('blobdiff'); return __('blobdiff');
} }
return 'blobdiff'; return 'blobdiff';
} }
   
/** /**
* ReadQuery * ReadQuery
* *
* Read query into parameters * Read query into parameters
* *
* @access protected * @access protected
*/ */
protected function ReadQuery() protected function ReadQuery()
{ {
  parent::ReadQuery();
   
if (isset($_GET['f'])) if (isset($_GET['f']))
$this->params['file'] = $_GET['f']; $this->params['file'] = $_GET['f'];
if (isset($_GET['h'])) if (isset($_GET['h']))
$this->params['hash'] = $_GET['h']; $this->params['hash'] = $_GET['h'];
if (isset($_GET['hb'])) if (isset($_GET['hb']))
$this->params['hashbase'] = $_GET['hb']; $this->params['hashbase'] = $_GET['hb'];
if (isset($_GET['hp'])) if (isset($_GET['hp']))
$this->params['hashparent'] = $_GET['hp']; $this->params['hashparent'] = $_GET['hp'];
if (isset($_GET['o'])) {  
if (!isset($this->params['plain']) || $this->params['plain'] != true) {  
if ($_GET['o'] == 'sidebyside') {  
$this->params['sidebyside'] = true;  
}  
}  
}  
}  
   
/**  
* LoadHeaders  
*  
* Loads headers for this template  
*  
* @access protected  
*/  
protected function LoadHeaders()  
{  
if (isset($this->params['plain']) && ($this->params['plain'] === true)) {  
$this->headers[] = 'Content-type: text/plain; charset=UTF-8';  
}  
} }
   
/** /**
* LoadData * LoadData
* *
* Loads data for this template * Loads data for this template
* *
* @access protected * @access protected
*/ */
protected function LoadData() protected function LoadData()
{ {
if (isset($this->params['file'])) if (isset($this->params['file']))
$this->tpl->assign('file', $this->params['file']); $this->tpl->assign('file', $this->params['file']);
   
$filediff = new GitPHP_FileDiff($this->project, $this->params['hashparent'], $this->params['hash']); $filediff = new GitPHP_FileDiff($this->project, $this->params['hashparent'], $this->params['hash']);
$this->tpl->assign('filediff', $filediff); $this->tpl->assign('filediff', $filediff);
   
if (isset($this->params['plain']) && ($this->params['plain'] === true)) { if (isset($this->params['plain']) && ($this->params['plain'] === true)) {
return; return;
} }
   
if (isset($this->params['sidebyside']) && ($this->params['sidebyside'] === true)) { if (isset($this->params['sidebyside']) && ($this->params['sidebyside'] === true)) {
$this->tpl->assign('sidebyside', true); $this->tpl->assign('sidebyside', true);
} }
   
$commit = $this->project->GetCommit($this->params['hashbase']); $commit = $this->project->GetCommit($this->params['hashbase']);
$this->tpl->assign('commit', $commit); $this->tpl->assign('commit', $commit);
   
$blobparent = $this->project->GetBlob($this->params['hashparent']); $blobparent = $this->project->GetBlob($this->params['hashparent']);
$blobparent->SetCommit($commit); $blobparent->SetCommit($commit);
$blobparent->SetPath($this->params['file']); $blobparent->SetPath($this->params['file']);
$this->tpl->assign('blobparent', $blobparent); $this->tpl->assign('blobparent', $blobparent);
   
$blob = $this->project->GetBlob($this->params['hash']); $blob = $this->project->GetBlob($this->params['hash']);
$blob->SetPath($this->params['file']); $blob->SetPath($this->params['file']);
$this->tpl->assign('blob', $blob); $this->tpl->assign('blob', $blob);
   
$tree = $commit->GetTree(); $tree = $commit->GetTree();
$this->tpl->assign('tree', $tree); $this->tpl->assign('tree', $tree);
} }
   
} }
   
<?php <?php
/** /**
* GitPHP Controller Commitdiff * GitPHP Controller Commitdiff
* *
* Controller for displaying a commitdiff * Controller for displaying a commitdiff
* *
* @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 Controller * @subpackage Controller
*/ */
   
  require_once(GITPHP_CONTROLLERDIR . 'Controller_DiffBase.class.php');
   
/** /**
* Commitdiff controller class * Commitdiff controller class
* *
* @package GitPHP * @package GitPHP
* @subpackage Controller * @subpackage Controller
*/ */
class GitPHP_Controller_Commitdiff extends GitPHP_ControllerBase class GitPHP_Controller_Commitdiff extends GitPHP_Controller_DiffBase
{ {
   
/** /**
* __construct * __construct
* *
* Constructor * Constructor
* *
* @access public * @access public
* @return controller * @return controller
*/ */
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
if (!$this->project) { if (!$this->project) {
throw new GitPHP_MessageException(__('Project is required'), true); throw new GitPHP_MessageException(__('Project is required'), true);
} }
} }
   
/** /**
* GetTemplate * GetTemplate
* *
* Gets the template for this controller * Gets the template for this controller
* *
* @access protected * @access protected
* @return string template filename * @return string template filename
*/ */
protected function GetTemplate() protected function GetTemplate()
{ {
if (isset($this->params['plain']) && ($this->params['plain'] === true)) { if (isset($this->params['plain']) && ($this->params['plain'] === true)) {
return 'commitdiffplain.tpl'; return 'commitdiffplain.tpl';
} }
return 'commitdiff.tpl'; return 'commitdiff.tpl';
} }
   
/** /**
* GetCacheKey * GetCacheKey
* *
* Gets the cache key for this controller * Gets the cache key for this controller
* *
* @access protected * @access protected
* @return string cache key * @return string cache key
*/ */
protected function GetCacheKey() protected function GetCacheKey()
{ {
return (isset($this->params['hash']) ? $this->params['hash'] : '') . '|' . (isset($this->params['hashparent']) ? $this->params['hashparent'] : ''); $key = (isset($this->params['hash']) ? $this->params['hash'] : '')
  . '|' . (isset($this->params['hashparent']) ? $this->params['hashparent'] : '')
  . '|' . (isset($this->params['sidebyside']) && ($this->params['sidebyside'] === true) ? '1' : '');
   
  return $key;
} }
   
/** /**
* GetName * GetName
* *
* Gets the name of this controller's action * Gets the name of this controller's action
* *
* @access public * @access public
* @param boolean $local true if caller wants the localized action name * @param boolean $local true if caller wants the localized action name
* @return string action name * @return string action name
*/ */
public function GetName($local = false) public function GetName($local = false)
{ {
if ($local) { if ($local) {
return __('commitdiff'); return __('commitdiff');
} }
return 'commitdiff'; return 'commitdiff';
} }
   
/** /**
* ReadQuery * ReadQuery
* *
* Read query into parameters * Read query into parameters
* *
* @access protected * @access protected
*/ */
protected function ReadQuery() protected function ReadQuery()
{ {
  parent::ReadQuery();
   
if (isset($_GET['h'])) if (isset($_GET['h']))
$this->params['hash'] = $_GET['h']; $this->params['hash'] = $_GET['h'];
if (isset($_GET['hp'])) if (isset($_GET['hp']))
$this->params['hashparent'] = $_GET['hp']; $this->params['hashparent'] = $_GET['hp'];
} }
   
/** /**
* LoadHeaders * LoadHeaders
* *
* Loads headers for this template * Loads headers for this template
* *
* @access protected * @access protected
*/ */
protected function LoadHeaders() protected function LoadHeaders()
{ {
  parent::LoadHeaders();
   
if (isset($this->params['plain']) && ($this->params['plain'] === true)) { if (isset($this->params['plain']) && ($this->params['plain'] === true)) {
GitPHP_Log::GetInstance()->SetEnabled(false);  
   
$this->headers[] = 'Content-type: text/plain; charset=UTF-8';  
$this->headers[] = 'Content-disposition: inline; filename="git-' . $this->params['hash'] . '.patch"'; $this->headers[] = 'Content-disposition: inline; filename="git-' . $this->params['hash'] . '.patch"';
} }
} }
   
/** /**
* LoadData * LoadData
* *
* Loads data for this template * Loads data for this template
* *
* @access protected * @access protected
*/ */
protected function LoadData() protected function LoadData()
{ {
$co = $this->project->GetCommit($this->params['hash']); $co = $this->project->GetCommit($this->params['hash']);
$this->tpl->assign('commit', $co); $this->tpl->assign('commit', $co);
   
if (isset($this->params['hashparent'])) { if (isset($this->params['hashparent'])) {
$this->tpl->assign("hashparent", $this->params['hashparent']); $this->tpl->assign("hashparent", $this->params['hashparent']);
} }
   
  if (isset($this->params['sidebyside']) && ($this->params['sidebyside'] === true)) {
  $this->tpl->assign('sidebyside', true);
  $this->tpl->assign('extrascripts', array('commitdiff'));
  }
   
$treediff = new GitPHP_TreeDiff($this->project, $this->params['hash'], (isset($this->params['hashparent']) ? $this->params['hashparent'] : '')); $treediff = new GitPHP_TreeDiff($this->project, $this->params['hash'], (isset($this->params['hashparent']) ? $this->params['hashparent'] : ''));
$this->tpl->assign('treediff', $treediff); $this->tpl->assign('treediff', $treediff);
} }
   
} }
   
  <?php
  /**
  * GitPHP Controller DiffBase
  *
  * Base controller for diff-type views
  *
  * @author Christopher Han <xiphux@gmail.com>
  * @copyright Copyright (c) 2011 Christopher Han
  * @package GitPHP
  * @subpackage Controller
  */
 
 
  /**
  * Constants for diff modes
  */
  define('GITPHP_DIFF_UNIFIED', 1);
  define('GITPHP_DIFF_SIDEBYSIDE', 2);
 
  /**
  * Constant of the diff mode cookie in the user's browser
  */
  define('GITPHP_DIFF_MODE_COOKIE', 'GitPHPDiffMode');
 
  /**
  * Diff mode cookie lifetime
  */
  define('GITPHP_DIFF_MODE_COOKIE_LIFETIME', 60*60*24*365); // 1 year
 
  /**
  * DiffBase controller class
  *
  * @package GitPHP
  * @subpackage Controller
  */
  abstract class GitPHP_Controller_DiffBase extends GitPHP_ControllerBase
  {
 
  /**
  * ReadQuery
  *
  * Read query into parameters
  *
  * @access protected
  */
  protected function ReadQuery()
  {
  if (!isset($this->params['plain']) || $this->params['plain'] != true) {
 
  if ($this->DiffMode(isset($_GET['o']) ? $_GET['o'] : '') == GITPHP_DIFF_SIDEBYSIDE) {
  $this->params['sidebyside'] = true;
  }
 
  }
  }
 
  /**
  * DiffMode
  *
  * Determines the diff mode to use
  *
  * @param string $overrideMode mode overridden by the user
  * @access protected
  */
  protected function DiffMode($overrideMode = '')
  {
  $mode = GITPHP_DIFF_UNIFIED; // default
 
  /*
  * Check cookie
  */
  if (!empty($_COOKIE[GITPHP_DIFF_MODE_COOKIE])) {
  $mode = $_COOKIE[GITPHP_DIFF_MODE_COOKIE];
  } else {
  /*
  * Create cookie to prevent browser delay
  */
  setcookie(GITPHP_DIFF_MODE_COOKIE, $mode, time()+GITPHP_DIFF_MODE_COOKIE_LIFETIME);
  }
 
  if (!empty($overrideMode)) {
  /*
  * User is choosing a new mode
  */
  if ($overrideMode == 'sidebyside') {
  $mode = GITPHP_DIFF_SIDEBYSIDE;
  setcookie(GITPHP_DIFF_MODE_COOKIE, GITPHP_DIFF_SIDEBYSIDE, time()+GITPHP_DIFF_MODE_COOKIE_LIFETIME);
  } else if ($overrideMode == 'unified') {
  $mode = GITPHP_DIFF_UNIFIED;
  setcookie(GITPHP_DIFF_MODE_COOKIE, GITPHP_DIFF_UNIFIED, time()+GITPHP_DIFF_MODE_COOKIE_LIFETIME);
  }
  }
 
  return $mode;
  }
 
  /**
  * LoadHeaders
  *
  * Loads headers for this template
  *
  * @access protected
  */
  protected function LoadHeaders()
  {
  if (isset($this->params['plain']) && ($this->params['plain'] === true)) {
  GitPHP_Log::GetInstance()->SetEnabled(false);
  $this->headers[] = 'Content-type: text/plain; charset=UTF-8';
  }
  }
 
  }
 
<?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
  *
  * Gets the from file blob
  *
  * @access public
  * @return mixed blob object
  */
  public function GetFromBlob()
  {
  if (empty($this->fromHash))
  return null;
   
  return $this->project->GetBlob($this->fromHash);
  }
   
  /**
  * GetToBlob
  *
  * Gets the to file blob
  *
  * @access public
  * @return mixed blob object
  */
  public function GetToBlob()
  {
  if (empty($this->toHash))
  return null;
   
  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(); $tmpdir = GitPHP_TmpDir::GetInstance();
   
$pid = 0; $pid = 0;
if (function_exists('posix_getpid')) if (function_exists('posix_getpid'))
$pid = posix_getpid(); $pid = posix_getpid();
else else
$pid = rand(); $pid = rand();
   
$fromTmpFile = null; $fromTmpFile = null;
$toTmpFile = null; $toTmpFile = null;
   
$fromName = null; $fromName = null;
$toName = null; $toName = null;
   
if ((empty($this->status)) || ($this->status == 'D') || ($this->status == 'M')) { if ((empty($this->status)) || ($this->status == 'D') || ($this->status == 'M')) {
$fromBlob = $this->project->GetBlob($this->fromHash); $fromBlob = $this->GetFromBlob();
$fromTmpFile = 'gitphp_' . $pid . '_from'; $fromTmpFile = 'gitphp_' . $pid . '_from';
$tmpdir->AddFile($fromTmpFile, $fromBlob->GetData()); $tmpdir->AddFile($fromTmpFile, $fromBlob->GetData());
   
$fromName = 'a/'; $fromName = 'a/';
if (!empty($file)) { if (!empty($file)) {
$fromName .= $file; $fromName .= $file;
} else if (!empty($this->fromFile)) { } else if (!empty($this->fromFile)) {
$fromName .= $this->fromFile; $fromName .= $this->fromFile;
} else { } else {
$fromName .= $this->fromHash; $fromName .= $this->fromHash;
} }
} }
   
if ((empty($this->status)) || ($this->status == 'A') || ($this->status == 'M')) { if ((empty($this->status)) || ($this->status == 'A') || ($this->status == 'M')) {
$toBlob = $this->project->GetBlob($this->toHash); $toBlob = $this->GetToBlob();
$toTmpFile = 'gitphp_' . $pid . '_to'; $toTmpFile = 'gitphp_' . $pid . '_to';
$tmpdir->AddFile($toTmpFile, $toBlob->GetData()); $tmpdir->AddFile($toTmpFile, $toBlob->GetData());
   
$toName = 'b/'; $toName = 'b/';
if (!empty($file)) { if (!empty($file)) {
$toName .= $file; $toName .= $file;
} else if (!empty($this->toFile)) { } else if (!empty($this->toFile)) {
$toName .= $this->toFile; $toName .= $this->toFile;
} else { } else {
$toName .= $this->toHash; $toName .= $this->toHash;
} }
} }
   
$this->diffData = GitPHP_DiffExe::Diff((empty($fromTmpFile) ? null : ($tmpdir->GetDir() . $fromTmpFile)), $fromName, (empty($toTmpFile) ? null : ($tmpdir->GetDir() . $toTmpFile)), $toName); $this->diffData = GitPHP_DiffExe::Diff((empty($fromTmpFile) ? null : ($tmpdir->GetDir() . $fromTmpFile)), $fromName, (empty($toTmpFile) ? null : ($tmpdir->GetDir() . $toTmpFile)), $toName);
   
if (!empty($fromTmpFile)) { if (!empty($fromTmpFile)) {
$tmpdir->RemoveFile($fromTmpFile); $tmpdir->RemoveFile($fromTmpFile);
} }
   
if (!empty($toTmpFile)) { if (!empty($toTmpFile)) {
$tmpdir->RemoveFile($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, $rawBlob = $exe->Execute(GIT_CAT_FILE,
array("blob", $this->fromHash)); array("blob", $this->fromHash));
$blob = explode("\n", $rawBlob); $blob = explode("\n", $rawBlob);
   
$diffLines = explode("\n", $exe->Execute(GIT_DIFF, $diffLines = explode("\n", $exe->Execute(GIT_DIFF,
array("-U0", $this->fromHash, array("-U0", $this->fromHash,
$this->toHash))); $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)
  $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)
  $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;
} }
   
/** /**
* GetCommit * GetCommit
* *
* Gets the commit for this filediff * Gets the commit for this filediff
* *
* @access public * @access public
* @return commit object * @return commit object
*/ */
public function GetCommit() public function GetCommit()
{ {
return $this->commit; return $this->commit;
} }
   
/** /**
* SetCommit * SetCommit
* *
* Sets the commit for this filediff * Sets the commit for this filediff
* *
* @access public * @access public
* @param mixed $commit commit object * @param mixed $commit commit object
*/ */
public function SetCommit($commit) public function SetCommit($commit)
{ {
$this->commit = $commit; $this->commit = $commit;
} }
} }
   
<?php <?php
/** /**
* GitPHP Project * GitPHP Project
* *
* Represents a single git project * Represents a single git project
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2010 Christopher Han * @copyright Copyright (c) 2010 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
   
require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php'); require_once(GITPHP_GITOBJECTDIR . 'GitExe.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Commit.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Commit.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Head.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Head.class.php');
require_once(GITPHP_GITOBJECTDIR . 'Tag.class.php'); require_once(GITPHP_GITOBJECTDIR . 'Tag.class.php');
   
/** /**
* Project class * Project class
* *
* @package GitPHP * @package GitPHP
* @subpackage Git * @subpackage Git
*/ */
class GitPHP_Project class GitPHP_Project
{ {
   
/** /**
* 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 = "";
   
/** /**
* readOwner * readOwner
* *
* Stores whether the file owner has been read * Stores whether the file owner has been read
* *
* @access protected * @access protected
*/ */
protected $readOwner = false; protected $readOwner = 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();
   
/** /**
* __construct * __construct
* *
* Class constructor * Class constructor
* *
* @access public * @access public
* @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($project)
{ {
$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')); $projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot'));
   
$realProjectRoot = realpath($projectRoot); $realProjectRoot = realpath($projectRoot);
$path = $projectRoot . $project; $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->readOwner) { if (empty($this->owner) && !$this->readOwner) {
   
$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);
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->readOwner = true; $this->readOwner = true;
} }
return $this->owner; return $this->owner;
} }
   
/** /**
* 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;
} }
   
/** /**
* 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( $from = array(
'/', '/',
'.git' '.git'
); );
$to = array( $to = array(
'-', '-',
'' ''
); );
return str_replace($from, $to, $this->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')); $projectRoot = GitPHP_Util::AddSlash(GitPHP_Config::GetInstance()->GetValue('projectroot'));
   
return $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;
   
$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));
} }
   
/** /**
* 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;
} 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->GetObject(); $obj = $tag->GetCommit();
if ($obj != null) { if ($obj != null) {
return $obj; return $obj;
} }
} }
} return null;
  }
if (!isset($this->commitCache[$hash])) {  
$cacheKey = 'project|' . $this->project . '|commit|' . $hash; if (preg_match('/[0-9a-f]{40}/i', $hash)) {
$cached = GitPHP_Cache::GetInstance()->Get($cacheKey);  
if ($cached) if (!isset($this->commitCache[$hash])) {
$this->commitCache[$hash] = $cached; $cacheKey = 'project|' . $this->project . '|commit|' . $hash;
else $cached = GitPHP_Cache::GetInstance()->Get($cacheKey);
$this->commitCache[$hash] = new GitPHP_Commit($this, $hash); if ($cached)
} $this->commitCache[$hash] = $cached;
  else
return $this->commitCache[$hash]; $this->commitCache[$hash] = new GitPHP_Commit($this, $hash);
  }
   
  return $this->commitCache[$hash];
   
  }
   
  if (!$this->readRefs)
  $this->ReadRefList();
   
  if (isset($this->heads['refs/heads/' . $hash]))
  return $this->heads['refs/heads/' . $hash]->GetCommit();
   
  if (isset($this->tags['refs/tags/' . $hash]))
  return $this->tags['refs/tags/' . $hash]->GetCommit();
   
  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
*/ */
public function ReadRefList() public function ReadRefList()
{ {
$this->readRefs = true; $this->readRefs = true;
   
$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) {
} }
} }
} }
} }
   
/** /**
* 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();
   
$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;
} }
   
/** /**
* 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;
   
$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();
   
$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;
} }
   
/** /**
* 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;
   
$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 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 hashes * @return array array of hashes
*/ */
public function GetLogHash($hash, $count = 50, $skip = 0) public 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)
{ {
$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;
} }
   
/** /**
* 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;
   
$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);
} }
   
} }
   
file:b/js/commitdiff.js (new)
  /*
  * GitPHP javascript commitdiff
  *
  * Javascript enhancements to make side-by-side
  * commitdiff more usable
  *
  * @author Christopher Han <xiphux@gmail.com>
  * @copyright Copyright (c) 2011 Christopher Han
  * @package GitPHP
  */
 
  var TOCYloc = null;
  var TOCposition = null;
  var TOCtop = null;
 
  function initSBSCommitDiff() {
  var sbsTOC = $('div.commitDiffSBS div.SBSTOC');
  if (sbsTOC.size() < 1) {
  return;
  }
 
  TOCYloc = sbsTOC.position().top;
  TOCposition = sbsTOC.css('position');
  TOCtop = sbsTOC.css('top');
  $(window).scroll(function() {
  var windowYloc = $(document).scrollTop();
  if (windowYloc > TOCYloc) {
  sbsTOC.css('position', 'fixed');
  sbsTOC.css('top', '0px');
  } else {
  sbsTOC.css('position', TOCposition);
  sbsTOC.css('top', TOCtop);
  }
  });
 
  $('a.SBSTOCItem').click(function() {
  var clickedItem = $(this).get(0);
  $('a.SBSTOCItem').each(function(index, value) {
  if (clickedItem == value) {
  $(this).parent().addClass('activeItem');
  } else {
  $(this).parent().removeClass('activeItem');
  }
  });
  var clickedId = $(this).attr('href').substring(1);
  $('div.diffBlob').each(function() {
  if ($(this).attr('id') == clickedId) {
  $(this).slideDown('fast');
  } else {
  $(this).slideUp('fast');
  }
  });
  $('a.showAll').show();
  if ($(document).scrollTop() > $('div.SBSContent').offset().top) {
  $('html, body').animate({
  scrollTop: $('div.SBSContent').offset().top
  }, 200);
  }
  return false;
  });
  $('a.showAll').click(function() {
  $('a.SBSTOCItem').parent().removeClass('activeItem');
  $('div.diffBlob').slideDown('fast');
  $(this).hide();
  if ($(document).scrollTop() > $('div.SBSContent').offset().top) {
  $('html, body').animate({
  scrollTop: $('div.SBSContent').offset().top
  }, 200);
  }
  return false;
  });
  };
 
  $(document).ready(function() {
  initSBSCommitDiff();
  });
 
{* {*
* commitdiff.tpl * commitdiff.tpl
* gitphp: A PHP git repository browser * gitphp: A PHP git repository browser
* Component: Commitdiff view template * Component: Commitdiff view template
* *
* Copyright (C) 2009 Christopher Han <xiphux@gmail.com> * Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
*} *}
{include file='header.tpl'} {include file='header.tpl'}
   
{* Nav *} {* Nav *}
<div class="page_nav"> <div class="page_nav">
{if $commit} {if $commit}
{assign var=tree value=$commit->GetTree()} {assign var=tree value=$commit->GetTree()}
{/if} {/if}
{include file='nav.tpl' current='commitdiff' logcommit=$commit treecommit=$commit} {include file='nav.tpl' current='commitdiff' logcommit=$commit treecommit=$commit}
<br /> <br />
<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=commitdiff_plain&amp;h={$commit->GetHash()}{if $hashparent}&amp;hp={$hashparent}{/if}">{t}plain{/t}</a> {if $sidebyside}
  <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=commitdiff&amp;h={$commit->GetHash()}{if $hashparent}&amp;hp={$hashparent}{/if}&amp;o=unified">{t}unified{/t}</a>
  {else}
  <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=commitdiff&amp;h={$commit->GetHash()}{if $hashparent}&amp;hp={$hashparent}{/if}&amp;o=sidebyside">{t}side by side{/t}</a>
  {/if}
  | <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=commitdiff_plain&amp;h={$commit->GetHash()}{if $hashparent}&amp;hp={$hashparent}{/if}">{t}plain{/t}</a>
</div> </div>
   
{include file='title.tpl' titlecommit=$commit} {include file='title.tpl' titlecommit=$commit}
<div class="page_body"> <div class="page_body">
{assign var=bugpattern value=$project->GetBugPattern()} {assign var=bugpattern value=$project->GetBugPattern()}
{assign var=bugurl value=$project->GetBugUrl()} {assign var=bugurl value=$project->GetBugUrl()}
{foreach from=$commit->GetComment() item=line} {foreach from=$commit->GetComment() item=line}
{$line|htmlspecialchars|buglink:$bugpattern:$bugurl}<br /> {$line|htmlspecialchars|buglink:$bugpattern:$bugurl}<br />
{/foreach} {/foreach}
<br /> <br />
   
  {if $sidebyside && ($treediff->Count() > 1)}
  <div class="commitDiffSBS">
   
  <div class="SBSTOC">
  <ul>
  <li class="listcount">
  {t count=$treediff->Count() 1=$treediff->Count() plural="%1 files changed:"}%1 file changed:{/t} <a href="#" class="showAll">(show all)</a></li>
  {foreach from=$treediff item=filediff}
  <li>
  <a href="#{$filediff->GetFromHash()}_{$filediff->GetToHash()}" class="SBSTOCItem">
  {if $filediff->GetStatus() == 'A'}
  {if $filediff->GetToFile()}{$filediff->GetToFile()}{else}{$filediff->GetToHash()}{/if} {t}(new){/t}
  {elseif $filediff->GetStatus() == 'D'}
  {if $filediff->GetFromFile()}{$filediff->GetFromFile()}{else}{$filediff->GetToFile()}{/if} {t}(deleted){/t}
  {elseif $filediff->GetStatus() == 'M'}
  {if $filediff->GetFromFile()}
  {assign var=fromfilename value=$filediff->GetFromFile()}
  {else}
  {assign var=fromfilename value=$filediff->GetFromHash()}
  {/if}
  {if $filediff->GetToFile()}
  {assign var=tofilename value=$filediff->GetToFile()}
  {else}
  {assign var=tofilename value=$filediff->GetToHash()}
  {/if}
  {$fromfilename}{if $fromfilename != $tofilename} -&gt; {$tofilename}{/if}
  {/if}
  </a>
  </li>
  {/foreach}
  </ul>
  </div>
   
  <div class="SBSContent">
  {/if}
   
{* Diff each file changed *} {* Diff each file changed *}
{foreach from=$treediff item=filediff} {foreach from=$treediff item=filediff}
  <div class="diffBlob" id="{$filediff->GetFromHash()}_{$filediff->GetToHash()}">
<div class="diff_info"> <div class="diff_info">
{if ($filediff->GetStatus() == 'D') || ($filediff->GetStatus() == 'M')} {if ($filediff->GetStatus() == 'D') || ($filediff->GetStatus() == 'M')}
{assign var=localfromtype value=$filediff->GetFromFileType(1)} {assign var=localfromtype value=$filediff->GetFromFileType(1)}
{$localfromtype}:<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=blob&amp;h={$filediff->GetFromHash()}&amp;hb={$commit->GetHash()}{if $filediff->GetFromFile()}&amp;f={$filediff->GetFromFile()}{/if}">{if $filediff->GetFromFile()}a/{$filediff->GetFromFile()}{else}{$filediff->GetFromHash()}{/if}</a> {$localfromtype}:<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=blob&amp;h={$filediff->GetFromHash()}&amp;hb={$commit->GetHash()}{if $filediff->GetFromFile()}&amp;f={$filediff->GetFromFile()}{/if}">{if $filediff->GetFromFile()}a/{$filediff->GetFromFile()}{else}{$filediff->GetFromHash()}{/if}</a>
{if $filediff->GetStatus() == 'D'} {if $filediff->GetStatus() == 'D'}
{t}(deleted){/t} {t}(deleted){/t}
{/if} {/if}
{/if} {/if}
   
{if $filediff->GetStatus() == 'M'} {if $filediff->GetStatus() == 'M'}
-&gt; -&gt;
{/if} {/if}
   
{if ($filediff->GetStatus() == 'A') || ($filediff->GetStatus() == 'M')} {if ($filediff->GetStatus() == 'A') || ($filediff->GetStatus() == 'M')}
{assign var=localtotype value=$filediff->GetToFileType(1)} {assign var=localtotype value=$filediff->GetToFileType(1)}
{$localtotype}:<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=blob&amp;h={$filediff->GetToHash()}&amp;hb={$commit->GetHash()}{if $filediff->GetToFile()}&amp;f={$filediff->GetToFile()}{/if}">{if $filediff->GetToFile()}b/{$filediff->GetToFile()}{else}{$filediff->GetToHash()}{/if}</a> {$localtotype}:<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=blob&amp;h={$filediff->GetToHash()}&amp;hb={$commit->GetHash()}{if $filediff->GetToFile()}&amp;f={$filediff->GetToFile()}{/if}">{if $filediff->GetToFile()}b/{$filediff->GetToFile()}{else}{$filediff->GetToHash()}{/if}</a>
   
{if $filediff->GetStatus() == 'A'} {if $filediff->GetStatus() == 'A'}
{t}(new){/t} {t}(new){/t}
{/if} {/if}
{/if} {/if}
</div> </div>
{include file='filediff.tpl' diff=$filediff->GetDiff('', true, true)} {if $sidebyside}
  {include file='filediffsidebyside.tpl' diffsplit=$filediff->GetDiffSplit()}
  {else}
  {include file='filediff.tpl' diff=$filediff->GetDiff('', true, true)}
  {/if}
  </div>
{/foreach} {/foreach}
   
  {if $sidebyside && ($treediff->Count() > 1)}
  </div>
  <div class="SBSFooter"></div>
   
  </div>
  {/if}
   
   
</div> </div>
   
{include file='footer.tpl'} {include file='footer.tpl'}
   
   
{* {*
* filediffsidebyside * filediffsidebyside
* *
* File diff with side-by-side changes template * File diff with side-by-side changes template
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @author Mattias Ulbrich * @author Mattias Ulbrich
* @copyright Copyright (c) 2010 Christopher Han * @copyright Copyright (c) 2010 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Template * @subpackage Template
*} *}
<table class="diffTable"> <table class="diffTable">
{foreach from=$diffsplit item=lineinfo} {if $filediff->GetStatus() == 'D'}
{if $lineinfo[0]=='added'} {assign var=delblob value=$filediff->GetFromBlob()}
<tr class="diff-added"> {foreach from=$delblob->GetData(true) item=blobline}
{elseif $lineinfo[0]=='deleted'} <tr class="diff-deleted">
<tr class="diff-deleted"> <td class="diff-left">{$blobline|escape}</td>
{elseif $lineinfo[0]=='modified'} <td>&nbsp;</td>
<tr class="diff-modified"> </tr>
{else} {/foreach}
<tr> {elseif $filediff->GetStatus() == 'A'}
{/if} {assign var=newblob value=$filediff->GetToBlob()}
<td class="diff-left">{if $lineinfo[1]}{$lineinfo[1]|escape}{else}&nbsp;{/if}</td> {foreach from=$newblob->GetData(true) item=blobline}
<td>{if $lineinfo[2]}{$lineinfo[2]|escape}{else}&nbsp;{/if}</td> <tr class="diff-added">
</tr> <td class="diff-left">&nbsp;</td>
{/foreach} <td>{$blobline|escape}</td>
  </tr>
  {/foreach}
  {else}
  {foreach from=$diffsplit item=lineinfo}
  {if $lineinfo[0]=='added'}
  <tr class="diff-added">
  {elseif $lineinfo[0]=='deleted'}
  <tr class="diff-deleted">
  {elseif $lineinfo[0]=='modified'}
  <tr class="diff-modified">
  {else}
  <tr>
  {/if}
  <td class="diff-left">{if $lineinfo[1]}{$lineinfo[1]|escape}{else}&nbsp;{/if}</td>
  <td>{if $lineinfo[2]}{$lineinfo[2]|escape}{else}&nbsp;{/if}</td>
  </tr>
  {/foreach}
  {/if}
</table> </table>
   
comments