From d0425fc238c3255f1436f311424121255adfab62 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 3 Oct 2012 15:32:03 -0700 Subject: [PATCH] Retain newline style when generating diffs Summary: Builds on D3441, D3440. Instead of exploding on "\r?\n" and then imploding on "\n", retain newline style throughout parsing. Test Plan: All unit tests pass (the parser has substantial existing coverage). These new tests pass in the git commit-by-commit test case: commit 176a4c2c3fd88b2d598ce41a55d9c3958be9fd2d Author: epriestley Date: Wed Sep 5 14:56:08 2012 -0700 Convert \r\n newlines to \n newlines. commit a73b28e139296d23ade768f2346038318b331f94 Author: epriestley Date: Wed Sep 5 14:55:31 2012 -0700 Add text with \r\n newlines. commit 337ccec314075a2bdb4a912ef467d35d04a713e4 Author: epriestley Date: Wed Sep 5 14:55:13 2012 -0700 Convert \n newlines to \r\n newlines. commit 6d5e64a4a7a6a036c53b1d087184cb2c70099f2c Author: epriestley Date: Wed Sep 5 14:53:39 2012 -0700 Remove tabs. commit 49395994a1a8a06287e40a3b318be4349e8e0288 Author: epriestley Date: Wed Sep 5 14:53:33 2012 -0700 Add tabs. commit a5a53c424f3c2a7e85f6aee35e834c8ec5b3dbe3 Author: epriestley Date: Wed Sep 5 14:52:57 2012 -0700 Add trailing newline. commit d53dc614090c6c7d6d023e170877d7f611f18f5a Author: epriestley Date: Wed Sep 5 14:52:41 2012 -0700 Remove trailing newline. Previously, the newline-related tests failed. Reviewers: vrana, btrahan Reviewed By: vrana CC: aran Maniphest Tasks: T866 Differential Revision: https://secure.phabricator.com/D3442 --- src/parser/ArcanistBundle.php | 58 +++++++------- src/parser/ArcanistDiffParser.php | 71 ++++++++++++------ .../__tests__/ArcanistDiffParserTestCase.php | 2 + src/parser/__tests__/bundle.git.tgz | Bin 7276 -> 10428 bytes 4 files changed, 77 insertions(+), 54 deletions(-) diff --git a/src/parser/ArcanistBundle.php b/src/parser/ArcanistBundle.php index 91ccb541..9bc003b3 100644 --- a/src/parser/ArcanistBundle.php +++ b/src/parser/ArcanistBundle.php @@ -226,7 +226,9 @@ final class ArcanistBundle { } $result[] = 'Index: '.$index_path; + $result[] = PHP_EOL; $result[] = str_repeat('=', 67); + $result[] = PHP_EOL; if ($old_path === null) { $old_path = '/dev/null'; @@ -247,8 +249,8 @@ final class ArcanistBundle { $old_path = $cur_path; } - $result[] = '--- '.$old_path; - $result[] = '+++ '.$cur_path; + $result[] = '--- '.$old_path.PHP_EOL; + $result[] = '+++ '.$cur_path.PHP_EOL; $result[] = $hunk_changes; } @@ -257,7 +259,7 @@ final class ArcanistBundle { return ''; } - $diff = implode("\n", $result)."\n"; + $diff = implode('', $result); return $this->convertNonUTF8Diff($diff); } @@ -383,45 +385,45 @@ final class ArcanistBundle { $cur_target = 'b/'.$cur_path; } - $result[] = "diff --git {$old_index} {$cur_index}"; + $result[] = "diff --git {$old_index} {$cur_index}".PHP_EOL; if ($type == ArcanistDiffChangeType::TYPE_ADD) { - $result[] = "new file mode {$new_mode}"; + $result[] = "new file mode {$new_mode}".PHP_EOL; } if ($type == ArcanistDiffChangeType::TYPE_COPY_HERE || $type == ArcanistDiffChangeType::TYPE_MOVE_HERE || $type == ArcanistDiffChangeType::TYPE_COPY_AWAY) { if ($old_mode !== $new_mode) { - $result[] = "old mode {$old_mode}"; - $result[] = "new mode {$new_mode}"; + $result[] = "old mode {$old_mode}".PHP_EOL; + $result[] = "new mode {$new_mode}".PHP_EOL; } } if ($type == ArcanistDiffChangeType::TYPE_COPY_HERE) { - $result[] = "copy from {$old_path}"; - $result[] = "copy to {$cur_path}"; + $result[] = "copy from {$old_path}".PHP_EOL; + $result[] = "copy to {$cur_path}".PHP_EOL; } else if ($type == ArcanistDiffChangeType::TYPE_MOVE_HERE) { - $result[] = "rename from {$old_path}"; - $result[] = "rename to {$cur_path}"; + $result[] = "rename from {$old_path}".PHP_EOL; + $result[] = "rename to {$cur_path}".PHP_EOL; } else if ($type == ArcanistDiffChangeType::TYPE_DELETE || $type == ArcanistDiffChangeType::TYPE_MULTICOPY) { $old_mode = idx($change->getOldProperties(), 'unix:filemode'); if ($old_mode) { - $result[] = "deleted file mode {$old_mode}"; + $result[] = "deleted file mode {$old_mode}".PHP_EOL; } } if ($change_body) { if (!$is_binary) { - $result[] = "--- {$old_target}"; - $result[] = "+++ {$cur_target}"; + $result[] = "--- {$old_target}".PHP_EOL; + $result[] = "+++ {$cur_target}".PHP_EOL; } $result[] = $change_body; } } - $diff = implode("\n", $result)."\n"; + $diff = implode('', $result).PHP_EOL; return $this->convertNonUTF8Diff($diff); } @@ -440,7 +442,7 @@ final class ArcanistBundle { $context = 3; $results = array(); - $lines = explode("\n", $base_hunk->getCorpus()); + $lines = phutil_split_lines($base_hunk->getCorpus()); $n = count($lines); $old_offset = $base_hunk->getOldOffset(); @@ -530,7 +532,7 @@ final class ArcanistBundle { $hunk->setNewLength($count_length - $old_lines); $corpus = array_slice($lines, $hunk_start, $hunk_length); - $corpus = implode("\n", $corpus); + $corpus = implode('', $corpus); $hunk->setCorpus($corpus); $results[] = $hunk; @@ -596,11 +598,11 @@ final class ArcanistBundle { $n_head = "{$n_off},{$n_len}"; } - $result[] = "@@ -{$o_head} +{$n_head} @@"; + $result[] = "@@ -{$o_head} +{$n_head} @@".PHP_EOL; $result[] = $corpus; } } - return implode("\n", $result); + return implode('', $result); } public function setLoadFileDataCallback($callback) { @@ -656,16 +658,16 @@ final class ArcanistBundle { } $content = array(); - $content[] = "index {$old_sha1}..{$new_sha1}"; - $content[] = "GIT binary patch"; + $content[] = "index {$old_sha1}..{$new_sha1}".PHP_EOL; + $content[] = "GIT binary patch".PHP_EOL; - $content[] = "literal {$new_length}"; - $content[] = $this->emitBinaryDiffBody($new_data); + $content[] = "literal {$new_length}".PHP_EOL; + $content[] = $this->emitBinaryDiffBody($new_data).PHP_EOL; - $content[] = "literal {$old_length}"; - $content[] = $this->emitBinaryDiffBody($old_data); + $content[] = "literal {$old_length}".PHP_EOL; + $content[] = $this->emitBinaryDiffBody($old_data).PHP_EOL; - return implode("\n", $content); + return implode('', $content); } private function emitBinaryDiffBody($data) { @@ -691,11 +693,9 @@ final class ArcanistBundle { $buf .= chr($len - 26 + ord('a') - 1); } $buf .= self::encodeBase85($line); - $buf .= "\n"; + $buf .= PHP_EOL; } - $buf .= "\n"; - return $buf; } diff --git a/src/parser/ArcanistDiffParser.php b/src/parser/ArcanistDiffParser.php index fcada2a0..ae582815 100644 --- a/src/parser/ArcanistDiffParser.php +++ b/src/parser/ArcanistDiffParser.php @@ -235,10 +235,11 @@ final class ArcanistDiffParser { '(?Pdiff -r) (?P[a-f0-9]+) (?:-r [a-f0-9]+ )?(?P.+)', ); - $line = $this->getLine(); + $line = $this->getLineTrimmed(); $match = null; $ok = $this->tryMatchHeader($patterns, $line, $match); + $failed_parse = false; if (!$ok && $this->isFirstNonEmptyLine()) { // 'hg export' command creates so called "extended diff" that // contains some meta information and comment at the beginning @@ -250,13 +251,19 @@ final class ArcanistDiffParser { if (!$this->tryMatchHeader($patterns, $line, $match)) { // Restore line before guessing to display correct error. $this->restoreLine(); - $this->didFailParse( - "Expected a hunk header, like 'Index: /path/to/file.ext' (svn), ". - "'Property changes on: /path/to/file.ext' (svn properties), ". - "'commit 59bcc3ad6775562f845953cf01624225' (git show), ". - "'diff --git' (git diff), '--- filename' (unified diff), or " . - "'diff -r' (hg diff or patch)."); + $failed_parse = true; } + } else if (!$ok) { + $failed_parse = true; + } + + if ($failed_parse) { + $this->didFailParse( + "Expected a hunk header, like 'Index: /path/to/file.ext' (svn), ". + "'Property changes on: /path/to/file.ext' (svn properties), ". + "'commit 59bcc3ad6775562f845953cf01624225' (git show), ". + "'diff --git' (git diff), '--- filename' (unified diff), or " . + "'diff -r' (hg diff or patch)."); } if (isset($match['type'])) { @@ -361,15 +368,17 @@ final class ArcanistDiffParser { $this->didFailParse("Expected 'Date:'."); } - while (($line = $this->nextLine()) !== null) { + while (($line = $this->nextLineTrimmed()) !== null) { if (strlen($line) && $line[0] != ' ') { break; } - // Strip leading spaces from Git commit messages. - $message[] = substr($line, 4); + + // Strip leading spaces from Git commit messages. Note that empty lines + // are represented as just "\n"; don't touch those. + $message[] = preg_replace('/^ /', '', $this->getLine()); } - $message = rtrim(implode("\n", $message)); + $message = rtrim(implode('', $message), "\r\n"); $change->setMetadata('message', $message); } @@ -472,8 +481,8 @@ final class ArcanistDiffParser { $line = $this->nextLine(); } - $old = rtrim(implode("\n", $old)); - $new = rtrim(implode("\n", $new)); + $old = rtrim(implode('', $old)); + $new = rtrim(implode('', $new)); if (!strlen($old)) { $old = null; @@ -754,7 +763,7 @@ final class ArcanistDiffParser { $this->didFailParse("Expected 'literal NNNN' to start git binary patch."); } do { - $line = $this->nextLine(); + $line = $this->nextLineTrimmed(); if ($line === '' || $line === null) { // Some versions of Mercurial apparently omit the terminal newline, // although it's unclear if Git will ever do this. In either case, @@ -800,7 +809,7 @@ final class ArcanistDiffParser { $all_changes = array(); do { $hunk = new ArcanistDiffHunk(); - $line = $this->getLine(); + $line = $this->getLineTrimmed(); $real = array(); // In the case where only one line is changed, the length is omitted. @@ -896,6 +905,8 @@ final class ArcanistDiffParser { --$new_len; $real[] = $line; break; + case "\r": + case "\n": case '~': $advance = true; break 2; @@ -908,7 +919,7 @@ final class ArcanistDiffParser { $this->didFailParse("Found the wrong number of hunk lines."); } - $corpus = implode("\n", $real); + $corpus = implode('', $real); $is_binary = false; if ($this->detectBinaryFiles) { @@ -1007,13 +1018,7 @@ final class ArcanistDiffParser { $text = preg_replace('/'.$ansi_color_pattern.'/', '', $text); } - // TODO: This is a hack for SVN + Windows. Eventually, we should retain line - // endings and preserve them through the patch lifecycle or something along - // those lines. - - // NOTE: On Windows, a diff may have \n delimited lines (because the file - // uses \n) but \r\n delimited blocks. Split on both. - $this->text = preg_split('/\r?\n/', $text); + $this->text = phutil_split_lines($text); $this->line = 0; } @@ -1027,11 +1032,27 @@ final class ArcanistDiffParser { return null; } + protected function getLineTrimmed() { + $line = $this->getLine(); + if ($line !== null) { + $line = trim($line, "\r\n"); + } + return $line; + } + protected function nextLine() { $this->line++; return $this->getLine(); } + protected function nextLineTrimmed() { + $line = $this->nextLine(); + if ($line !== null) { + $line = trim($line, "\r\n"); + } + return $line; + } + protected function nextNonemptyLine() { while (($line = $this->nextLine()) !== null) { if (strlen(trim($line)) !== 0) { @@ -1079,14 +1100,14 @@ final class ArcanistDiffParser { } protected function didFailParse($message) { - $context = 3; + $context = 5; $min = max(0, $this->line - $context); $max = min($this->line + $context, count($this->text) - 1); $context = ''; for ($ii = $min; $ii <= $max; $ii++) { $context .= sprintf( - "%8.8s %6.6s %s\n", + "%8.8s %6.6s %s", ($ii == $this->line) ? '>>> ' : '', $ii + 1, $this->text[$ii]); diff --git a/src/parser/__tests__/ArcanistDiffParserTestCase.php b/src/parser/__tests__/ArcanistDiffParserTestCase.php index 025dbae4..3a6b785d 100644 --- a/src/parser/__tests__/ArcanistDiffParserTestCase.php +++ b/src/parser/__tests__/ArcanistDiffParserTestCase.php @@ -93,6 +93,7 @@ final class ArcanistDiffParserTestCase extends ArcanistTestCase { %% %% %%% + EOCORPUS; $corpus_1 = <<assertEqual( $corpus_0, diff --git a/src/parser/__tests__/bundle.git.tgz b/src/parser/__tests__/bundle.git.tgz index c3a3fcac2e53eb8ca7a8fdd65edbc77a4e0e4adb..41e85594279eaeae04b1555b874dbf3af0e829d1 100644 GIT binary patch literal 10428 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
    _0l7UbQ*aH3Mk=T5n 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 GgZUpS4GJs( literal 7276 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