Add support forcopied and renamed files.

R=dpranke@chromium.org
BUG=
TEST=


Review URL: http://codereview.chromium.org/6874006

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@100138 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/patch.py b/patch.py
index 333cbb9..888e100 100644
--- a/patch.py
+++ b/patch.py
@@ -33,6 +33,8 @@
 
   def __init__(self, filename):
     self.filename = self._process_filename(filename)
+    # Set when the file is copied or moved.
+    self.source_filename = None
 
   @staticmethod
   def _process_filename(filename):
@@ -59,6 +61,9 @@
       self._fail('Relative path starts with %s' % relpath[0])
     self.filename = self._process_filename(
         posixpath.join(relpath, self.filename))
+    if self.source_filename:
+      self.source_filename = self._process_filename(
+          posixpath.join(relpath, self.source_filename))
 
   def _fail(self, msg):
     """Shortcut function to raise UnsupportedPatchFormat."""
@@ -112,9 +117,21 @@
 
   def set_relpath(self, relpath):
     old_filename = self.filename
+    old_source_filename = self.source_filename or self.filename
     super(FilePatchDiff, self).set_relpath(relpath)
     # Update the header too.
-    self.diff_header = self.diff_header.replace(old_filename, self.filename)
+    source_filename = self.source_filename or self.filename
+    lines = self.diff_header.splitlines(True)
+    for i, line in enumerate(lines):
+      if line.startswith('diff --git'):
+        lines[i] = line.replace(
+            'a/' + old_source_filename, source_filename).replace(
+                'b/' + old_filename, self.filename)
+      elif re.match(r'^\w+ from .+$', line) or line.startswith('---'):
+        lines[i] = line.replace(old_source_filename, source_filename)
+      elif re.match(r'^\w+ to .+$', line) or line.startswith('+++'):
+        lines[i] = line.replace(old_filename, self.filename)
+    self.diff_header = ''.join(lines)
 
   def _split_header(self, diff):
     """Splits a diff in two: the header and the hunks."""
@@ -186,12 +203,11 @@
       match = re.match(r'^diff \-\-git (.*?) (.*)$', lines.pop(0))
       if not match:
         continue
-      old = match.group(1).replace('\\', '/')
-      new = match.group(2).replace('\\', '/')
-      if old.startswith('a/') and new.startswith('b/'):
+      if match.group(1).startswith('a/') and match.group(2).startswith('b/'):
         self.patchlevel = 1
-        old = old[2:]
-        new = new[2:]
+      old = self.mangle(match.group(1))
+      new = self.mangle(match.group(2))
+
       # The rename is about the new file so the old file can be anything.
       if new not in (self.filename, 'dev/null'):
         self._fail('Unexpected git diff output name %s.' % new)
@@ -202,13 +218,15 @@
     if not old or not new:
       self._fail('Unexpected git diff; couldn\'t find git header.')
 
+    if old not in (self.filename, 'dev/null'):
+      # Copy or rename.
+      self.source_filename = old
+
     last_line = ''
 
     while lines:
       line = lines.pop(0)
-      # TODO(maruel): old should be replace with self.source_file
-      # TODO(maruel): new == self.filename and remove new
-      self._verify_git_header_process_line(lines, line, last_line, old, new)
+      self._verify_git_header_process_line(lines, line, last_line)
       last_line = line
 
     # Cheap check to make sure the file name is at least mentioned in the
@@ -216,7 +234,7 @@
     if not self.filename in self.diff_header:
       self._fail('Diff seems corrupted.')
 
-  def _verify_git_header_process_line(self, lines, line, last_line, old, new):
+  def _verify_git_header_process_line(self, lines, line, last_line):
     """Processes a single line of the header.
 
     Returns True if it should continue looping.
@@ -225,6 +243,7 @@
     http://www.kernel.org/pub/software/scm/git/docs/git-diff.html
     """
     match = re.match(r'^(rename|copy) from (.+)$', line)
+    old = self.source_filename or self.filename
     if match:
       if old != match.group(2):
         self._fail('Unexpected git diff input name for line %s.' % line)
@@ -236,7 +255,7 @@
 
     match = re.match(r'^(rename|copy) to (.+)$', line)
     if match:
-      if new != match.group(2):
+      if self.filename != match.group(2):
         self._fail('Unexpected git diff output name for line %s.' % line)
       if not last_line.startswith('%s from ' % match.group(1)):
         self._fail(
@@ -258,7 +277,7 @@
         self._fail('--- and +++ are reversed')
       self.is_new = match.group(1) == '/dev/null'
       # TODO(maruel): Use self.source_file.
-      if old != self.mangle(match.group(1)) and match.group(1) != '/dev/null':
+      if self.mangle(match.group(1)) not in (old, 'dev/null'):
         self._fail('Unexpected git diff: %s != %s.' % (old, match.group(1)))
       if not lines or not lines[0].startswith('+++'):
         self._fail('Missing git diff output name.')
@@ -271,8 +290,9 @@
       # TODO(maruel): new == self.filename.
       if '/dev/null' == match.group(1):
         self.is_delete = True
-      elif new != self.mangle(match.group(1)):
-        self._fail('Unexpected git diff: %s != %s.' % (new, match.group(1)))
+      elif self.filename != self.mangle(match.group(1)):
+        self._fail(
+            'Unexpected git diff: %s != %s.' % (self.filename, match.group(1)))
       if lines:
         self._fail('Crap after +++')
       # We're done.
@@ -308,13 +328,9 @@
       if last_line[:3] in ('---', '+++'):
         self._fail('--- and +++ are reversed')
       self.is_new = match.group(1) == '/dev/null'
-      # For copy and renames, it's possible that the -- line doesn't match
-      # +++, so don't check match.group(1) to match self.filename or
-      # '/dev/null', it can be anything else.
-      # TODO(maruel): Handle rename/copy explicitly.
-      # if (self.mangle(match.group(1)) != self.filename and
-      #     match.group(1) != '/dev/null'):
-      #  self.source_file = match.group(1)
+      if (self.mangle(match.group(1)) != self.filename and
+           match.group(1) != '/dev/null'):
+        self.source_filename = match.group(1)
       if not lines or not lines[0].startswith('+++'):
         self._fail('Nothing after header.')
       return