Fix svn delete handling (again).
Fix RAW.NEW_NOT_NULL parsing to detect correctly it's a new file.
Add proper Hunk parsing to fix these 2 bugs.
R=dpranke@chromium.org
BUG=109715
TEST=CQ'ing a patch generated with svn that delete files is applied properly, e.g. the file is deleted and not simple 0-length.
Review URL: http://codereview.chromium.org/9167015
git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@117084 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/patch.py b/patch.py
index 4a67cad..8fe8b5c 100644
--- a/patch.py
+++ b/patch.py
@@ -1,5 +1,5 @@
# coding=utf8
-# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utility functions to handle patches."""
@@ -86,7 +86,10 @@
out += 'R'
else:
out += ' '
- return out + ' %s->%s' % (self.source_filename, self.filename)
+ out += ' '
+ if self.source_filename:
+ out += '%s->' % self.source_filename
+ return out + str(self.filename)
class FilePatchDelete(FilePatchBase):
@@ -112,6 +115,18 @@
return self.data
+class Hunk(object):
+ """Parsed hunk data container."""
+
+ def __init__(self, start_src, lines_src, start_dst, lines_dst):
+ self.start_src = start_src
+ self.lines_src = lines_src
+ self.start_dst = start_dst
+ self.lines_dst = lines_dst
+ self.variation = self.lines_dst - self.lines_src
+ self.text = []
+
+
class FilePatchDiff(FilePatchBase):
"""Patch for a single file."""
@@ -127,6 +142,7 @@
self._verify_git_header()
else:
self._verify_svn_header()
+ self.hunks = self._split_hunks()
if self.source_filename and not self.is_new:
self._fail('If source_filename is set, is_new must be also be set')
@@ -198,6 +214,65 @@
# http://codereview.chromium.org/download/issue6287022_3001_4010.diff
return any(l.startswith('diff --git') for l in diff_header.splitlines())
+ def _split_hunks(self):
+ """Splits the hunks and does verification."""
+ hunks = []
+ for line in self.diff_hunks.splitlines(True):
+ if line.startswith('@@'):
+ match = re.match(r'^@@ -(\d+),(\d+) \+([\d,]+) @@.*$', line)
+ # File add will result in "-0,0 +1" but file deletion will result in
+ # "-1,N +0,0" where N is the number of lines deleted. That's from diff
+ # and svn diff. git diff doesn't exhibit this behavior.
+ if not match:
+ self._fail('Hunk header is unparsable')
+ if ',' in match.group(3):
+ start_dst, lines_dst = map(int, match.group(3).split(',', 1))
+ else:
+ start_dst = int(match.group(3))
+ lines_dst = 0
+ new_hunk = Hunk(int(match.group(1)), int(match.group(2)),
+ start_dst, lines_dst)
+ if hunks:
+ if new_hunk.start_src <= hunks[-1].start_src:
+ self._fail('Hunks source lines are not ordered')
+ if new_hunk.start_dst <= hunks[-1].start_dst:
+ self._fail('Hunks destination lines are not ordered')
+ hunks.append(new_hunk)
+ continue
+ hunks[-1].text.append(line)
+
+ if len(hunks) == 1:
+ if hunks[0].start_src == 0 and hunks[0].lines_src == 0:
+ self.is_new = True
+ if hunks[0].start_dst == 0 and hunks[0].lines_dst == 0:
+ self.is_delete = True
+
+ if self.is_new and self.is_delete:
+ self._fail('Hunk header is all 0')
+
+ if not self.is_new and not self.is_delete:
+ for hunk in hunks:
+ variation = (
+ len([1 for i in hunk.text if i.startswith('+')]) -
+ len([1 for i in hunk.text if i.startswith('-')]))
+ if variation != hunk.variation:
+ self._fail(
+ 'Hunk header is incorrect: %d vs %d' % (
+ variation, hunk.variation))
+ if not hunk.start_src:
+ self._fail(
+ 'Hunk header start line is incorrect: %d' % hunk.start_src)
+ if not hunk.start_dst:
+ self._fail(
+ 'Hunk header start line is incorrect: %d' % hunk.start_dst)
+ hunk.start_src -= 1
+ hunk.start_dst -= 1
+ if self.is_new and hunks:
+ hunks[0].start_dst -= 1
+ if self.is_delete and hunks:
+ hunks[0].start_src -= 1
+ return hunks
+
def mangle(self, string):
"""Mangle a file path."""
return '/'.join(string.replace('\\', '/').split('/')[self.patchlevel:])