diff --git a/.gitignore b/.gitignore
index 530a8f1..faadd4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,19 @@
*~
-tmp
+.buildpath
+.externalToolBuilders
+.project
+.settings
+.tx/config
+attachments
+indefero-*.zip
src/IDF/conf/idf.php
src/IDF/conf/idf.test.php
-www/test.php
-www/media/upload
-src/IDF/gettexttemplates
-indefero-*.zip
src/IDF/conf/path.php
-.tx/config
+src/IDF/gettexttemplates
src/IDF/locale/idf.pot.bak
+test/config.php
test/test.db
test/tmp
-test/config.php
+tmp
+www/media/upload
+www/test.php
diff --git a/src/IDF/Diff.php b/src/IDF/Diff.php
index 8909618..798b0a8 100644
--- a/src/IDF/Diff.php
+++ b/src/IDF/Diff.php
@@ -35,9 +35,7 @@ class IDF_Diff
public function __construct($diff, $path_strip_level = 0)
{
$this->path_strip_level = $path_strip_level;
- // this works because in unified diff format even empty lines are
- // either prefixed with a '+', '-' or ' '
- $this->lines = preg_split("/\015\012|\015|\012/", $diff, -1, PREG_SPLIT_NO_EMPTY);
+ $this->lines = IDF_FileUtil::splitIntoLines($diff, true);
}
public function parse()
@@ -66,12 +64,12 @@ class IDF_Diff
}
// use new file name by default
- preg_match("/^\+\+\+ ([^\t]+)/", $newfileline, $m);
+ preg_match("/^\+\+\+ ([^\t\n\r]+)/", $newfileline, $m);
$current_file = $m[1];
if ($current_file === '/dev/null') {
// except if it's /dev/null, use the old one instead
// eg. mtn 0.48 and newer
- preg_match("/^--- ([^\t]+)/", $oldfileline, $m);
+ preg_match("/^--- ([^\t\r\n]+)/", $oldfileline, $m);
$current_file = $m[1];
}
if ($this->path_strip_level > 0) {
@@ -102,10 +100,11 @@ class IDF_Diff
while ($i < $diffsize && ($addlines >= 0 || $dellines >= 0)) {
$linetype = $this->lines[$i] != '' ? $this->lines[$i][0] : false;
+ $content = substr($this->lines[$i], 1);
switch ($linetype) {
case ' ':
$files[$current_file]['chunks'][$current_chunk][] =
- array($delstart, $addstart, substr($this->lines[$i++], 1));
+ array($delstart, $addstart, $content);
$dellines--;
$addlines--;
$delstart++;
@@ -113,23 +112,26 @@ class IDF_Diff
break;
case '+':
$files[$current_file]['chunks'][$current_chunk][] =
- array('', $addstart, substr($this->lines[$i++], 1));
+ array('', $addstart, $content);
$addlines--;
$addstart++;
break;
case '-':
$files[$current_file]['chunks'][$current_chunk][] =
- array($delstart, '', substr($this->lines[$i++], 1));
+ array($delstart, '', $content);
$dellines--;
$delstart++;
break;
case '\\':
- // ignore newline handling for now, see issue 636
- $i++;
+ // no new line at the end of this file; remove pseudo new line from last line
+ $cur = count($files[$current_file]['chunks'][$current_chunk]) - 1;
+ $files[$current_file]['chunks'][$current_chunk][$cur][2] =
+ rtrim($files[$current_file]['chunks'][$current_chunk][$cur][2], "\r\n");
continue;
default:
break 2;
}
+ $i++;
}
$current_chunk++;
}
@@ -144,46 +146,80 @@ class IDF_Diff
public function as_html()
{
$out = '';
- foreach ($this->files as $filename=>$file) {
+ foreach ($this->files as $filename => $file) {
$pretty = '';
$fileinfo = IDF_FileUtil::getMimeType($filename);
if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
$pretty = ' prettyprint';
}
- $out .= "\n".'
'."\n";
- $out .= ''.Pluf_esc($filename).' |
'."\n";
+
$cc = 1;
+ $offsets = array();
+ $contents = array();
+
foreach ($file['chunks'] as $chunk) {
foreach ($chunk as $line) {
- if ($line[0] and $line[1]) {
- $class = 'diff-c';
- } elseif ($line[0]) {
- $class = 'diff-r';
+ list($left, $right, $content) = $line;
+ if ($left and $right) {
+ $class = 'context';
+ } elseif ($left) {
+ $class = 'removed';
} else {
- $class = 'diff-a';
+ $class = 'added';
}
- $line_content = self::padLine(Pluf_esc($line[2]));
- $out .= sprintf('%s | %s | %s |
'."\n", $line[0], $line[1], $class, $pretty, $line_content);
+
+ $offsets[] = sprintf('%s | %s | ', $left, $right);
+ $content = IDF_FileUtil::emphasizeControlCharacters(Pluf_esc($content));
+ $contents[] = sprintf('%s | ', $class, $pretty, $content);
+ }
+ if (count($file['chunks']) > $cc) {
+ $offsets[] = '... | ... | ';
+ $contents[] = ' | ';
}
- if (count($file['chunks']) > $cc)
- $out .= '... | ... | |
'."\n";
$cc++;
}
- $out .= '
';
- }
- return Pluf_Template::markSafe($out);
- }
- public static function padLine($line)
- {
- $line = str_replace("\t", ' ', $line);
- $n = strlen($line);
- for ($i=0;$i<$n;$i++) {
- if (substr($line, $i, 1) != ' ') {
- break;
- }
+ list($added, $removed) = end($file['chunks_def']);
+
+ $added = $added[0] + $added[1];
+ $leftwidth = 1;
+ if ($added > 0)
+ $leftwidth = ((ceil(log10($added)) + 1) * 8) + 12;
+
+ $removed = $removed[0] + $removed[1];
+ $rightwidth = 1;
+ if ($removed > 0)
+ $rightwidth = ((ceil(log10($removed)) + 1) * 8) + 12;
+
+ $inner_linecounts =
+ '' ."\n".
+ '' ."\n".
+ '' .
+ implode('
'."\n".'', $offsets).
+ '
' ."\n".
+ '
' ."\n";
+
+
+ $inner_contents =
+ '' ."\n".
+ '' .
+ implode('
'."\n".'', $contents) .
+ '
' ."\n".
+ '
' ."\n";
+
+ $out .= '' ."\n".
+ '' ."\n".
+ ''.
+ ''.Pluf_esc($filename).' | '.
+ '
' ."\n".
+ '' .
+ ''. $inner_linecounts .' | '. "\n".
+ ''. $inner_contents .' | '.
+ '
' ."\n".
+ '
' ."\n";
}
- return str_repeat(' ', $i).substr($line, $i);
+
+ return Pluf_Template::markSafe($out);
}
/**
@@ -208,12 +244,12 @@ class IDF_Diff
*/
public function fileCompare($orig, $chunks, $filename, $context=10)
{
- $orig_lines = preg_split("/\015\012|\015|\012/", $orig);
+ $orig_lines = IDF_FileUtil::splitIntoLines($orig);
$new_chunks = $this->mergeChunks($orig_lines, $chunks, $context);
return $this->renderCompared($new_chunks, $filename);
}
- public function mergeChunks($orig_lines, $chunks, $context=10)
+ private function mergeChunks($orig_lines, $chunks, $context=10)
{
$spans = array();
$new_chunks = array();
@@ -310,38 +346,115 @@ class IDF_Diff
return $nnew_chunks;
}
- public function renderCompared($chunks, $filename)
+ private function renderCompared($chunks, $filename)
{
$fileinfo = IDF_FileUtil::getMimeType($filename);
$pretty = '';
if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
$pretty = ' prettyprint';
}
- $out = '';
+
$cc = 1;
- $i = 0;
+ $left_offsets = array();
+ $left_contents = array();
+ $right_offsets = array();
+ $right_contents = array();
+
+ $max_lineno_left = $max_lineno_right = 0;
+
foreach ($chunks as $chunk) {
foreach ($chunk as $line) {
- $line1 = ' ';
- $line2 = ' ';
- $line[2] = (strlen($line[2])) ? self::padLine(Pluf_esc($line[2])) : ' ';
+ $left = '';
+ $right = '';
+ $content = IDF_FileUtil::emphasizeControlCharacters(Pluf_esc($line[2]));
+
if ($line[0] and $line[1]) {
- $class = 'diff-c';
- $line1 = $line2 = $line[2];
+ $class = 'context';
+ $left = $right = $content;
} elseif ($line[0]) {
- $class = 'diff-r';
- $line1 = $line[2];
+ $class = 'removed';
+ $left = $content;
} else {
- $class = 'diff-a';
- $line2 = $line[2];
+ $class = 'added';
+ $right = $content;
}
- $out .= sprintf('%s | %s | %s | %s |
'."\n", $line[0], $class, $pretty, $line1, $line[1], $class, $pretty, $line2);
+
+ $left_offsets[] = sprintf('%s | ', $line[0]);
+ $right_offsets[] = sprintf('%s | ', $line[1]);
+ $left_contents[] = sprintf('%s | ', $class, $pretty, $left);
+ $right_contents[] = sprintf('%s | ', $class, $pretty, $right);
+
+ $max_lineno_left = max($max_lineno_left, $line[0]);
+ $max_lineno_right = max($max_lineno_right, $line[1]);
+ }
+
+ if (count($chunks) > $cc) {
+ $left_offsets[] = '... | ';
+ $right_offsets[] = '... | ';
+ $left_contents[] = ' | ';
+ $right_contents[] = ' | ';
}
- if (count($chunks) > $cc)
- $out .= '... | | ... | |
'."\n";
$cc++;
- $i++;
}
+
+ $leftwidth = 1;
+ if ($max_lineno_left > 0)
+ $leftwidth = ((ceil(log10($max_lineno_left)) + 1) * 8) + 12;
+
+ $rightwidth = 1;
+ if ($max_lineno_right > 0)
+ $rightwidth = ((ceil(log10($max_lineno_right)) + 1) * 8) + 12;
+
+ $inner_linecounts_left =
+ '' ."\n".
+ '' ."\n".
+ '' .
+ implode('
'."\n".'', $left_offsets).
+ '
' ."\n".
+ '
' ."\n";
+
+ $inner_linecounts_right =
+ '' ."\n".
+ '' ."\n".
+ '' .
+ implode('
'."\n".'', $right_offsets).
+ '
' ."\n".
+ '
' ."\n";
+
+ $inner_contents_left =
+ '' ."\n".
+ '' .
+ implode('
'."\n".'', $left_contents) .
+ '
' ."\n".
+ '
' ."\n";
+
+ $inner_contents_right =
+ '' ."\n".
+ '' .
+ implode('
'."\n".'', $right_contents) .
+ '
' ."\n".
+ '
' ."\n";
+
+ $out =
+ '' ."\n".
+ '' .
+ '' .
+ '' .
+ '' ."\n".
+ ''.
+ ''.Pluf_esc($filename).' | '.
+ '
' ."\n".
+ '' .
+ ''.__('Old').' | '.__('New').' | ' .
+ '
'.
+ '' .
+ ''. $inner_linecounts_left .' | '. "\n".
+ ''. $inner_contents_left .' | '. "\n".
+ ''. $inner_linecounts_right .' | '. "\n".
+ ''. $inner_contents_right .' | '. "\n".
+ '
' ."\n".
+ '
' ."\n";
+
return Pluf_Template::markSafe($out);
}
}
diff --git a/src/IDF/FileUtil.php b/src/IDF/FileUtil.php
index 346dfa8..73f6dce 100644
--- a/src/IDF/FileUtil.php
+++ b/src/IDF/FileUtil.php
@@ -65,9 +65,9 @@ class IDF_FileUtil
}
$table = array();
$i = 1;
- foreach (preg_split("/\015\012|\015|\012/", $content) as $line) {
+ foreach (self::splitIntoLines($content) as $line) {
$table[] = ''.$i.' | '
- .''.IDF_Diff::padLine(Pluf_esc($line)).' |
';
+ .''.self::emphasizeControlCharacters(Pluf_esc($line)).' | ';
$i++;
}
return Pluf_Template::markSafe(implode("\n", $table));
@@ -143,6 +143,56 @@ class IDF_FileUtil
return $res;
}
+ /**
+ * Splits a string into separate lines while retaining the individual
+ * line ending character for every line.
+ *
+ * OS9 line endings are not supported.
+ *
+ * @param string content
+ * @param boolean if true, skip completely empty lines
+ * @return string
+ */
+ public static function splitIntoLines($content, $skipEmpty = false)
+ {
+ $flags = PREG_SPLIT_OFFSET_CAPTURE;
+ if ($skipEmpty) $flags |= PREG_SPLIT_NO_EMPTY;
+ $splitted = preg_split("/\r\n|\n/", $content, -1, $flags);
+
+ $last_off = -1;
+ $lines = array();
+ while (($split = array_shift($splitted)) !== null) {
+ if ($last_off != -1) {
+ $lines[] .= substr($content, $last_off, $split[1] - $last_off);
+ }
+ $last_off = $split[1];
+ }
+ $lines[] = substr($content, $last_off);
+ return $lines;
+ }
+
+ /**
+ * This translates most of the C0 ASCII control characters into
+ * their visual counterparts in the 0x24## unicode plane
+ * (http://en.wikipedia.org/wiki/C0_and_C1_control_codes).
+ *
+ * We could add DEL (0x7F) to this set, but unfortunately this
+ * is not nicely mapped to 0x247F in the control plane, but 0x2421
+ * and adding an if expression below just for this is a little bit
+ * of a hassle. And of course, the more esoteric ones from C1 are
+ * missing as well...
+ *
+ * @param string $content
+ * @return string
+ */
+ public static function emphasizeControlCharacters($content)
+ {
+ return preg_replace(
+ '/([\x00-\x1F])/ue',
+ '"$".bin2hex("\\1").""',
+ $content);
+ }
+
/**
* Find if a given mime type is a text file.
* This uses the output of the self::getMimeType function.
diff --git a/src/IDF/Scm/Git.php b/src/IDF/Scm/Git.php
index 53bb417..b0f7b76 100644
--- a/src/IDF/Scm/Git.php
+++ b/src/IDF/Scm/Git.php
@@ -507,33 +507,27 @@ class IDF_Scm_Git extends IDF_Scm
"'".$this->mediumtree_fmt."'",
escapeshellarg($commit));
}
- $out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
- self::exec('IDF_Scm_Git::getCommit', $cmd, $out, $ret);
- if ($ret != 0 or count($out) == 0) {
+ $out = self::shell_exec('IDF_Scm_Git::getCommit', $cmd);
+ if (strlen($out) == 0) {
return false;
}
- if ($getdiff) {
- $log = array();
- $change = array();
- $inchange = false;
- foreach ($out as $line) {
- if (!$inchange and 0 === strpos($line, 'diff --git a')) {
- $inchange = true;
- }
- if ($inchange) {
- $change[] = $line;
- } else {
- $log[] = $line;
- }
- }
- $out = self::parseLog($log);
- $out[0]->diff = implode("\n", $change);
- } else {
- $out = self::parseLog($out);
- $out[0]->diff = '';
+
+ $diffStart = false;
+ if (preg_match('/^diff (?:--git a|--cc)/m', $out, $m, PREG_OFFSET_CAPTURE)) {
+ $diffStart = $m[0][1];
}
+ $diff = '';
+ if ($diffStart !== false) {
+ $log = substr($out, 0, $diffStart);
+ $diff = substr($out, $diffStart);
+ } else {
+ $log = $out;
+ }
+
+ $out = self::parseLog(preg_split('/\r\n|\n/', $log));
+ $out[0]->diff = $diff;
$out[0]->branch = implode(', ', $this->inBranches($out[0]->commit, null));
return $out[0];
}
diff --git a/src/IDF/Scm/Mercurial.php b/src/IDF/Scm/Mercurial.php
index 95ddada..484f3a2 100644
--- a/src/IDF/Scm/Mercurial.php
+++ b/src/IDF/Scm/Mercurial.php
@@ -408,24 +408,23 @@ class IDF_Scm_Mercurial extends IDF_Scm
escapeshellarg($commit),
escapeshellarg($this->repo),
escapeshellarg($logStyle->get()));
- $out = array();
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
- self::exec('IDF_Scm_Mercurial::getCommit', $cmd, $out);
- $log = array();
- $change = array();
- $inchange = false;
- foreach ($out as $line) {
- if (!$inchange and 0 === strpos($line, 'diff -r')) {
- $inchange = true;
- }
- if ($inchange) {
- $change[] = $line;
- } else {
- $log[] = $line;
- }
+ $out = self::shell_exec('IDF_Scm_Mercurial::getCommit', $cmd);
+ if (strlen($out) == 0) {
+ return false;
}
- $out = self::parseLog($log);
- $out[0]->diff = implode("\n", $change);
+
+ $diffStart = strpos($out, 'diff -r');
+ $diff = '';
+ if ($diffStart !== false) {
+ $log = substr($out, 0, $diffStart);
+ $diff = substr($out, $diffStart);
+ } else {
+ $log = $out;
+ }
+
+ $out = self::parseLog(preg_split('/\r\n|\n/', $log));
+ $out[0]->diff = $diff;
return $out[0];
}
diff --git a/src/IDF/Tests/TestDiff.php b/src/IDF/Tests/TestDiff.php
index b854171..6f7f0e6 100644
--- a/src/IDF/Tests/TestDiff.php
+++ b/src/IDF/Tests/TestDiff.php
@@ -32,21 +32,24 @@ class IDF_Tests_TestDiff extends UnitTestCase
parent::__construct('Test the diff parser.');
}
- public function testBinaryDiff()
- {
- $diff_content = file_get_contents(dirname(__FILE__).'/test-diff.diff');
- $orig = file_get_contents(dirname(__FILE__).'/test-diff-view.html');
- $diff = new IDF_Diff($diff_content);
- $diff->parse();
- $def = $diff->files['src/IDF/templates/idf/issues/view.html'];
-
- $orig_lines = preg_split("/\015\012|\015|\012/", $orig);
- $merged = $diff->mergeChunks($orig_lines, $def, 10);
- $lchunk = end($merged);
- $lline = end($lchunk);
- $this->assertEqual(array('', '166', '{/if}{/block}'),
- $lline);
- }
+ //
+ // IDF_Diff::mergeChunks() is now private, so this test needs to be rewritten
+ //
+ //public function testBinaryDiff()
+ //{
+ // $diff_content = file_get_contents(dirname(__FILE__).'/test-diff.diff');
+ // $orig = file_get_contents(dirname(__FILE__).'/test-diff-view.html');
+ // $diff = new IDF_Diff($diff_content);
+ // $diff->parse();
+ // $def = $diff->files['src/IDF/templates/idf/issues/view.html'];
+ //
+ // $orig_lines = preg_split("/\015\012|\015|\012/", $orig);
+ // $merged = $diff->mergeChunks($orig_lines, $def, 10);
+ // $lchunk = end($merged);
+ // $lline = end($lchunk);
+ // $this->assertEqual(array('', '166', '{/if}{/block}'),
+ // $lline);
+ //}
public function testDiffWithHeaders()
{
diff --git a/src/IDF/templates/idf/issues/js-autocomplete.html b/src/IDF/templates/idf/issues/js-autocomplete.html
index 575e1a5..24a0675 100644
--- a/src/IDF/templates/idf/issues/js-autocomplete.html
+++ b/src/IDF/templates/idf/issues/js-autocomplete.html
@@ -68,6 +68,9 @@
return row.to;
}
});
+{/literal}
+ {if $issue}
+{literal}
$("#id_relation_issue" + idx).autocomplete("{/literal}{url 'IDF_Views_Issue::autoCompleteIssueList', array($project.shortname, $issue.id)}{literal}", {
minChars: 0,
width: 310,
@@ -83,6 +86,9 @@
return row[1];
}
});
+{/literal}
+ {/if}
+{literal}
}
});
{/literal} //-->
diff --git a/src/IDF/templates/idf/review/view.html b/src/IDF/templates/idf/review/view.html
index d655083..41d368b 100644
--- a/src/IDF/templates/idf/review/view.html
+++ b/src/IDF/templates/idf/review/view.html
@@ -10,8 +10,26 @@
{/if}
{/if}
-
-
+{if !$user.isAnonymous()}
+
+ {trans 'How to Participate in a Code Review'}
+
+ {blocktrans}Code review is a process in which
+after or before changes are commited into the code repository,
+different people discuss the code changes. The goal is
+to improve the quality of the code and the
+contributions, as such, you must be pragmatic when writing
+your review. Correctly mention the line numbers (in the old or in the
+new file) and try to keep a good balance between seriousness and fun.
+{/blocktrans}
+ {blocktrans}
+Proposing code for review is intimidating, you know
+you will receive critics, so please, as a reviewer, keep this
+process fun, use it to help your contributor learn your
+coding standards and the structure of the code and make them want
+to propose more contributions.
+{/blocktrans}
+{/if}
- |
-{if !$user.isAnonymous()}
-
- {trans 'How to Participate in a Code Review'}
-
- {blocktrans}Code review is a process in which
-after or before changes are commited into the code repository,
-different people discuss the code changes. The goal is
-to improve the quality of the code and the
-contributions, as such, you must be pragmatic when writing
-your review. Correctly mention the line numbers (in the old or in the
-new file) and try to keep a good balance between seriousness and fun.
-{/blocktrans}
- {blocktrans}
-Proposing code for review is intimidating, you know
-you will receive critics, so please, as a reviewer, keep this
-process fun, use it to help your contributor learn your
-coding standards and the structure of the code and make them want
-to propose more contributions.
-{/blocktrans}
-{/if}
- |
-