Changeset View
Changeset View
Standalone View
Standalone View
src/parser/ArcanistDiffParser.php
Context not available. | |||||
$root = $rinfo['URL'].'/'; | $root = $rinfo['URL'].'/'; | ||||
} | } | ||||
$cpath = $info['Copied From URL']; | $cpath = $info['Copied From URL']; | ||||
$root_len = strlen($root); | $root_len = @strlen($root); | ||||
if (!strncmp($cpath, $root, $root_len)) { | if (!strncmp($cpath, $root, $root_len)) { | ||||
$cpath = substr($cpath, $root_len); | $cpath = @substr($cpath, $root_len); | ||||
// The user can "svn cp /path/to/file@12345 x", which pulls a file out | // The user can "svn cp /path/to/file@12345 x", which pulls a file out | ||||
// of version history at a specific revision. If we just use the path, | // of version history at a specific revision. If we just use the path, | ||||
// we'll collide with possible changes to that path in the working | // we'll collide with possible changes to that path in the working | ||||
Context not available. | |||||
} | } | ||||
public function parseDiff($diff) { | public function parseDiff($diff) { | ||||
if (!strlen(trim($diff))) { | if (!@strlen(trim($diff))) { | ||||
throw new Exception(pht("Can't parse an empty diff!")); | throw new Exception(pht("Can't parse an empty diff!")); | ||||
} | } | ||||
Context not available. | |||||
// lost in transit. | // lost in transit. | ||||
$detect_patch = '/^---$.*^-- ?[\s\d.]+\z/ms'; | $detect_patch = '/^---$.*^-- ?[\s\d.]+\z/ms'; | ||||
$message = null; | $message = null; | ||||
if (preg_match($detect_patch, $diff)) { | if (@preg_match($detect_patch, $diff)) { | ||||
list($message, $diff) = $this->stripGitFormatPatch($diff); | list($message, $diff) = $this->stripGitFormatPatch($diff); | ||||
} | } | ||||
Context not available. | |||||
// file, `git apply` is more strict. We get these comments in `hg export` | // file, `git apply` is more strict. We get these comments in `hg export` | ||||
// diffs, and Eclipse can also produce them. | // diffs, and Eclipse can also produce them. | ||||
$line = $this->getLineTrimmed(); | $line = $this->getLineTrimmed(); | ||||
while (preg_match('/^#/', $line)) { | while (@preg_match('/^#/', $line)) { | ||||
$line = $this->nextLine(); | $line = $this->nextLine(); | ||||
} | } | ||||
if (strlen($message)) { | if (@strlen($message)) { | ||||
// If we found a message during pre-parse steps, add it to the resulting | // If we found a message during pre-parse steps, add it to the resulting | ||||
// changes here. | // changes here. | ||||
$change = $this->buildChange(null) | $change = $this->buildChange(null) | ||||
Context not available. | |||||
$this->parseCommitMessage($change); | $this->parseCommitMessage($change); | ||||
break; | break; | ||||
case '---': | case '---': | ||||
$ok = preg_match( | $ok = @preg_match( | ||||
'@^(?:\+\+\+) (.*)\s+\d{4}-\d{2}-\d{2}.*$@', | '@^(?:\+\+\+) (.*)\s+\d{4}-\d{2}-\d{2}.*$@', | ||||
$line, | $line, | ||||
$match); | $match); | ||||
Context not available. | |||||
protected function tryMatchHeader($patterns, $line, &$match) { | protected function tryMatchHeader($patterns, $line, &$match) { | ||||
foreach ($patterns as $pattern) { | foreach ($patterns as $pattern) { | ||||
if (preg_match('@^'.$pattern.'$@', $line, $match)) { | if (@preg_match('@^'.$pattern.'$@', $line, $match)) { | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
Context not available. | |||||
$message = array(); | $message = array(); | ||||
$line = $this->getLine(); | $line = $this->getLine(); | ||||
if (preg_match('/^Merge: /', $line)) { | if (@preg_match('/^Merge: /', $line)) { | ||||
$this->nextLine(); | $this->nextLine(); | ||||
} | } | ||||
$line = $this->getLine(); | $line = $this->getLine(); | ||||
if (!preg_match('/^Author: /', $line)) { | if (!@preg_match('/^Author: /', $line)) { | ||||
$this->didFailParse(pht("Expected 'Author:'.")); | $this->didFailParse(pht("Expected 'Author:'.")); | ||||
} | } | ||||
$line = $this->nextLine(); | $line = $this->nextLine(); | ||||
if (!preg_match('/^Date: /', $line)) { | if (!@preg_match('/^Date: /', $line)) { | ||||
$this->didFailParse(pht("Expected 'Date:'.")); | $this->didFailParse(pht("Expected 'Date:'.")); | ||||
} | } | ||||
while (($line = $this->nextLineTrimmed()) !== null) { | while (($line = $this->nextLineTrimmed()) !== null) { | ||||
if (strlen($line) && $line[0] != ' ') { | if (@strlen($line) && $line[0] != ' ') { | ||||
break; | break; | ||||
} | } | ||||
Context not available. | |||||
*/ | */ | ||||
protected function parsePropertyHunk(ArcanistDiffChange $change) { | protected function parsePropertyHunk(ArcanistDiffChange $change) { | ||||
$line = $this->getLineTrimmed(); | $line = $this->getLineTrimmed(); | ||||
if (!preg_match('/^_+$/', $line)) { | if (!@preg_match('/^_+$/', $line)) { | ||||
$this->didFailParse(pht("Expected '%s'.", '______________________')); | $this->didFailParse(pht("Expected '%s'.", '______________________')); | ||||
} | } | ||||
$line = $this->nextLine(); | $line = $this->nextLine(); | ||||
while ($line !== null) { | while ($line !== null) { | ||||
$done = preg_match('/^(Index|Property changes on):/', $line); | $done = @preg_match('/^(Index|Property changes on):/', $line); | ||||
if ($done) { | if ($done) { | ||||
break; | break; | ||||
} | } | ||||
Context not available. | |||||
// "Modified", "Added" and "Deleted". | // "Modified", "Added" and "Deleted". | ||||
$matches = null; | $matches = null; | ||||
$ok = preg_match( | $ok = @preg_match( | ||||
'/^(Name|Modified|Added|Deleted): (.*)$/', | '/^(Name|Modified|Added|Deleted): (.*)$/', | ||||
$line, | $line, | ||||
$matches); | $matches); | ||||
Context not available. | |||||
$line = $this->nextLine(); | $line = $this->nextLine(); | ||||
$prop_index = 2; | $prop_index = 2; | ||||
while ($line !== null) { | while ($line !== null) { | ||||
$done = preg_match( | $done = @preg_match( | ||||
'/^(Modified|Added|Deleted|Index|Property changes on):/', | '/^(Modified|Added|Deleted|Index|Property changes on):/', | ||||
$line); | $line); | ||||
if ($done) { | if ($done) { | ||||
Context not available. | |||||
'+')); | '+')); | ||||
} | } | ||||
$target = 'new'; | $target = 'new'; | ||||
$line = substr($trimline, $prop_index); | $line = @substr($trimline, $prop_index); | ||||
} else if ($trimline && $trimline[0] == '-') { | } else if ($trimline && $trimline[0] == '-') { | ||||
if ($op == 'Added') { | if ($op == 'Added') { | ||||
$this->didFailParse(pht( | $this->didFailParse(pht( | ||||
Context not available. | |||||
'-')); | '-')); | ||||
} | } | ||||
$target = 'old'; | $target = 'old'; | ||||
$line = substr($trimline, $prop_index); | $line = @substr($trimline, $prop_index); | ||||
} else if (!strncmp($trimline, 'Merged', 6)) { | } else if (!strncmp($trimline, 'Merged', 6)) { | ||||
if ($op == 'Added') { | if ($op == 'Added') { | ||||
$target = 'new'; | $target = 'new'; | ||||
Context not available. | |||||
$old = rtrim(implode('', $old)); | $old = rtrim(implode('', $old)); | ||||
$new = rtrim(implode('', $new)); | $new = rtrim(implode('', $new)); | ||||
if (!strlen($old)) { | if (!@strlen($old)) { | ||||
$old = null; | $old = null; | ||||
} | } | ||||
if (!strlen($new)) { | if (!@strlen($new)) { | ||||
$new = null; | $new = null; | ||||
} | } | ||||
Context not available. | |||||
$ok = false; | $ok = false; | ||||
$match = null; | $match = null; | ||||
foreach ($patterns as $pattern) { | foreach ($patterns as $pattern) { | ||||
$ok = preg_match('@^'.$pattern.'@', $line, $match); | $ok = @preg_match('@^'.$pattern.'@', $line, $match); | ||||
if ($ok) { | if ($ok) { | ||||
break; | break; | ||||
} | } | ||||
Context not available. | |||||
if (!$ok) { | if (!$ok) { | ||||
if ($line === null || | if ($line === null || | ||||
preg_match('/^(diff --git|commit) /', $line)) { | @preg_match('/^(diff --git|commit) /', $line)) { | ||||
// In this case, there are ONLY file mode changes, or this is a | // In this case, there are ONLY file mode changes, or this is a | ||||
// pure move. If it's a move, flag these changesets so we can build | // pure move. If it's a move, flag these changesets so we can build | ||||
// synthetic changes later, enabling us to show file contents in | // synthetic changes later, enabling us to show file contents in | ||||
Context not available. | |||||
$line = $this->getLine(); | $line = $this->getLine(); | ||||
if ($is_svn) { | if ($is_svn) { | ||||
$ok = preg_match('/^=+\s*$/', $line); | $ok = @preg_match('/^=+\s*$/', $line); | ||||
if (!$ok) { | if (!$ok) { | ||||
$this->didFailParse(pht( | $this->didFailParse(pht( | ||||
"Expected '%s' divider line.", | "Expected '%s' divider line.", | ||||
Context not available. | |||||
$line = $this->nextNonemptyLine(); | $line = $this->nextNonemptyLine(); | ||||
} | } | ||||
} else if ($is_git) { | } else if ($is_git) { | ||||
$ok = preg_match('/^index .*$/', $line); | $ok = @preg_match('/^index .*$/', $line); | ||||
if (!$ok) { | if (!$ok) { | ||||
// TODO: "hg diff -g" diffs ("mercurial git-style diffs") do not include | // TODO: "hg diff -g" diffs ("mercurial git-style diffs") do not include | ||||
// this line, so we can't parse them if we fail on it. Maybe introduce | // this line, so we can't parse them if we fail on it. Maybe introduce | ||||
Context not available. | |||||
// supplied as command-line flags to `diff', svn and git both produce | // supplied as command-line flags to `diff', svn and git both produce | ||||
// changes without any body. | // changes without any body. | ||||
if ($line === null || | if ($line === null || | ||||
preg_match( | @preg_match( | ||||
'/^(Index:|Property changes on:|diff --git|commit) /', | '/^(Index:|Property changes on:|diff --git|commit) /', | ||||
$line)) { | $line)) { | ||||
return; | return; | ||||
} | } | ||||
$is_binary_add = preg_match( | $is_binary_add = @preg_match( | ||||
'/^Cannot display: file marked as a binary type\.$/', | '/^Cannot display: file marked as a binary type\.$/', | ||||
rtrim($line)); | rtrim($line)); | ||||
if ($is_binary_add) { | if ($is_binary_add) { | ||||
Context not available. | |||||
// We can get this in git, or in SVN when a file exists in the repository | // We can get this in git, or in SVN when a file exists in the repository | ||||
// WITHOUT a binary mime-type and is changed and given a binary mime-type. | // WITHOUT a binary mime-type and is changed and given a binary mime-type. | ||||
$is_binary_diff = preg_match( | $is_binary_diff = @preg_match( | ||||
'/^(Binary files|Files) .* and .* differ$/', | '/^(Binary files|Files) .* and .* differ$/', | ||||
rtrim($line)); | rtrim($line)); | ||||
if ($is_binary_diff) { | if ($is_binary_diff) { | ||||
Context not available. | |||||
// test case "hg-binary-delete.hgdiff". (I believe it never occurs under | // test case "hg-binary-delete.hgdiff". (I believe it never occurs under | ||||
// git, which reports the "files X and /dev/null differ" string above. Git | // git, which reports the "files X and /dev/null differ" string above. Git | ||||
// can not apply these patches.) | // can not apply these patches.) | ||||
$is_hg_binary_delete = preg_match( | $is_hg_binary_delete = @preg_match( | ||||
'/^Binary file .* has changed$/', | '/^Binary file .* has changed$/', | ||||
rtrim($line)); | rtrim($line)); | ||||
if ($is_hg_binary_delete) { | if ($is_hg_binary_delete) { | ||||
Context not available. | |||||
// invoke and then, e.g., copy-paste into the web console) or "hg diff | // invoke and then, e.g., copy-paste into the web console) or "hg diff | ||||
// --git" (normal under hg workflows), we may encounter a literal binary | // --git" (normal under hg workflows), we may encounter a literal binary | ||||
// patch. | // patch. | ||||
$is_git_binary_patch = preg_match( | $is_git_binary_patch = @preg_match( | ||||
'/^GIT binary patch$/', | '/^GIT binary patch$/', | ||||
rtrim($line)); | rtrim($line)); | ||||
if ($is_git_binary_patch) { | if ($is_git_binary_patch) { | ||||
$this->nextLine(); | $this->nextLine(); | ||||
$this->parseGitBinaryPatch(); | $this->parseGitBinaryPatch(); | ||||
$line = $this->getLine(); | $line = $this->getLine(); | ||||
if (preg_match('/^literal/', $line)) { | if (@preg_match('/^literal/', $line)) { | ||||
// We may have old/new binaries (change) or just a new binary (hg add). | // We may have old/new binaries (change) or just a new binary (hg add). | ||||
// If there are two blocks, parse both. | // If there are two blocks, parse both. | ||||
$this->parseGitBinaryPatch(); | $this->parseGitBinaryPatch(); | ||||
Context not available. | |||||
if ($is_git) { | if ($is_git) { | ||||
// "git diff -b" ignores whitespace, but has an empty hunk target | // "git diff -b" ignores whitespace, but has an empty hunk target | ||||
if (preg_match('@^diff --git .*$@', $line)) { | if (@preg_match('@^diff --git .*$@', $line)) { | ||||
$this->nextLine(); | $this->nextLine(); | ||||
return null; | return null; | ||||
} | } | ||||
Context not available. | |||||
// case ("arc diff"). | // case ("arc diff"). | ||||
$line = $this->getLine(); | $line = $this->getLine(); | ||||
if (!preg_match('/^literal /', $line)) { | if (!@preg_match('/^literal /', $line)) { | ||||
$this->didFailParse( | $this->didFailParse( | ||||
pht("Expected '%s' to start git binary patch.", 'literal NNNN')); | pht("Expected '%s' to start git binary patch.", 'literal NNNN')); | ||||
} | } | ||||
Context not available. | |||||
// rely on the base85 check for sanity. | // rely on the base85 check for sanity. | ||||
$this->nextNonemptyLine(); | $this->nextNonemptyLine(); | ||||
return; | return; | ||||
} else if (!preg_match('/^[a-zA-Z]/', $line)) { | } else if (!@preg_match('/^[a-zA-Z]/', $line)) { | ||||
$this->didFailParse( | $this->didFailParse( | ||||
pht('Expected base85 line length character (a-zA-Z).')); | pht('Expected base85 line length character (a-zA-Z).')); | ||||
} | } | ||||
Context not available. | |||||
$remainder = '(?:\t.*)?'; | $remainder = '(?:\t.*)?'; | ||||
} | } | ||||
$ok = preg_match( | $ok = @preg_match( | ||||
'@^[-+]{3} (?:[ab]/)?(?P<path>.*?)'.$remainder.'$@', | '@^[-+]{3} (?:[ab]/)?(?P<path>.*?)'.$remainder.'$@', | ||||
$line, | $line, | ||||
$matches); | $matches); | ||||
Context not available. | |||||
// The final group is for git, which appends a guess at the function | // The final group is for git, which appends a guess at the function | ||||
// context to the diff. | // context to the diff. | ||||
$matches = null; | $matches = null; | ||||
$ok = preg_match( | $ok = @preg_match( | ||||
'/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(?: .*?)?$/U', | '/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(?: .*?)?$/U', | ||||
$line, | $line, | ||||
$matches); | $matches); | ||||
Context not available. | |||||
// by a "Property changes on:" section similar to svn1.6. | // by a "Property changes on:" section similar to svn1.6. | ||||
if ($line == '') { | if ($line == '') { | ||||
$line = $this->nextNonemptyLine(); | $line = $this->nextNonemptyLine(); | ||||
$ok = preg_match('/^Property changes on:/', $line); | $ok = @preg_match('/^Property changes on:/', $line); | ||||
if (!$ok) { | if (!$ok) { | ||||
$this->didFailParse(pht('Confused by empty line')); | $this->didFailParse(pht('Confused by empty line')); | ||||
} | } | ||||
Context not available. | |||||
// Cover for the cases where length wasn't present (implying one line). | // Cover for the cases where length wasn't present (implying one line). | ||||
$old_len = idx($matches, 2); | $old_len = idx($matches, 2); | ||||
if (!strlen($old_len)) { | if (!@strlen($old_len)) { | ||||
$old_len = 1; | $old_len = 1; | ||||
} | } | ||||
$new_len = idx($matches, 4); | $new_len = idx($matches, 4); | ||||
if (!strlen($new_len)) { | if (!@strlen($new_len)) { | ||||
$new_len = 1; | $new_len = 1; | ||||
} | } | ||||
Context not available. | |||||
$hit_next_hunk = false; | $hit_next_hunk = false; | ||||
while ((($line = $this->nextLine()) !== null)) { | while ((($line = $this->nextLine()) !== null)) { | ||||
if (strlen(rtrim($line, "\r\n"))) { | if (@strlen(rtrim($line, "\r\n"))) { | ||||
$char = $line[0]; | $char = $line[0]; | ||||
} else { | } else { | ||||
// Normally, we do not encouter empty lines in diffs, because | // Normally, we do not encouter empty lines in diffs, because | ||||
Context not available. | |||||
} | } | ||||
switch ($char) { | switch ($char) { | ||||
case '\\': | case '\\': | ||||
if (!preg_match('@\\ No newline at end of file@', $line)) { | if (!@preg_match('@\\ No newline at end of file@', $line)) { | ||||
$this->didFailParse( | $this->didFailParse( | ||||
pht("Expected '\ No newline at end of file'.")); | pht("Expected '\ No newline at end of file'.")); | ||||
} | } | ||||
Context not available. | |||||
$line = $this->nextNonemptyLine(); | $line = $this->nextNonemptyLine(); | ||||
} | } | ||||
} while (preg_match('/^@@ /', $line)); | } while (@preg_match('/^@@ /', $line)); | ||||
} | } | ||||
protected function buildChange($path = null) { | protected function buildChange($path = null) { | ||||
Context not available. | |||||
// and you've dug your own grave. | // and you've dug your own grave. | ||||
$ansi_color_pattern = '\x1B\[[\d;]*m'; | $ansi_color_pattern = '\x1B\[[\d;]*m'; | ||||
if (preg_match('/^'.$ansi_color_pattern.'/m', $text)) { | if (@preg_match('/^'.$ansi_color_pattern.'/m', $text)) { | ||||
$text = preg_replace('/'.$ansi_color_pattern.'/', '', $text); | $text = preg_replace('/'.$ansi_color_pattern.'/', '', $text); | ||||
} | } | ||||
Context not available. | |||||
protected function nextNonemptyLine() { | protected function nextNonemptyLine() { | ||||
while (($line = $this->nextLine()) !== null) { | while (($line = $this->nextLine()) !== null) { | ||||
if (strlen(trim($line)) !== 0) { | if (@strlen(trim($line)) !== 0) { | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
Context not available. | |||||
protected function nextLineThatLooksLikeDiffStart() { | protected function nextLineThatLooksLikeDiffStart() { | ||||
while (($line = $this->nextLine()) !== null) { | while (($line = $this->nextLine()) !== null) { | ||||
if (preg_match('/^\s*diff\s+-(?:r|-git)/', $line)) { | if (@preg_match('/^\s*diff\s+-(?:r|-git)/', $line)) { | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
Context not available. | |||||
for ($ii = 0; $ii < $len; $ii++) { | for ($ii = 0; $ii < $len; $ii++) { | ||||
$line = $this->text[$ii]; | $line = $this->text[$ii]; | ||||
if (!strlen(trim($line))) { | if (!@strlen(trim($line))) { | ||||
// This line is empty, skip it. | // This line is empty, skip it. | ||||
continue; | continue; | ||||
} | } | ||||
if (preg_match('/^#/', $line)) { | if (@preg_match('/^#/', $line)) { | ||||
// This line is a comment, skip it. | // This line is a comment, skip it. | ||||
continue; | continue; | ||||
} | } | ||||
Context not available. | |||||
* Unescape escaped filenames, e.g. from "git diff". | * Unescape escaped filenames, e.g. from "git diff". | ||||
*/ | */ | ||||
private static function unescapeFilename($name) { | private static function unescapeFilename($name) { | ||||
if (preg_match('/^".+"$/', $name)) { | if (@preg_match('/^".+"$/', $name)) { | ||||
return stripcslashes(substr($name, 1, -1)); | return stripcslashes(@substr($name, 1, -1)); | ||||
} else { | } else { | ||||
return $name; | return $name; | ||||
} | } | ||||
Context not available. | |||||
." " | ." " | ||||
."(?P<new>(?P<newq>\"?){$prefix}\\k<common>\\k<newq>)$@"; | ."(?P<new>(?P<newq>\"?){$prefix}\\k<common>\\k<newq>)$@"; | ||||
if (!preg_match($pattern, $paths, $matches)) { | if (!@preg_match($pattern, $paths, $matches)) { | ||||
// A rename or some form; return null for now, and let the | // A rename or some form; return null for now, and let the | ||||
// "rename from" / "rename to" lines fix it up. | // "rename from" / "rename to" lines fix it up. | ||||
return null; | return null; | ||||
Context not available. | |||||
// 9. Patch footer. | // 9. Patch footer. | ||||
list($head, $tail) = preg_split('/^---$/m', $diff, 2); | list($head, $tail) = preg_split('/^---$/m', $diff, 2); | ||||
list($mail_headers, $mail_body) = explode("\n\n", $head, 2); | list($mail_headers, $mail_body) = @explode("\n\n", $head, 2); | ||||
list($body, $foot) = preg_split('/^-- ?$/m', $tail, 2); | list($body, $foot) = preg_split('/^-- ?$/m', $tail, 2); | ||||
list($stat, $diff) = explode("\n\n", $body, 2); | list($stat, $diff) = @explode("\n\n", $body, 2); | ||||
// Rebuild the commit message by putting the subject line back on top of it, | // Rebuild the commit message by putting the subject line back on top of it, | ||||
// if we can find one. | // if we can find one. | ||||
$matches = null; | $matches = null; | ||||
$pattern = '/^Subject: (?:\[PATCH\] )?(.*)$/mi'; | $pattern = '/^Subject: (?:\[PATCH\] )?(.*)$/mi'; | ||||
if (preg_match($pattern, $mail_headers, $matches)) { | if (@preg_match($pattern, $mail_headers, $matches)) { | ||||
$mail_body = $matches[1]."\n\n".$mail_body; | $mail_body = $matches[1]."\n\n".$mail_body; | ||||
$mail_body = rtrim($mail_body); | $mail_body = rtrim($mail_body); | ||||
} | } | ||||
Context not available. |
Content licensed under Creative Commons Attribution-ShareAlike 4.0 (CC-BY-SA) unless otherwise noted; code licensed under Apache 2.0 or other open source licenses. · CC BY-SA 4.0 · Apache 2.0