mirror of
https://we.phorge.it/source/arcanist.git
synced 2025-02-05 19:38:26 +01:00
(stable) Promote 2016 Week 34
This commit is contained in:
commit
e78618ce22
6 changed files with 190 additions and 161 deletions
|
@ -288,9 +288,11 @@ final class ArcanistDiffParser extends Phobject {
|
|||
|
||||
if (isset($match['type'])) {
|
||||
if ($match['type'] == 'diff --git') {
|
||||
list($old, $new) = self::splitGitDiffPaths($match['oldnew']);
|
||||
$match['old'] = $old;
|
||||
$match['cur'] = $new;
|
||||
$filename = self::extractGitCommonFilename($match['oldnew']);
|
||||
if ($filename !== null) {
|
||||
$match['old'] = $filename;
|
||||
$match['cur'] = $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1308,99 +1310,52 @@ final class ArcanistDiffParser extends Phobject {
|
|||
|
||||
|
||||
/**
|
||||
* Strip prefixes off paths from `git diff`. By default git uses a/ and b/,
|
||||
* but you can set `diff.mnemonicprefix` to get a different set of prefixes,
|
||||
* or use `--no-prefix`, `--src-prefix` or `--dst-prefix` to set these to
|
||||
* other arbitrary values.
|
||||
*
|
||||
* We strip the default and mnemonic prefixes, and trust the user knows what
|
||||
* they're doing in the other cases.
|
||||
*
|
||||
* @param string Path to strip.
|
||||
* @return string Stripped path.
|
||||
*/
|
||||
public static function stripGitPathPrefix($path) {
|
||||
|
||||
static $regex;
|
||||
if ($regex === null) {
|
||||
$prefixes = array(
|
||||
// These are the defaults.
|
||||
'a/',
|
||||
'b/',
|
||||
|
||||
// These show up when you set "diff.mnemonicprefix".
|
||||
'i/',
|
||||
'c/',
|
||||
'w/',
|
||||
'o/',
|
||||
'1/',
|
||||
'2/',
|
||||
);
|
||||
|
||||
foreach ($prefixes as $key => $prefix) {
|
||||
$prefixes[$key] = preg_quote($prefix, '@');
|
||||
}
|
||||
$regex = '@^('.implode('|', $prefixes).')@S';
|
||||
}
|
||||
|
||||
return preg_replace($regex, '', $path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Split the paths on a "diff --git" line into old and new paths. This
|
||||
* is difficult because they may be ambiguous if the files contain spaces.
|
||||
* Extracts the common filename from two strings with differing path
|
||||
* prefixes as found after `diff --git`. These strings may be
|
||||
* quoted; if so, the filename is returned unescaped. The prefixes
|
||||
* default to "a/" and "b/", but may be any string -- or may be
|
||||
* entierly absent. This function may return "null" if the hunk
|
||||
* represents a file move or copy, and with pathological renames may
|
||||
* return an incorrect value. Such cases are expected to be
|
||||
* recovered by later rename detection codepaths.
|
||||
*
|
||||
* @param string Text from a diff line after "diff --git ".
|
||||
* @return pair<string, string> Old and new paths.
|
||||
* @return string Filename being altered, or null for a rename.
|
||||
*/
|
||||
public static function splitGitDiffPaths($paths) {
|
||||
public static function extractGitCommonFilename($paths) {
|
||||
$matches = null;
|
||||
$paths = rtrim($paths, "\r\n");
|
||||
|
||||
$patterns = array(
|
||||
// Try quoted paths, used for unicode filenames or filenames with quotes.
|
||||
'@^(?P<old>"(?:\\\\.|[^"\\\\]+)+") (?P<new>"(?:\\\\.|[^"\\\\]+)+")$@',
|
||||
// Try the exact same string twice in a row separated by a
|
||||
// space, with an optional prefix. This can hit a false
|
||||
// positive for moves from files like "old file old" to "file",
|
||||
// but such a cases will be caught by the "rename from" /
|
||||
// "rename to" lines.
|
||||
$prefix = '(?:[^/]+/)?';
|
||||
$pattern =
|
||||
"@^(?P<old>(?P<oldq>\"?){$prefix}(?P<common>.+)\\k<oldq>)"
|
||||
." "
|
||||
."(?P<new>(?P<newq>\"?){$prefix}\\k<common>\\k<newq>)$@";
|
||||
|
||||
// Try paths without spaces.
|
||||
'@^(?P<old>[^ ]+) (?P<new>[^ ]+)$@',
|
||||
|
||||
// Try paths with well-known prefixes.
|
||||
'@^(?P<old>[abicwo12]/.*) (?P<new>[abicwo12]/.*)$@',
|
||||
|
||||
// Try the exact same string twice in a row separated by a space.
|
||||
// This can hit a false positive for moves from files like "old file old"
|
||||
// to "file", but such a case combined with custom diff prefixes is
|
||||
// incredibly obscure.
|
||||
'@^(?P<old>.*) (?P<new>\\1)$@',
|
||||
);
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match($pattern, $paths, $matches)) {
|
||||
break;
|
||||
}
|
||||
if (!preg_match($pattern, $paths, $matches)) {
|
||||
// A rename or some form; return null for now, and let the
|
||||
// "rename from" / "rename to" lines fix it up.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$matches) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
"Input diff contains ambiguous line '%s'. This line is ambiguous ".
|
||||
"because there are spaces in the file names, so the parser can not ".
|
||||
"determine where the file names begin and end. To resolve this ".
|
||||
"ambiguity, use standard prefixes ('a/' and 'b/') when ".
|
||||
"generating diffs.",
|
||||
"diff --git {$paths}"));
|
||||
}
|
||||
// Use the common subpart. There may be ambiguity here: "src/file
|
||||
// dst/file" may _either_ be a prefix-less move, or a change with
|
||||
// two custom prefixes. We assume it is the latter; if it is a
|
||||
// rename, diff parsing will update based on the "rename from" /
|
||||
// "rename to" lines.
|
||||
|
||||
$old = $matches['old'];
|
||||
$old = self::unescapeFilename($old);
|
||||
$old = self::stripGitPathPrefix($old);
|
||||
|
||||
$new = $matches['new'];
|
||||
// This re-assembles with the differing prefixes removed, but the
|
||||
// quoting from the original. Necessary so we know if we should
|
||||
// unescape characters from the common string.
|
||||
$new = $matches['newq'].$matches['common'].$matches['newq'];
|
||||
$new = self::unescapeFilename($new);
|
||||
$new = self::stripGitPathPrefix($new);
|
||||
|
||||
return array($old, $new);
|
||||
return $new;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -535,7 +535,14 @@ EOTEXT
|
|||
$this->assertEqual(1, count($changes));
|
||||
$change = head($changes);
|
||||
$this->assertEqual(
|
||||
'dst/file',
|
||||
'file',
|
||||
$change->getCurrentPath());
|
||||
break;
|
||||
case 'custom-prefixes-edit.gitdiff':
|
||||
$this->assertEqual(1, count($changes));
|
||||
$change = head($changes);
|
||||
$this->assertEqual(
|
||||
'file',
|
||||
$change->getCurrentPath());
|
||||
break;
|
||||
case 'more-newlines.svndiff':
|
||||
|
@ -607,81 +614,111 @@ EOTEXT
|
|||
}
|
||||
}
|
||||
|
||||
public function testGitPrefixStripping() {
|
||||
public function testGitCommonFilenameExtraction() {
|
||||
static $tests = array(
|
||||
'a/file.c' => 'file.c',
|
||||
'b/file.c' => 'file.c',
|
||||
'i/file.c' => 'file.c',
|
||||
'c/file.c' => 'file.c',
|
||||
'w/file.c' => 'file.c',
|
||||
'o/file.c' => 'file.c',
|
||||
'1/file.c' => 'file.c',
|
||||
'2/file.c' => 'file.c',
|
||||
'src/file.c' => 'src/file.c',
|
||||
'file.c' => 'file.c',
|
||||
'a/filename.c b/filename.c' => 'filename.c',
|
||||
"a/filename.c b/filename.c\n" => 'filename.c',
|
||||
"a/filename.c b/filename.c\r\n" => 'filename.c',
|
||||
'filename.c filename.c' => 'filename.c',
|
||||
'1/filename.c 2/filename.c' => 'filename.c',
|
||||
'"a/\\"quotes\\"" "b/\\"quotes\\""' => '"quotes"',
|
||||
'"a/\\"quotes and spaces\\"" "b/\\"quotes and spaces\\""' =>
|
||||
'"quotes and spaces"',
|
||||
'"a/\\342\\230\\203" "b/\\342\\230\\203"' =>
|
||||
"\xE2\x98\x83",
|
||||
'a/Core Data/filename.c b/Core Data/filename.c' =>
|
||||
'Core Data/filename.c',
|
||||
'some file with spaces.c some file with spaces.c' =>
|
||||
'some file with spaces.c',
|
||||
'"foo bar.c" foo bar.c' => 'foo bar.c',
|
||||
'"a/foo bar.c" b/foo bar.c' => 'foo bar.c',
|
||||
'src/file dst/file' => 'file',
|
||||
|
||||
// Renames are handled by the "rename from ..." lines later in
|
||||
// the diff, for simplicity of parsing; this is also how git
|
||||
// itself handles it.
|
||||
'a/foo.c b/bar.c' => null,
|
||||
'a/foo bar.c b/baz troz.c' => null,
|
||||
'"a/foo bar.c" b/baz troz.c' => null,
|
||||
'a/foo bar.c "b/baz troz.c"' => null,
|
||||
'"a/foo bar.c" "b/baz troz.c"' => null,
|
||||
'filename file with spaces.c filename file with spaces.c' =>
|
||||
'filename file with spaces.c',
|
||||
);
|
||||
|
||||
foreach ($tests as $input => $expect) {
|
||||
$this->assertEqual(
|
||||
$expect,
|
||||
ArcanistDiffParser::stripGitPathPrefix($input),
|
||||
pht("Strip git prefix from '%s'.", $input));
|
||||
}
|
||||
}
|
||||
|
||||
public function testGitPathSplitting() {
|
||||
static $tests = array(
|
||||
'a/old.c b/new.c' => array('old.c', 'new.c'),
|
||||
"a/old.c b/new.c\n" => array('old.c', 'new.c'),
|
||||
"a/old.c b/new.c\r\n" => array('old.c', 'new.c'),
|
||||
'old.c new.c' => array('old.c', 'new.c'),
|
||||
'1/old.c 2/new.c' => array('old.c', 'new.c'),
|
||||
'"a/\\"quotes1\\"" "b/\\"quotes2\\""' => array(
|
||||
'"quotes1"',
|
||||
'"quotes2"',
|
||||
),
|
||||
'"a/\\"quotes and spaces1\\"" "b/\\"quotes and spaces2\\""' => array(
|
||||
'"quotes and spaces1"',
|
||||
'"quotes and spaces2"',
|
||||
),
|
||||
'"a/\\342\\230\\2031" "b/\\342\\230\\2032"' => array(
|
||||
"\xE2\x98\x831",
|
||||
"\xE2\x98\x832",
|
||||
),
|
||||
'a/Core Data/old.c b/Core Data/new.c' => array(
|
||||
'Core Data/old.c',
|
||||
'Core Data/new.c',
|
||||
),
|
||||
'some file with spaces.c some file with spaces.c' => array(
|
||||
'some file with spaces.c',
|
||||
'some file with spaces.c',
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($tests as $input => $expect) {
|
||||
$result = ArcanistDiffParser::splitGitDiffPaths($input);
|
||||
$result = ArcanistDiffParser::extractGitCommonFilename($input);
|
||||
$this->assertEqual(
|
||||
$expect,
|
||||
$result,
|
||||
pht('Split: %s', $input));
|
||||
}
|
||||
|
||||
|
||||
static $ambiguous = array(
|
||||
'old file with spaces.c new file with spaces.c',
|
||||
);
|
||||
|
||||
foreach ($ambiguous as $input) {
|
||||
$caught = null;
|
||||
try {
|
||||
ArcanistDiffParser::splitGitDiffPaths($input);
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
$this->assertTrue(
|
||||
($caught instanceof Exception),
|
||||
pht('Ambiguous: %s', $input));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function runSingleRename($diffline, $from, $to, $old, $new) {
|
||||
$str = "diff --git $diffline\nsimilarity index 95%\n"
|
||||
."rename from $from\nrename to $to\n";
|
||||
$parser = new ArcanistDiffParser();
|
||||
$changes = $parser->parseDiff($str);
|
||||
$this->assertTrue(
|
||||
$changes !== null,
|
||||
pht("Parsed:\n%s", $str));
|
||||
$this->assertEqual(
|
||||
$old == $new ? 1 : 2, count($changes),
|
||||
pht("Parsed one change:\n%s", $str));
|
||||
$change = reset($changes);
|
||||
$this->assertEqual(
|
||||
array($old, $new),
|
||||
array($change->getOldPath(), $change->getCurrentPath()),
|
||||
pht('Split: %s', $diffline));
|
||||
}
|
||||
|
||||
public function testGitRenames() {
|
||||
$this->runSingleRename('a/old.c b/new.c',
|
||||
'old.c', 'new.c',
|
||||
'old.c', 'new.c');
|
||||
$this->runSingleRename('old.c new.c',
|
||||
'old.c', 'new.c',
|
||||
'old.c', 'new.c');
|
||||
$this->runSingleRename('1/old.c 2/new.c',
|
||||
'old.c', 'new.c',
|
||||
'old.c', 'new.c');
|
||||
$this->runSingleRename('from/file.c to/file.c',
|
||||
'from/file.c', 'to/file.c',
|
||||
'from/file.c', 'to/file.c');
|
||||
$this->runSingleRename('"a/\\"quotes1\\"" "b/\\"quotes2\\""',
|
||||
'"\\"quotes1\\""', '"\\"quotes2\\""',
|
||||
'"quotes1"', '"quotes2"');
|
||||
$this->runSingleRename('"a/\\"quotes spaces1\\"" "b/\\"quotes spaces2\\""',
|
||||
'"\\"quotes spaces1\\""', '"\\"quotes spaces2\\""',
|
||||
'"quotes spaces1"', '"quotes spaces2"');
|
||||
$this->runSingleRename('"a/\\342\\230\\2031" "b/\\342\\230\\2032"',
|
||||
'"\\342\\230\\2031"', '"\\342\\230\\2032"',
|
||||
"\xE2\x98\x831", "\xE2\x98\x832");
|
||||
$this->runSingleRename('a/Core Data/old.c b/Core Data/new.c',
|
||||
'Core Data/old.c', 'Core Data/new.c',
|
||||
'Core Data/old.c', 'Core Data/new.c');
|
||||
$this->runSingleRename('file with spaces.c file with spaces.c',
|
||||
'file with spaces.c', 'file with spaces.c',
|
||||
'file with spaces.c', 'file with spaces.c');
|
||||
$this->runSingleRename('a/non-quoted filename.c "b/quoted filename.c"',
|
||||
'non-quoted filename.c', '"quoted filename.c"',
|
||||
'non-quoted filename.c', 'quoted filename.c');
|
||||
$this->runSingleRename('non-quoted filename.c "quoted filename.c"',
|
||||
'non-quoted filename.c', '"quoted filename.c"',
|
||||
'non-quoted filename.c', 'quoted filename.c');
|
||||
$this->runSingleRename('"a/quoted filename.c" b/non quoted filename.c',
|
||||
'"quoted filename.c"', 'non quoted filename.c',
|
||||
'quoted filename.c', 'non quoted filename.c');
|
||||
$this->runSingleRename('"quoted filename.c" non-quoted filename.c',
|
||||
'"quoted filename.c"', 'non-quoted filename.c',
|
||||
'quoted filename.c', 'non-quoted filename.c');
|
||||
$this->runSingleRename('old file with spaces.c new file with spaces.c',
|
||||
'old file with spaces.c', 'new file with spaces.c',
|
||||
'old file with spaces.c', 'new file with spaces.c');
|
||||
$this->runSingleRename('old file old file',
|
||||
'old file old', 'file',
|
||||
'old file old', 'file');
|
||||
}
|
||||
}
|
||||
|
|
11
src/parser/__tests__/diff/custom-prefixes-edit.gitdiff
Normal file
11
src/parser/__tests__/diff/custom-prefixes-edit.gitdiff
Normal file
|
@ -0,0 +1,11 @@
|
|||
diff --git src/file dst/file
|
||||
index 711edb6..a8cd7be 100644
|
||||
--- src/file
|
||||
+++ dst/file
|
||||
@@ -5,5 +5,5 @@
|
||||
some context
|
||||
other context
|
||||
-old value
|
||||
+new value
|
||||
some context
|
||||
other context
|
|
@ -826,6 +826,22 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
|||
$mask |= self::FLAG_EXTERNALS;
|
||||
} else if (isset($flags[$flag])) {
|
||||
$mask |= $flags[$flag];
|
||||
} else if ($flag[0] == 'R') {
|
||||
$both = explode("\t", $file);
|
||||
if ($full) {
|
||||
$files[$both[0]] = array(
|
||||
'mask' => $mask | self::FLAG_DELETED,
|
||||
'ref' => str_repeat('0', 40),
|
||||
);
|
||||
} else {
|
||||
$files[$both[0]] = $mask | self::FLAG_DELETED;
|
||||
}
|
||||
$file = $both[1];
|
||||
$mask |= self::FLAG_ADDED;
|
||||
} else if ($flag[0] == 'C') {
|
||||
$both = explode("\t", $file);
|
||||
$file = $both[1];
|
||||
$mask |= self::FLAG_ADDED;
|
||||
}
|
||||
|
||||
if ($full) {
|
||||
|
|
|
@ -116,8 +116,7 @@ final class ArcanistFileUploader extends Phobject {
|
|||
$conduit = $this->conduit;
|
||||
$futures = array();
|
||||
foreach ($files as $key => $file) {
|
||||
$params = array(
|
||||
'name' => $file->getName(),
|
||||
$params = $this->getUploadParameters($file) + array(
|
||||
'contentLength' => $file->getByteSize(),
|
||||
'contentHash' => $file->getContentHash(),
|
||||
);
|
||||
|
@ -127,11 +126,6 @@ final class ArcanistFileUploader extends Phobject {
|
|||
$params['deleteAfterEpoch'] = $delete_after;
|
||||
}
|
||||
|
||||
$view_policy = $file->getViewPolicy();
|
||||
if ($view_policy !== null) {
|
||||
$params['viewPolicy'] = $view_policy;
|
||||
}
|
||||
|
||||
$futures[$key] = $conduit->callMethod('file.allocate', $params);
|
||||
}
|
||||
|
||||
|
@ -294,13 +288,29 @@ final class ArcanistFileUploader extends Phobject {
|
|||
|
||||
return $conduit->callMethodSynchronous(
|
||||
'file.upload',
|
||||
array(
|
||||
'name' => $file->getName(),
|
||||
$this->getUploadParameters($file) + array(
|
||||
'data_base64' => base64_encode($data),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get common parameters for file uploads.
|
||||
*/
|
||||
private function getUploadParameters(ArcanistFileDataRef $file) {
|
||||
$params = array(
|
||||
'name' => $file->getName(),
|
||||
);
|
||||
|
||||
$view_policy = $file->getViewPolicy();
|
||||
if ($view_policy !== null) {
|
||||
$params['viewPolicy'] = $view_policy;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write a status message.
|
||||
*
|
||||
|
|
|
@ -2528,7 +2528,7 @@ EOTEXT
|
|||
"You don't own revision %s: \"%s\". Normally, you should ".
|
||||
"only update revisions you own. You can \"Commandeer\" this revision ".
|
||||
"from the web interface if you want to become the owner.\n\n".
|
||||
"Update this revision anyway? [y/N]",
|
||||
"Update this revision anyway?",
|
||||
"D{$id}",
|
||||
$title);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue