1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-10 08:52:39 +01:00

Fix ArcanistBundle to generate disjoint, minimal hunks

Summary: 'patch' chokes on hunks with too much trailing stuff. 'git apply'
chokes on overlapping hunks. Make them both happy. Write some test cases so I
stop breaking this stuff.

Test Plan:
  - Applied a previously-failing patch via SVN.
  - Applied a previously-failing-then-succeeding patch via Git.
  - Ran unit tests.

Reviewers: jungejason, btrahan, nh, tuomaspelkonen, aran

Reviewed By: jungejason

CC: aran, jungejason

Differential Revision: 1099
This commit is contained in:
epriestley 2011-11-09 17:55:04 -08:00
parent 2a78b2bffa
commit 8c70ee3877
10 changed files with 258 additions and 7 deletions

View file

@ -15,6 +15,7 @@ phutil_register_library_map(array(
'ArcanistBaseWorkflow' => 'workflow/base', 'ArcanistBaseWorkflow' => 'workflow/base',
'ArcanistBranchWorkflow' => 'workflow/branch', 'ArcanistBranchWorkflow' => 'workflow/branch',
'ArcanistBundle' => 'parser/bundle', 'ArcanistBundle' => 'parser/bundle',
'ArcanistBundleTestCase' => 'parser/bundle/__tests__',
'ArcanistCallConduitWorkflow' => 'workflow/call-conduit', 'ArcanistCallConduitWorkflow' => 'workflow/call-conduit',
'ArcanistCapabilityNotSupportedException' => 'workflow/exception/notsupported', 'ArcanistCapabilityNotSupportedException' => 'workflow/exception/notsupported',
'ArcanistChooseInvalidRevisionException' => 'exception', 'ArcanistChooseInvalidRevisionException' => 'exception',
@ -104,6 +105,7 @@ phutil_register_library_map(array(
'ArcanistApacheLicenseLinter' => 'ArcanistLicenseLinter', 'ArcanistApacheLicenseLinter' => 'ArcanistLicenseLinter',
'ArcanistApacheLicenseLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistApacheLicenseLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistBranchWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistBranchWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistBundleTestCase' => 'ArcanistPhutilTestCase',
'ArcanistCallConduitWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistCallConduitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCommitWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistCommitWorkflow' => 'ArcanistBaseWorkflow',
'ArcanistCoverWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistCoverWorkflow' => 'ArcanistBaseWorkflow',

View file

@ -296,22 +296,39 @@ class ArcanistBundle {
$hunk_start = max($jj - $context, 0); $hunk_start = max($jj - $context, 0);
// NOTE: There are two tricky considerations here.
// We can not generate a patch with overlapping hunks, or 'git apply'
// rejects it after 1.7.3.4.
// We can not generate a patch with too much trailing context, or
// 'patch' rejects it.
// So we need to ensure that we generate disjoint hunks, but don't
// generate any hunks with too much context.
$old_lines = 0; $old_lines = 0;
$new_lines = 0; $new_lines = 0;
$last_change = $jj; $last_change = $jj;
$break_here = null;
for (; $jj < $n; ++$jj) { for (; $jj < $n; ++$jj) {
if ($lines[$jj][0] == ' ') { if ($lines[$jj][0] == ' ') {
// NOTE: We must look past the context size or we may generate
// overlapping hunks. For instance, if we have "context = 3" and four if ($jj - $last_change > $context) {
// unchanged lines between hunks, we'll include unchanged lines 1, 2, if ($break_here === null) {
// 3 in the first hunk and 2, 3, and 4 in the second hunk -- that is, // We haven't seen a change in $context lines, so this is a
// lines 2 and 3 will appear twice in the patch. Some time after // potential place to break the hunk. However, we need to keep
// 1.7.3.4, Git stopped cleanly applying patches with overlapping // looking in case there is another change fewer than $context
// hunks, so be careful to avoid generating them. // lines away, in which case we have to merge the hunks.
$break_here = $jj;
}
}
if ($jj - $last_change > (($context + 1) * 2)) { if ($jj - $last_change > (($context + 1) * 2)) {
// We definitely aren't going to merge this with the next hunk, so
// break out of the loop. We'll end the hunk at $break_here.
break; break;
} }
} else { } else {
$break_here = null;
$last_change = $jj; $last_change = $jj;
if ($lines[$jj][0] == '-') { if ($lines[$jj][0] == '-') {
++$old_lines; ++$old_lines;
@ -321,6 +338,10 @@ class ArcanistBundle {
} }
} }
if ($break_here !== null) {
$jj = $break_here;
}
$hunk_length = min($jj, $n) - $hunk_start; $hunk_length = min($jj, $n) - $hunk_start;
$hunk = new ArcanistDiffHunk(); $hunk = new ArcanistDiffHunk();

View file

@ -0,0 +1,70 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class ArcanistBundleTestCase extends ArcanistPhutilTestCase {
private function loadResource($name) {
return Filesystem::readFile($this->getResourcePath($name));
}
private function getResourcePath($name) {
return dirname(__FILE__).'/data/'.$name;
}
private function loadDiff($old, $new) {
list($err, $stdout) = exec_manual(
'diff --unified=65535 --label %s --label %s -- %s %s',
'file 9999-99-99',
'file 9999-99-99',
$this->getResourcePath($old),
$this->getResourcePath($new));
$this->assertEqual(
1,
$err,
"Expect `diff` to find changes between '{$old}' and '{$new}'.");
return $stdout;
}
private function loadOneChangeBundle($old, $new) {
$diff = $this->loadDiff($old, $new);
return ArcanistBundle::newFromDiff($diff);
}
public function testTrailingContext() {
// Diffs need to generate without extra trailing context, or 'patch' will
// choke on them.
$this->assertEqual(
$this->loadResource('trailing-context.diff'),
$this->loadOneChangeBundle(
'trailing-context.old',
'trailing-context.new')->toUnifiedDiff());
}
public function testDisjointHunks() {
// Diffs need to generate without overlapping hunks.
$this->assertEqual(
$this->loadResource('disjoint-hunks.diff'),
$this->loadOneChangeBundle(
'disjoint-hunks.old',
'disjoint-hunks.new')->toUnifiedDiff());
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('arcanist', 'parser/bundle');
phutil_require_module('arcanist', 'unit/engine/phutil/testcase');
phutil_require_module('phutil', 'filesystem');
phutil_require_module('phutil', 'future/exec');
phutil_require_source('ArcanistBundleTestCase.php');

View file

@ -0,0 +1,22 @@
Index: file
===================================================================
--- file
+++ file
@@ -7,14 +7,17 @@
g
h
i
+MMMM
j
k
l
m
+MMMM
n
o
p
q
+MMMM
r
s
t

View file

@ -0,0 +1,29 @@
a
b
c
d
e
f
g
h
i
MMMM
j
k
l
m
MMMM
n
o
p
q
MMMM
r
s
t
u
v
w
x
y
z

View file

@ -0,0 +1,26 @@
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z

View file

@ -0,0 +1,12 @@
Index: file
===================================================================
--- file
+++ file
@@ -7,6 +7,7 @@
g
h
i
+MMMM
j
k
l

View file

@ -0,0 +1,27 @@
a
b
c
d
e
f
g
h
i
MMMM
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z

View file

@ -0,0 +1,26 @@
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z