From ef0a9cbb79b5a9c9fccbf60c2e4234cb7c245012 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 25 Sep 2011 09:59:41 -0700 Subject: [PATCH] Parse 'hg status -C' to show file move/copy sources Summary: For Mercurial parsing in Diffusion, it looks like the only command we can use to get move/copy information is "hg status -C --rev ", so add a parser for it. Test Plan: Ran unit tests. Reviewers: Makinde, jungejason, nh, tuomaspelkonen, aran Reviewed By: Makinde CC: aran, Makinde Differential Revision: 958 --- .../mercurial/ArcanistMercurialParser.php | 47 ++++++++++++++++--- src/repository/parser/mercurial/__init__.php | 2 + .../ArcanistMercurialParserTestCase.php | 17 ++++++- .../mercurial/__tests__/data/status-moves.txt | 6 +++ 4 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 src/repository/parser/mercurial/__tests__/data/status-moves.txt diff --git a/src/repository/parser/mercurial/ArcanistMercurialParser.php b/src/repository/parser/mercurial/ArcanistMercurialParser.php index a5831e29..7820768a 100644 --- a/src/repository/parser/mercurial/ArcanistMercurialParser.php +++ b/src/repository/parser/mercurial/ArcanistMercurialParser.php @@ -30,13 +30,15 @@ final class ArcanistMercurialParser { /** - * Parse the output of "hg status". + * Parse the output of "hg status". This provides detailed information, you + * can get less detailed information with @{method:parseMercurialStatus}. In + * particular, this will parse copy sources as per "hg status -C". * * @param string The stdout from running an "hg status" command. - * @return dict Map of paths to ArcanistRepositoryAPI status flags. + * @return dict Map of paths to status dictionaries. * @task parse */ - public static function parseMercurialStatus($stdout) { + public static function parseMercurialStatusDetails($stdout) { $result = array(); $stdout = trim($stdout); @@ -44,16 +46,21 @@ final class ArcanistMercurialParser { return $result; } + $last_path = null; $lines = explode("\n", $stdout); foreach ($lines as $line) { $flags = 0; - list($code, $path) = explode(' ', $line, 2); + if ($line[1] !== ' ') { + throw new Exception("Unparsable Mercurial status line '{$line}'."); + } + $code = $line[0]; + $path = substr($line, 2); switch ($code) { case 'A': $flags |= ArcanistRepositoryAPI::FLAG_ADDED; break; case 'R': - $flags |= ArcanistRepositoryAPI::FLAG_REMOVED; + $flags |= ArcanistRepositoryAPI::FLAG_DELETED; break; case 'M': $flags |= ArcanistRepositoryAPI::FLAG_MODIFIED; @@ -71,17 +78,45 @@ final class ArcanistMercurialParser { case 'I': // This is "ignored" and included only for completeness. break; + case ' ': + // This shows the source of a file move, so update the last file we + // parsed to set its source. + if ($last_path === null) { + throw new Exception( + "Unexpected copy source in hg status, '{$line}'."); + } + $result[$last_path]['from'] = $path; + continue 2; default: throw new Exception("Unknown Mercurial status '{$code}'."); } - $result[$path] = $flags; + $result[$path] = array( + 'flags' => $flags, + 'from' => null, + ); + $last_path = $path; } return $result; } + /** + * Parse the output of "hg status". This provides only basic information, you + * can get more detailed information by invoking + * @{method:parseMercurialStatusDetails}. + * + * @param string The stdout from running an "hg status" command. + * @return dict Map of paths to ArcanistRepositoryAPI status flags. + * @task parse + */ + public static function parseMercurialStatus($stdout) { + $result = self::parseMercurialStatusDetails($stdout); + return ipull($result, 'flags'); + } + + /** * Parse the output of "hg log". This also parses "hg outgoing", "hg parents", * and other similar commands. This assumes "--style default". diff --git a/src/repository/parser/mercurial/__init__.php b/src/repository/parser/mercurial/__init__.php index 9de7b75e..2758caad 100644 --- a/src/repository/parser/mercurial/__init__.php +++ b/src/repository/parser/mercurial/__init__.php @@ -8,5 +8,7 @@ phutil_require_module('arcanist', 'repository/api/base'); +phutil_require_module('phutil', 'utils'); + phutil_require_source('ArcanistMercurialParser.php'); diff --git a/src/repository/parser/mercurial/__tests__/ArcanistMercurialParserTestCase.php b/src/repository/parser/mercurial/__tests__/ArcanistMercurialParserTestCase.php index 939efdad..19a9f7b4 100644 --- a/src/repository/parser/mercurial/__tests__/ArcanistMercurialParserTestCase.php +++ b/src/repository/parser/mercurial/__tests__/ArcanistMercurialParserTestCase.php @@ -20,7 +20,7 @@ final class ArcanistMercurialParserTestCase extends ArcanistPhutilTestCase { public function testParseAll() { $root = dirname(__FILE__).'/data/'; - foreach (Filesystem::listDirectory($root) as $file) { + foreach (Filesystem::listDirectory($root, $hidden = false) as $file) { $this->parseData( basename($file), Filesystem::readFile($root.'/'.$file)); @@ -64,6 +64,21 @@ final class ArcanistMercurialParserTestCase extends ArcanistPhutilTestCase { array('changed', 'added', 'removed', 'untracked'), array_keys($output)); break; + case 'status-moves.txt': + $output = ArcanistMercurialParser::parseMercurialStatusDetails($data); + $this->assertEqual( + 'move_source', + $output['moved_file']['from']); + $this->assertEqual( + null, + $output['changed_file']['from']); + $this->assertEqual( + 'copy_source', + $output['copied_file']['from']); + $this->assertEqual( + null, + idx($output, 'copy_source')); + break; default: throw new Exception("No test information for test data '{$name}'!"); } diff --git a/src/repository/parser/mercurial/__tests__/data/status-moves.txt b/src/repository/parser/mercurial/__tests__/data/status-moves.txt new file mode 100644 index 00000000..aa69b511 --- /dev/null +++ b/src/repository/parser/mercurial/__tests__/data/status-moves.txt @@ -0,0 +1,6 @@ +M changed_file +A moved_file + move_source +R moved_source +A copied_file + copy_source