Indefero

Indefero Commit Details


Date:2011-03-16 17:50:41 (13 years 9 months ago)
Author:Patrick Georgi
Branch:develop, feature-issue_links, feature.better-home, feature.content-md5, feature.diff-whitespace, feature.download-md5, feature.issue-links, feature.issue-of-others, feature.issue-summary, feature.search-filter, feature.webrepos, feature.wiki-default-page, release-1.1, release-1.2, release-1.3
Commit:baa88412b9c7cb4f9d34c4e5430e57006de9000e
Parents: 6fb9b72e22e3d14b3eb4364ee857b42d905c97c9
Message:Rewrite the diff parser and reduce the memory footprint.

The diff parser code was rewritten for clarity and speed and now handles
a couple of ugly cornercases, like SVN's property change output and single
change chunks, much better. Since the path parsing was unified as well,
the SCM interface gained a new method `getPathStripLevel()` which determines
how many path components need to be shoven off a file name for the SCM
to form a valid path in the workspace (similar to patch(1)'s --strip option).
Fixes issue 627. Automated tests follow.
Changes:

File differences

NEWS.mdtext
5252
5353
5454
55
5556
5657
5758
- Disable browser autocomplete of password fields in the account settings (issue 616)
- Improvements in the automatic linker parser (issue 618)
- The `createIssue` API method did not check the API authentication (issue 619)
- Reduce the memory footprint and compatibility of the internal diff parser (issue 627)
- Print active git branch heads and tags in bold
## Documentation
src/IDF/Diff.php
2727
2828
2929
30
31
30
3231
3332
3433
3534
36
35
3736
38
39
37
4038
4139
4240
......
4947
5048
5149
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
50
51
52
53
54
11755
11856
119
120
121
57
58
59
60
61
62
12263
12364
124
125
126
127
65
66
67
68
69
70
71
72
73
12874
129
130
131
132
133
75
76
13477
135
136
137
138
139
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
140128
141129
142130
143131
144132
145133
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164134
165135
166136
......
197167
198168
199169
200
201170
202171
203172
......
211180
212181
213182
214
215
216
217
218
219
220
221
222
223
224
225
226
227183
228184
229185
......
347303
348304
349305
350
351306
352307
353308
......
381336
382337
383338
384
385339
386340
*/
class IDF_Diff
{
public $repo = '';
public $diff = '';
public $path_strip_level = 0;
protected $lines = array();
public $files = array();
public function __construct($diff, $repo='')
public function __construct($diff, $path_strip_level = 0)
{
$this->repo = $repo;
$this->diff = $diff;
$this->path_strip_level = $path_strip_level;
$this->lines = preg_split("/\015\012|\015|\012/", $diff);
}
$files = array();
$indiff = false; // Used to skip the headers in the git patches
$i = 0; // Used to skip the end of a git patch with --\nversion number
foreach ($this->lines as $line) {
$i++;
if (0 === strpos($line, '--') and isset($this->lines[$i])
and preg_match('/^\d+\.\d+\.\d+\.\d+$/', $this->lines[$i])) {
break;
}
if (0 === strpos($line, 'diff --git a')) {
$current_file = self::getFile($line);
$files[$current_file] = array();
$files[$current_file]['chunks'] = array();
$files[$current_file]['chunks_def'] = array();
$current_chunk = 0;
$indiff = true;
continue;
} else if (preg_match('#^diff -r [^\s]+ -r [^\s]+ (.+)$#', $line, $matches)) {
$current_file = $matches[1];
$files[$current_file] = array();
$files[$current_file]['chunks'] = array();
$files[$current_file]['chunks_def'] = array();
$current_chunk = 0;
$indiff = true;
continue;
} else if (!$indiff && 0 === strpos($line, '=========')) {
// ignore pseudo stanzas with a hint of a binary file
if (preg_match("/^# (.+) is binary/", $this->lines[$i]))
continue;
// by default always use the new name of a possibly renamed file
$current_file = self::getMtnFile($this->lines[$i+1]);
// mtn 0.48 and newer set /dev/null as file path for dropped files
// so we display the old name here
if ($current_file == "/dev/null") {
$current_file = self::getMtnFile($this->lines[$i]);
}
if ($current_file == "/dev/null") {
throw new Exception(
"could not determine path from diff"
);
}
$files[$current_file] = array();
$files[$current_file]['chunks'] = array();
$files[$current_file]['chunks_def'] = array();
$current_chunk = 0;
$indiff = true;
continue;
} else if (0 === strpos($line, 'Index: ')) {
$current_file = self::getSvnFile($line);
$files[$current_file] = array();
$files[$current_file]['chunks'] = array();
$files[$current_file]['chunks_def'] = array();
$current_chunk = 0;
$indiff = true;
continue;
}
if (!$indiff) {
continue;
}
if (0 === strpos($line, '@@ ')) {
$files[$current_file]['chunks_def'][] = self::getChunk($line);
$files[$current_file]['chunks'][] = array();
$current_chunk++;
$lline = $files[$current_file]['chunks_def'][$current_chunk-1][0][0];
$rline = $files[$current_file]['chunks_def'][$current_chunk-1][1][0];
continue;
}
if (0 === strpos($line, '---') or 0 === strpos($line, '+++')) {
$diffsize = count($this->lines);
while ($i < $diffsize) {
// look for the potential beginning of a diff
if (substr($this->lines[$i], 0, 4) !== '--- ') {
$i++;
continue;
}
if (0 === strpos($line, '-')) {
$files[$current_file]['chunks'][$current_chunk-1][] = array($lline, '', substr($line, 1));
$lline++;
// we're inside a diff candiate
$oldfileline = $this->lines[$i++];
$newfileline = $this->lines[$i++];
if (substr($newfileline, 0, 4) !== '+++ ') {
// not a valid diff here, move on
continue;
}
if (0 === strpos($line, '+')) {
$files[$current_file]['chunks'][$current_chunk-1][] = array('', $rline, substr($line, 1));
$rline++;
continue;
// use new file name by default
preg_match("/^\+\+\+ ([^\t]+)/", $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);
$current_file = $m[1];
}
if (0 === strpos($line, ' ')) {
$files[$current_file]['chunks'][$current_chunk-1][] = array($lline, $rline, substr($line, 1));
$rline++;
$lline++;
continue;
if ($this->path_strip_level > 0) {
$current_file = array_pop(explode('/', $current_file, $this->path_strip_level+1));
}
if ($line == '') {
$files[$current_file]['chunks'][$current_chunk-1][] = array($lline, $rline, $line);
$rline++;
$lline++;
continue;
$current_chunk = 0;
$files[$current_file] = array();
$files[$current_file]['chunks'] = array();
$files[$current_file]['chunks_def'] = array();
while ($i < $diffsize && substr($this->lines[$i], 0, 3) === '@@ ') {
$elems = preg_match('/@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@.*/',
$this->lines[$i++], $results);
if ($elems != 1) {
// hunk is badly formatted
break;
}
$delstart = $results[1];
$dellines = $results[2] === '' ? 1 : $results[2];
$addstart = $results[3];
$addlines = $results[4] === '' ? 1 : $results[4];
$chunks_def = array(array($delstart), array($addstart));
if ($results[2] != '') $chunks_def[0][] = $dellines;
if ($results[4] != '') $chunks_def[1][] = $addlines;
$files[$current_file]['chunks_def'][] = $chunks_def;
$files[$current_file]['chunks'][] = array();
while ($addlines >= 0 || $dellines >= 0) {
$linetype = $this->lines[$i] != '' ? $this->lines[$i][0] : ' ';
switch ($linetype) {
case ' ':
$files[$current_file]['chunks'][$current_chunk][] =
array($delstart, $addstart, substr($this->lines[$i++], 1));
$dellines--;
$addlines--;
$delstart++;
$addstart++;
break;
case '+':
$files[$current_file]['chunks'][$current_chunk][] =
array('', $addstart, substr($this->lines[$i++], 1));
$addlines--;
$addstart++;
break;
case '-':
$files[$current_file]['chunks'][$current_chunk][] =
array($delstart, '', substr($this->lines[$i++], 1));
$dellines--;
$delstart++;
break;
default:
break 2;
}
}
$current_chunk++;
}
}
$this->files = $files;
return $files;
}
public static function getFile($line)
{
$line = substr(trim($line), 10);
$n = (int) strlen($line)/2;
return trim(substr($line, 3, $n-3));
}
public static function getSvnFile($line)
{
return substr(trim($line), 7);
}
public static function getMtnFile($line)
{
preg_match("/^[+-]{3} ([^\t]+)/", $line, $m);
return $m[1];
}
/**
* Return the html version of a parsed diff.
*/
return Pluf_Template::markSafe($out);
}
public static function padLine($line)
{
$line = str_replace("\t", ' ', $line);
}
/**
* @return array array(array(start, n), array(start, n))
*/
public static function getChunk($line)
{
$elts = explode(' ', $line);
$res = array();
for ($i=1;$i<3;$i++) {
$res[] = explode(',', trim(substr($elts[$i], 1)));
}
return $res;
}
/**
* Review patch.
*
* Given the original file as a string and the parsed
return $nnew_chunks;
}
public function renderCompared($chunks, $filename)
{
$fileinfo = IDF_FileUtil::getMimeType($filename);
$i++;
}
return Pluf_Template::markSafe($out);
}
}
src/IDF/Scm.php
473473
474474
475475
476
477
478
479
480
481
482
483
484
476485
477486
{
return str_replace('%2F', '/', rawurlencode($path));
}
/**
* Returns the number of slashes and preceeding path components
* that should be stripped from paths in the SCM's diff output
*/
public function getDiffPathStripLevel()
{
return 0;
}
}
src/IDF/Scm/Git.php
649649
650650
651651
652
653
654
655
656
657
658
659
652660
653661
654662
return new Pluf_HTTP_Response_CommandPassThru($cmd, 'application/x-zip');
}
/**
* @see IDF_Scm::getDiffPathStripLevel()
*/
public function getDiffPathStripLevel()
{
return 1;
}
/*
* =====================================================
* Specific Git Commands
src/IDF/Scm/Mercurial.php
464464
465465
466466
467
468
469
470
471
472
473
474
467475
escapeshellarg($commit));
return new Pluf_HTTP_Response_CommandPassThru($cmd, 'application/x-zip');
}
/**
* @see IDF_Scm::getDiffPathStripLevel()
*/
public function getDiffPathStripLevel()
{
return 1;
}
}
src/IDF/Tests/TestDiff.php
3232
3333
3434
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
5835
5936
6037
......
9067
9168
9269
93
70
parent::__construct('Test the diff parser.');
}
public function testGetFile()
{
$lines = array(
'diff --git a/src/IDF/Form/Register.php b/src/IDF/Form/Register.php',
'diff --git a/src/IDF/Form/RegisterConfirmation.php b/src/IDF/Form/RegisterConfirmation.php',
'diff --git a/src/IDF/Form/RegisterInputKey.php b/src/IDF/Form/RegisterInputKey.php',
'diff --git a/src/IDF/Views.php b/src/IDF/Views.php',
'diff --git a/src/IDF/conf/views.php b/src/IDF/conf/views.php',
);
$files = array(
'src/IDF/Form/Register.php',
'src/IDF/Form/RegisterConfirmation.php',
'src/IDF/Form/RegisterInputKey.php',
'src/IDF/Views.php',
'src/IDF/conf/views.php',
);
$i = 0;
foreach ($lines as $line) {
$this->assertEqual($files[$i], IDF_Diff::getFile($line));
$i++;
}
}
public function testBinaryDiff()
{
$diff_content = file_get_contents(dirname(__FILE__).'/test-diff.diff');
$diff->files['src/IDF/Scm/Git.php']['chunks'][1][2]);
$this->assertEqual(7, count($diff->files['src/IDF/Scm/Git.php']['chunks'][1]));
}
}
}
src/IDF/Views/Source.php
303303
304304
305305
306
306
307
307308
308309
309310
$title = sprintf(__('%s Commit Details'), (string) $request->project);
$page_title = sprintf(__('%s Commit Details - %s'), (string) $request->project, $commit);
$rcommit = IDF_Commit::getOrAdd($cobject, $request->project);
$diff = new IDF_Diff($cobject->diff);
$diff = new IDF_Diff($cobject->diff, $scm->getDiffPathStripLevel());
$cobject->diff = null;
$diff->parse();
$scmConf = $request->conf->getVal('scm', 'git');
try {

Archive Download the corresponding diff file

Page rendered in 0.10138s using 14 queries.