Implemented side-by-side diff comparison of files
Implemented side-by-side diff comparison of files

Signed-off-by: Chris Han <xiphux@gmail.com>

--- a/css/gitphp.css
+++ b/css/gitphp.css
@@ -111,7 +111,6 @@
 	white-space: nowrap;
 }
 
-
 /*
  * File search view
  */
@@ -119,6 +118,34 @@
 	white-space: pre;
 }
 
+/*
+ * side-by-side-diff diff
+ */
+table.diffTable {
+	font-family: monospace;
+	border-spacing:0px;
+	width: 100%;
+}
+
+table.diffTable td {
+	width: 50%;
+}
+
+table.diffTable td.diff-added {
+	background-color: #C1FFC1;
+}
+
+table.diffTable td.diff-modified {
+	background-color: #DDEEFF;
+}
+
+table.diffTable td.diff-deleted {
+	background-color: #FFDDDD;
+}
+
+table.diffTable td.diff-left {
+	border-right: 1px solid #d9d8d1;
+}
 
 /*
  * Geshi styles

--- a/include/controller/Controller.class.php
+++ b/include/controller/Controller.class.php
@@ -53,6 +53,10 @@
 				$controller = new GitPHP_Controller_Blobdiff();
 				if ($action === 'blobdiff_plain')
 					$controller->SetParam('plain', true);
+				break;
+			case 'blobdiff_sidebyside':
+				require_once(GITPHP_CONTROLLERDIR . 'Controller_Sidediff.class.php');
+				$controller = new GitPHP_Controller_Sidediff();
 				break;
 			case 'history':
 				require_once(GITPHP_CONTROLLERDIR . 'Controller_History.class.php');

--- /dev/null
+++ b/include/controller/Controller_Sidediff.class.php
@@ -1,1 +1,239 @@
-
+<?php
+/**
+ * GitPHP Controller Blobdiff
+ *
+ * Controller for displaying a blobdiff
+ *
+ * @author Mattias Ulbrich
+ * @package GitPHP
+ * @subpackage Controller
+ */
+
+/**
+ * private little helper
+ */
+function toH($in) {
+	if(!$in || strlen($in) == 0) {
+		return "&nbsp";
+	} else {
+		$in = htmlentities($in);
+		$in = str_replace(" ", "&nbsp;", $in);
+		return $in;
+	}
+}
+
+/**
+ * Blobdiff controller class
+ *
+ * @package GitPHP
+ * @subpackage Controller
+ */
+class GitPHP_Controller_Sidediff extends GitPHP_ControllerBase
+{
+	private $gitexe;
+
+	/**
+	 * __construct
+	 *
+	 * Constructor
+	 *
+	 * @access public
+	 * @return controller
+	 */
+	public function __construct()
+	{
+		parent::__construct();
+		$this->gitexe = new GitPHP_GitExe($this->project);
+
+		if (!$this->project) {
+			throw new GitPHP_MessageException(__('Project is required'), true);
+		}
+	}
+
+	/**
+	 * GetTemplate
+	 *
+	 * Gets the template for this controller
+	 *
+	 * @access protected
+	 * @return string template filename
+	 */
+	protected function GetTemplate()
+	{
+		return 'sidebyside.tpl';
+	}
+
+	/**
+	 * GetCacheKey
+	 *
+	 * Gets the cache key for this controller
+	 *
+	 * @access protected
+	 * @return string cache key
+	 */
+	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']) : '');
+	}
+
+	/**
+	 * GetName
+	 *
+	 * Gets the name of this controller's action
+	 *
+	 * @access public
+	 * @param boolean $local true if caller wants the localized action name
+	 * @return string action name
+	 */
+	public function GetName($local = false)
+	{
+		if ($local) {
+			return __('blobdiff_sidebyside');
+		}
+		return 'blobdiff_sidebyside';
+	}
+
+	/**
+	 * ReadQuery
+	 *
+	 * Read query into parameters
+	 *
+	 * @access protected
+	 */
+	protected function ReadQuery()
+	{
+		if (isset($_GET['f']))
+			$this->params['file'] = $_GET['f'];
+		if (isset($_GET['h']))
+			$this->params['hash'] = $_GET['h'];
+		if (isset($_GET['hb']))
+			$this->params['hashbase'] = $_GET['hb'];
+		if (isset($_GET['hp']))
+			$this->params['hashparent'] = $_GET['hp'];
+	}
+
+	/**
+	 * LoadData
+	 *
+	 * Loads data for this template
+	 *
+	 * @access protected
+	 */
+	protected function LoadData()
+	{
+		if (isset($this->params['file']))
+			$this->tpl->assign('file', $this->params['file']);
+
+		$diffData = $this->makeDiffData();
+		$this->tpl->assign('diffdata', $diffData);
+
+		$commit = $this->project->GetCommit($this->params['hashbase']);
+		$this->tpl->assign('commit', $commit);
+
+		$blobparent = $this->project->GetBlob($this->params['hashparent']);
+		$blobparent->SetCommit($commit);
+		$blobparent->SetPath($this->params['file']);
+		$this->tpl->assign('blobparent', $blobparent);
+
+		$blob = $this->project->GetBlob($this->params['hash']);
+		$blob->SetPath($this->params['file']);
+		$this->tpl->assign('blob', $blob);
+
+		$tree = $commit->GetTree();
+		$this->tpl->assign('tree', $tree);
+	}
+
+	/**
+	 * construct the side by side diff data from the git data
+	 * The result is an array of ternary arrays with 3 elements each:
+	 * First the mode ("" or "-added" or "-deleted" or "-modified"),
+	 * then the first column, then the second.
+	 *
+	 * @return an array of line elements (see above)
+	 */
+	private function makeDiffData()
+	{
+		$rawBlob = $this->gitexe->Execute(GIT_CAT_FILE,
+			array("blob", $this->params['hashparent']));
+		$blob  = explode("\n", $rawBlob);
+
+		$diffLines = explode("\n", $this->gitexe->Execute("diff",
+			array("-U0", $this->params['hashparent'],
+				$this->params['hash'])));
+
+		//
+		// parse diffs
+		$diffs = array();
+		$currentDiff = FALSE;
+		foreach($diffLines as $d) {
+			if(strlen($d) == 0)
+				continue;
+			switch($d[0]) {
+				case '@':
+					if($currentDiff)
+						$diffs[] = $currentDiff;
+					$comma = strpos($d, ",");
+					$line = -intval(substr($d, 2, $comma-2));
+					$currentDiff = array("line" => $line,
+						"left" => array(), "right" => array());
+					break;
+				case '+':
+					if($currentDiff)
+						$currentDiff["right"][] = substr($d, 1);
+					break;
+				case '-':
+					if($currentDiff)
+						$currentDiff["left"][] = substr($d, 1);
+					break;
+				case ' ':
+					echo "should not happen!";
+					if($currentDiff) {
+						$currentDiff["left"][] = substr($d, 1);
+						$currentDiff["right"][] = substr($d, 1);
+					}
+					break;
+			}
+		}
+		if($currentDiff)
+			$diffs[] = $currentDiff;
+		// echo "<pre>"; print_r($diffs);
+
+		//
+		// iterate over diffs
+		$output = array();
+		$idx = 0;
+		foreach($diffs as $d) {
+			while($idx+1 < $d['line']) {
+				$h = toH($blob[$idx]);
+				$output[] = array(' ', $h, $h);
+				$idx ++;
+			}
+
+			if(count($d['left']) == 0) {
+				$mode = '-added';
+			} elseif(count($d['right']) == 0) {
+				$mode = '-deleted';
+			} else {
+				$mode = '-modified';
+			}
+
+			for($i = 0; $i < count($d['left']) || $i < count($d['right']); $i++) {
+				$left = $i < count($d['left']) ? $d['left'][$i] : FALSE;
+				$right = $i < count($d['right']) ? $d['right'][$i] : FALSE;
+				$output[] = array($mode, toH($left), toH($right));
+			}
+
+			$idx += count($d['left']);
+		}
+
+		while($idx < count($blob)) {
+			$h = toH($blob[$idx]);
+			$output[] = array(' ', $h, $h);
+			$idx ++;
+		}
+
+		return $output;
+	}
+
+}
+

--- a/include/git/GitExe.class.php
+++ b/include/git/GitExe.class.php
@@ -107,7 +107,8 @@
 
 		$ret = shell_exec($fullCommand);
 
-		GitPHP_Log::GetInstance()->Log('Finish executing "' . $fullCommand . '"');
+		GitPHP_Log::GetInstance()->Log('Finish executing "' . $fullCommand . '"' .
+			"\nwith result: " . $ret);
 
 		return $ret;
 	}

--- a/locale/gitphp.pot
+++ b/locale/gitphp.pot
@@ -107,6 +107,14 @@
 #: templates/commitdiff.tpl
 #: templates/blob.tpl
 msgid "plain"
+msgstr ""
+
+# Used as a link to a side-by-side version of a diff
+msgid "side by side"
+msgstr ""
+
+# Used as a link to a unified version of a diff
+msgid "unified"
 msgstr ""
 
 # Used as a link to the first page in a list of results

--- a/templates/blobdiff.tpl
+++ b/templates/blobdiff.tpl
@@ -10,6 +10,7 @@
  <div class="page_nav">
    {include file='nav.tpl' treecommit=$commit}
    <br />
+   <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=blobdiff_sidebyside&amp;h={$blob->GetHash()}&amp;hp={$blobparent->GetHash()}&amp;hb={$commit->GetHash()}&amp;f={$file}">{t}side by side{/t}</a> |
    <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=blobdiff_plain&amp;h={$blob->GetHash()}&amp;hp={$blobparent->GetHash()}&amp;f={$file}">{t}plain{/t}</a>
  </div>
 

--- /dev/null
+++ b/templates/sidebyside.tpl
@@ -1,1 +1,38 @@
+{*
+ *  blobdiff.tpl
+ *  gitphp: A PHP git repository browser
+ *  Component: Blobdiff view template
+ *
+ *  Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
+ *}
+{include file='header.tpl'}
 
+ <div class="page_nav">
+   {include file='nav.tpl' treecommit=$commit}
+   <br />
+   <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=blobdiff&amp;h={$blob->GetHash()}&amp;hp={$blobparent->GetHash()}&amp;hb={$commit->GetHash()}&amp;f={$file}">{t}unified{/t}</a> |
+   <a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=blobdiff_plain&amp;h={$blob->GetHash()}&amp;hp={$blobparent->GetHash()}&amp;f={$file}">{t}plain{/t}</a>
+ </div>
+
+ {include file='title.tpl' titlecommit=$commit}
+
+ {include file='path.tpl' pathobject=$blobparent target='blob'}
+
+ <div class="page_body">
+   <div class="diff_info">
+     {* Display the from -> to diff header *}
+     {t}blob{/t}:<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=blob&amp;h={$blobparent->GetHash()}&amp;hb={$commit->GetHash()}&amp;f={$file}">{if $file}a/{$file}{else}{$blobparent->GetHash()}{/if}</a> -&gt; {t}blob{/t}:<a href="{$SCRIPT_NAME}?p={$project->GetProject()|urlencode}&amp;a=blob&amp;h={$blob->GetHash()}&amp;hb={$commit->GetHash()}&amp;f={$file}">{if $file}b/{$file}{else}{$blob->GetHash()}{/if}</a>
+   </div>
+   {* Display the sidebysidediff *}
+   <table class="diffTable">
+   {foreach from=$diffdata item=lineinfo}
+     <tr>
+       <td class="diff{$lineinfo[0]} diff-left">{$lineinfo[1]}</td>
+       <td class="diff{$lineinfo[0]}">{$lineinfo[2]}</td>
+     </tr>
+   {/foreach}
+   </table>
+ </div>
+
+ {include file='footer.tpl'}
+

comments