diff --git a/patch.py b/patch.py index 4a67cadfc..8fe8b5c20 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 @@ class FilePatchBase(object): 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 @@ class FilePatchBinary(FilePatchBase): 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 @@ class FilePatchDiff(FilePatchBase): 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 @@ class FilePatchDiff(FilePatchBase): # 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:]) diff --git a/testing_support/patches_data.py b/testing_support/patches_data.py index b70f55ba3..80d36d24e 100644 --- a/testing_support/patches_data.py +++ b/testing_support/patches_data.py @@ -1,4 +1,4 @@ -# 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. @@ -54,12 +54,63 @@ class RAW(object): '--- chrome/file.cc\tbar\n' '+++ /dev/null\tfoo\n') + DELETE2 = ( + 'Index: browser/extensions/extension_sidebar_api.cc\n' + '===================================================================\n' + '--- browser/extensions/extension_sidebar_api.cc\t(revision 116830)\n' + '+++ browser/extensions/extension_sidebar_api.cc\t(working copy)\n' + '@@ -1,19 +0,0 @@\n' + '-// Copyright (c) 2011 The Chromium Authors. All rights reserved.\n' + '-// Use of this source code is governed by a BSD-style license that\n' + '-// found in the LICENSE file.\n' + '-\n' + '-#include "base/command_line.h"\n' + '-#include "chrome/browser/extensions/extension_apitest.h"\n' + '-#include "chrome/common/chrome_switches.h"\n' + '-\n' + '-class SidebarApiTest : public ExtensionApiTest {\n' + '- public:\n' + '- void SetUpCommandLine(CommandLine* command_line) {\n' + '- ExtensionApiTest::SetUpCommandLine(command_line);\n' + '- command_line->AppendSwitch(switches::Bleh);\n' + '- }\n' + '-};\n' + '-\n' + '-IN_PROC_BROWSER_TEST_F(SidebarApiTest, Sidebar) {\n' + '- ASSERT_TRUE(RunExtensionTest("sidebar")) << message_;\n' + '-}\n') + # http://codereview.chromium.org/api/7530007/5001 # http://codereview.chromium.org/download/issue7530007_5001_4011.diff CRAP_ONLY = ( 'Index: scripts/master/factory/skia/__init__.py\n' '===================================================================\n') + TWO_HUNKS = ( + 'Index: chrome/app/generated_resources.grd\n' + '===================================================================\n' + '--- chrome/app/generated_resources.grd\t(revision 116830)\n' + '+++ chrome/app/generated_resources.grd\t(working copy)\n' + '@@ -4169,9 +4169,6 @@\n' + ' \n' + ' Could not load options page \'$1\n' + '- \n' + '- Could not load sidebar page \'$1\n' + ' \n' + '