diff --git a/src/parser/ArcanistBundle.php b/src/parser/ArcanistBundle.php index 8586e281..7ec8b89d 100644 --- a/src/parser/ArcanistBundle.php +++ b/src/parser/ArcanistBundle.php @@ -424,6 +424,9 @@ final class ArcanistBundle extends Phobject { $cur_target = 'b/'.$cur_path; } + $old_target = $this->encodeGitTargetPath($old_target); + $cur_target = $this->encodeGitTargetPath($cur_target); + $result[] = "diff --git {$old_index} {$cur_index}".$eol; if ($type == ArcanistDiffChangeType::TYPE_ADD) { @@ -591,6 +594,24 @@ final class ArcanistBundle extends Phobject { return $results; } + private function encodeGitTargetPath($path) { + // See T8768. If a target path contains spaces, it must be terminated with + // a tab. If we don't do this, Mercurial has the wrong behavior when + // applying the patch. This results in a semantic trailing whitespace + // character: + // + // +++ b/X Y.txt\t + // + // Everyone is at fault here and there are no winners. + + if (strpos($path, ' ') !== false) { + $path = $path."\t"; + } + + return $path; + } + + private function getOldPath(ArcanistDiffChange $change) { $old_path = $change->getOldPath(); $type = $change->getType(); diff --git a/src/parser/__tests__/ArcanistBundleTestCase.php b/src/parser/__tests__/ArcanistBundleTestCase.php index 8fa78f0a..94b3517f 100644 --- a/src/parser/__tests__/ArcanistBundleTestCase.php +++ b/src/parser/__tests__/ArcanistBundleTestCase.php @@ -33,6 +33,37 @@ final class ArcanistBundleTestCase extends PhutilTestCase { return ArcanistBundle::newFromDiff($diff); } + public function testTabEncoding() { + // See T8768. Test that we add semantic trailing tab literals to diffs + // touching files with spaces in them. This is a pain to encode using the + // support toolset here so just do it manually. + + // Note that the "b/X Y.txt" line has a trailing tab literal. + + $diff = <<getChanges(); + $this->assertEqual(1, count($changes)); + + // The path should parse as "X Y.txt" despite the trailing tab. + $change = head($changes); + $this->assertEqual('X Y.txt', $change->getCurrentPath()); + + // The tab should be restored when the diff is output again. + $this->assertEqual($diff, $bundle->toGitPatch()); + } + /** * Unarchive a saved git repository and apply each commit as though via * "arc patch", verifying that the resulting tree hash is identical to the