Sometimes finfo_open chokes on the magic file and floods warnings
Sometimes finfo_open chokes on the magic file and floods warnings

All sorts of junk "invalid offset" and "invalid type" messages, suppress
them

--- a/css/gitphp.css
+++ b/css/gitphp.css
@@ -131,6 +131,14 @@
 	width: 50%;
 }
 
+/*
+ * side-by-side commitdiff
+ */
+div.commitDiffSBS div.SBSTOC .showAll
+{
+	display: none;
+}
+
 
 /*
  * Geshi styles

--- a/css/gitphpskin.css
+++ b/css/gitphpskin.css
@@ -325,6 +325,14 @@
 	color: #880000; 
 }
 
+span.commit_title {
+	font-weight: bold;
+}
+
+span.merge_title {
+	color: #777777;
+}
+
 span.newfile {
 	color: #008000;
 }
@@ -414,12 +422,29 @@
 	float: left;
 	width: 19%;
 	word-wrap: break-word;
+	background-color: #ffffff;
+	border-bottom: 1px solid #edece6;
+}
+
+div.commitDiffSBS div.SBSTOC a
+{
+	text-decoration: none;
 }
 
 div.commitDiffSBS div.SBSTOC ul
 {
-	margin-left: 5px;
-	padding-left: 5px;
+	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

--- a/include/Util.class.php
+++ b/include/Util.class.php
@@ -25,21 +25,54 @@
 	 * @access public
 	 * @static
 	 * @param string $path path to add slash to
-	 * @param $backslash true to also check for backslash (windows paths)
+	 * @param $filesystem true if this is a filesystem path (to also check for backslash for windows paths)
 	 * @return string $path with a trailing slash
 	 */
-	public static function AddSlash($path, $backslash = true)
+	public static function AddSlash($path, $filesystem = true)
 	{
 		if (empty($path))
 			return $path;
 
 		$end = substr($path, -1);
 
-		if (!(( ($end == '/') || ($end == ':')) || ($backslash && (strtoupper(substr(PHP_OS, 0, 3))) && ($end == '\\'))))
-			$path .= '/';
+		if (!(( ($end == '/') || ($end == ':')) || ($filesystem && GitPHP_Util::IsWindows() && ($end == '\\')))) {
+			if (GitPHP_Util::IsWindows() && $filesystem) {
+				$path .= '\\';
+			} else {
+				$path .= '/';
+			}
+		}
 
 		return $path;
 	}
 
+	/**
+	 * IsWindows
+	 *
+	 * Tests if this is running on windows
+	 *
+	 * @access public
+	 * @static
+	 * @return bool true if on windows
+	 */
+	public static function IsWindows()
+	{
+		return (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
+	}
+
+	/**
+	 * Is64Bit
+	 *
+	 * Tests if this is a 64 bit machine
+	 *
+	 * @access public
+	 * @static
+	 * @return bool true if on 64 bit
+	 */
+	public static function Is64Bit()
+	{
+		return (strpos(php_uname('m'), '64') !== false);
+	}
+
 }
 

--- a/include/controller/Controller_Commitdiff.class.php
+++ b/include/controller/Controller_Commitdiff.class.php
@@ -138,6 +138,7 @@
 
 		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'] : ''));

--- a/include/git/Blob.class.php
+++ b/include/git/Blob.class.php
@@ -259,14 +259,14 @@
 
 		$magicdb = GitPHP_Config::GetInstance()->GetValue('magicdb', null);
 		if (empty($magicdb)) {
-			if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+			if (GitPHP_Util::IsWindows()) {
 				$magicdb = 'C:\\wamp\\php\\extras\\magic';
 			} else {
 				$magicdb = '/usr/share/misc/magic';
 			}
 		}
 
-		$finfo = finfo_open(FILEINFO_MIME, $magicdb);
+		$finfo = @finfo_open(FILEINFO_MIME, $magicdb);
 		if ($finfo) {
 			$mime = finfo_buffer($finfo, $this->data, FILEINFO_MIME);
 			if ($mime && strpos($mime, '/')) {
@@ -290,7 +290,7 @@
 	 */
 	private function FileMime_File()
 	{
-		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+		if (GitPHP_Util::IsWindows()) {
 			return '';
 		}
 

--- a/include/git/Commit.class.php
+++ b/include/git/Commit.class.php
@@ -510,6 +510,22 @@
 			return time() - $this->committerEpoch;
 
 		return '';
+	}
+
+	/**
+	 * IsMergeCommit
+	 *
+	 * Returns whether this is a merge commit
+	 *
+	 * @access pubilc
+	 * @return boolean true if merge commit
+	 */
+	public function IsMergeCommit()
+	{
+		if (!$this->dataRead)
+			$this->ReadData();
+
+		return count($this->parents) > 1;
 	}
 
 	/**

--- a/include/git/DiffExe.class.php
+++ b/include/git/DiffExe.class.php
@@ -54,13 +54,11 @@
 	 */
 	public function __construct()
 	{
-		$this->binary = GitPHP_Config::GetInstance()->GetValue('diffbin');
-		if (empty($this->binary)) {
-			if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
-				$this->binary = 'C:\\Progra~1\\Git\\bin\\diff.exe';
-			} else {
-				$this->binary = 'diff';
-			}
+		$binary = GitPHP_Config::GetInstance()->GetValue('diffbin');
+		if (empty($binary)) {
+			$this->binary = GitPHP_DiffExe::DefaultBinary();
+		} else {
+			$this->binary = $binary;
 		}
 
 	}
@@ -241,11 +239,10 @@
 	 */
 	public static function DefaultBinary()
 	{
-		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+		if (GitPHP_Util::IsWindows()) {
 			// windows
 
-			$arch = php_uname('m');
-			if (strpos($arch, '64') !== false) {
+			if (GitPHP_Util::Is64Bit()) {
 				// match x86_64 and x64 (64 bit)
 				// C:\Program Files (x86)\Git\bin\diff.exe
 				return 'C:\\Progra~2\\Git\\bin\\diff.exe';
@@ -256,7 +253,7 @@
 			}
 		} else {
 			// *nix, just use PATH
-			$this->binary = 'diff';
+			return 'diff';
 		}
 	}
 }

--- a/include/git/FileDiff.class.php
+++ b/include/git/FileDiff.class.php
@@ -351,6 +351,38 @@
 	}
 
 	/**
+	 * 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
 	 *
 	 * Gets the status of the change
@@ -556,7 +588,7 @@
 		$toName = null;
 
 		if ((empty($this->status)) || ($this->status == 'D') || ($this->status == 'M')) {
-			$fromBlob = $this->project->GetBlob($this->fromHash);
+			$fromBlob = $this->GetFromBlob();
 			$fromTmpFile = 'gitphp_' . $pid . '_from';
 			$tmpdir->AddFile($fromTmpFile, $fromBlob->GetData());
 
@@ -571,7 +603,7 @@
 		}
 
 		if ((empty($this->status)) || ($this->status == 'A') || ($this->status == 'M')) {
-			$toBlob = $this->project->GetBlob($this->toHash);
+			$toBlob = $this->GetToBlob();
 			$toTmpFile = 'gitphp_' . $pid . '_to';
 			$tmpdir->AddFile($toTmpFile, $toBlob->GetData());
 
@@ -585,7 +617,7 @@
 			}
 		}
 
-		$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 : escapeshellarg($tmpdir->GetDir() . $fromTmpFile)), $fromName, (empty($toTmpFile) ? null : escapeshellarg($tmpdir->GetDir() . $toTmpFile)), $toName);
 
 		if (!empty($fromTmpFile)) {
 			$tmpdir->RemoveFile($fromTmpFile);

--- a/include/git/GitExe.class.php
+++ b/include/git/GitExe.class.php
@@ -250,11 +250,10 @@
 	 */
 	public static function DefaultBinary()
 	{
-		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+		if (GitPHP_Util::IsWindows()) {
 			// windows
 
-			$arch = php_uname('m');
-			if (strpos($arch, '64') !== false) {
+			if (GitPHP_Util::Is64Bit()) {
 				// match x86_64 and x64 (64 bit)
 				// C:\Program Files (x86)\Git\bin\git.exe
 				return 'C:\\Progra~2\\Git\\bin\\git.exe';

--- a/include/git/TmpDir.class.php
+++ b/include/git/TmpDir.class.php
@@ -107,7 +107,7 @@
 
 		if (empty($tmpdir)) {
 			// ultimate default - should never get this far
-			if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+			if (GitPHP_Util::IsWindows()) {
 				$tmpdir = 'C:\\Windows\\Temp';
 			} else {
 				$tmpdir = '/tmp';

--- a/include/version.php
+++ b/include/version.php
@@ -12,7 +12,7 @@
 /**
  * Defines the version
  */
-$gitphp_version = "0.2.3";
+$gitphp_version = "0.2.4";
 
 /**
  * Defines the app string (app name and version)

file:b/js/commitdiff.js (new)
--- /dev/null
+++ b/js/commitdiff.js
@@ -1,1 +1,77 @@
+/*
+ * 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();
+});
+

--- a/locale/gitphp.pot
+++ b/locale/gitphp.pot
@@ -6,9 +6,9 @@
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: GitPHP 0.2.1\n"
+"Project-Id-Version: GitPHP 0.2.3\n"
 "Report-Msgid-Bugs-To: xiphux@gmail.com\n"
-"POT-Creation-Date: 2010-12-02 22:09-0600\n"
+"POT-Creation-Date: 2011-06-18 21:03-0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -110,10 +110,14 @@
 msgstr ""
 
 # Used as a link to a side-by-side version of a diff
+#: templates/blobdiff.tpl
+#: templates/commitdiff.tpl
 msgid "side by side"
 msgstr ""
 
 # Used as a link to a unified version of a diff
+#: templates/blobdiff.tpl
+#: templates/commitdiff.tpl
 msgid "unified"
 msgstr ""
 
@@ -147,7 +151,7 @@
 #: templates/log.tpl
 #: templates/history.tpl
 #: templates/shortloglist.tpl
-#: include/controller/Controller_Commitdiff.class.php:79
+#: include/controller/Controller_Commitdiff.class.php:85
 msgid "commitdiff"
 msgstr ""
 
@@ -169,6 +173,7 @@
 # Comes before a list of files
 # %1: the number of files
 #: templates/commit.tpl
+#: templates/commitdiff.tpl
 msgid "%1 file changed:"
 msgid_plural "%1 files changed:"
 msgstr[0] ""
@@ -288,7 +293,7 @@
 
 # Link back to the list of projects
 #: templates/header.tpl
-#: include/controller/ControllerBase.class.php:250
+#: include/controller/ControllerBase.class.php:257
 #: include/controller/Controller_ProjectList.class.php:94
 msgid "projects"
 msgstr ""
@@ -428,8 +433,8 @@
 #: include/controller/Controller_Tag.class.php:34
 #: include/controller/Controller_Tags.class.php:34
 #: include/controller/Controller_Project.class.php:33
-#: include/controller/Controller_Commitdiff.class.php:34
-#: include/controller/Controller_Blobdiff.class.php:34
+#: include/controller/Controller_Commitdiff.class.php:36
+#: include/controller/Controller_Blobdiff.class.php:36
 #: include/controller/Controller_History.class.php:34
 #: include/controller/Controller_Heads.class.php:34
 #: include/controller/Controller_Search.class.php:47
@@ -443,7 +448,7 @@
 msgstr ""
 
 # Used as link to and title for a diff of a single file
-#: include/controller/Controller_Blobdiff.class.php:79
+#: include/controller/Controller_Blobdiff.class.php:81
 msgid "blobdiff"
 msgstr ""
 
@@ -514,28 +519,28 @@
 # Error message when user specifies a path for a project root or project, but the path given isn't a directory
 # %1$s: the path the user specified
 #: include/git/ProjectListDirectory.class.php:47
-#: include/git/Project.class.php:230
+#: include/git/Project.class.php:221
 #, php-format
 msgid "%1$s is not a directory"
 msgstr ""
 
 # Error message when a path specified in the config is not a git repository
 # %1$s: the specified path
-#: include/git/Project.class.php:234
+#: include/git/Project.class.php:225
 #, php-format
 msgid "%1$s is not a git repository"
 msgstr ""
 
 # Error message when a path specified is using '..' to break out of the project root (a hack attempt)
 # %1$s: The specified path
-#: include/git/Project.class.php:238
+#: include/git/Project.class.php:229
 #, php-format
 msgid "%1$s is attempting directory traversal"
 msgstr ""
 
 # Error message when a path specified is outside of the project root
 # %1$s: The specified path
-#: include/git/Project.class.php:244
+#: include/git/Project.class.php:235
 #, php-format
 msgid "%1$s is outside of the projectroot"
 msgstr ""
@@ -775,3 +780,10 @@
 "\" config value."
 msgstr ""
 
+# Link displayed in commitdiff view, when the user has filtered
+# the display to a single file using the list of changed files.
+# This will go back to showing all files in the commitdiff
+#: templates/commitdiff.tpl
+msgid "(show all)"
+msgstr ""
+

--- a/templates/commitdiff.tpl
+++ b/templates/commitdiff.tpl
@@ -37,9 +37,11 @@
 
      <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">{t}(show all){/t}</a></li>
        {foreach from=$treediff item=filediff}
        <li>
-       <a href="#{$filediff->GetFromHash()}_{$filediff->GetToHash()}">
+       <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'}

--- a/templates/filediffsidebyside.tpl
+++ b/templates/filediffsidebyside.tpl
@@ -10,19 +10,37 @@
  * @subpackage Template
  *}
 <table class="diffTable">
-  {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 $filediff->GetStatus() == 'D'}
+    {assign var=delblob value=$filediff->GetFromBlob()}
+    {foreach from=$delblob->GetData(true) item=blobline}
+      <tr class="diff-deleted">
+        <td class="diff-left">{$blobline|escape}</td>
+	<td>&nbsp;</td>
+      </tr>
+    {/foreach}
+  {elseif $filediff->GetStatus() == 'A'}
+    {assign var=newblob value=$filediff->GetToBlob()}
+    {foreach from=$newblob->GetData(true) item=blobline}
+      <tr class="diff-added">
+        <td class="diff-left">&nbsp;</td>
+	<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>
 

--- a/templates/shortloglist.tpl
+++ b/templates/shortloglist.tpl
@@ -15,7 +15,9 @@
        <td title="{if $rev->GetAge() > 60*60*24*7*2}{$rev->GetAge()|agestring}{else}{$rev->GetCommitterEpoch()|date_format:"%Y-%m-%d"}{/if}"><em>{if $rev->GetAge() > 60*60*24*7*2}{$rev->GetCommitterEpoch()|date_format:"%Y-%m-%d"}{else}{$rev->GetAge()|agestring}{/if}</em></td>
        <td><em>{$rev->GetAuthorName()}</em></td>
        <td>
-         <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=commit&amp;h={$rev->GetHash()}" class="list commitTip" {if strlen($rev->GetTitle()) > 50}title="{$rev->GetTitle()|htmlspecialchars}"{/if}><strong>{$rev->GetTitle(50)|escape}</strong></a>
+         <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=commit&amp;h={$rev->GetHash()}" class="list commitTip" {if strlen($rev->GetTitle()) > 50}title="{$rev->GetTitle()|htmlspecialchars}"{/if}>
+         {if $rev->IsMergeCommit()}<span class="merge_title">{else}<span class="commit_title">{/if}{$rev->GetTitle(50)|escape}</span>
+         </a>
 	 {include file='refbadges.tpl' commit=$rev}
        </td>
        <td class="link">

comments