Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(868)

Side by Side Diff: patch.py

Issue 6874006: Add support for A+ files. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: . Created 9 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | rietveld.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # coding=utf8 1 # coding=utf8
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 """Utility functions to handle patches.""" 5 """Utility functions to handle patches."""
6 6
7 import posixpath 7 import posixpath
8 import os 8 import os
9 import re 9 import re
10 10
(...skipping 15 matching lines...) Expand all
26 """Defines a single file being modified. 26 """Defines a single file being modified.
27 27
28 '/' is always used instead of os.sep for consistency. 28 '/' is always used instead of os.sep for consistency.
29 """ 29 """
30 is_delete = False 30 is_delete = False
31 is_binary = False 31 is_binary = False
32 is_new = False 32 is_new = False
33 33
34 def __init__(self, filename): 34 def __init__(self, filename):
35 self.filename = self._process_filename(filename) 35 self.filename = self._process_filename(filename)
36 # Set when the file is copied or moved.
37 self.source_filename = None
36 38
37 @staticmethod 39 @staticmethod
38 def _process_filename(filename): 40 def _process_filename(filename):
39 filename = filename.replace('\\', '/') 41 filename = filename.replace('\\', '/')
40 # Blacklist a few characters for simplicity. 42 # Blacklist a few characters for simplicity.
41 for i in ('%', '$', '..', '\'', '"'): 43 for i in ('%', '$', '..', '\'', '"'):
42 if i in filename: 44 if i in filename:
43 raise UnsupportedPatchFormat( 45 raise UnsupportedPatchFormat(
44 filename, 'Can\'t use \'%s\' in filename.' % i) 46 filename, 'Can\'t use \'%s\' in filename.' % i)
45 for i in ('/', 'CON', 'COM'): 47 for i in ('/', 'CON', 'COM'):
46 if filename.startswith(i): 48 if filename.startswith(i):
47 raise UnsupportedPatchFormat( 49 raise UnsupportedPatchFormat(
48 filename, 'Filename can\'t start with \'%s\'.' % i) 50 filename, 'Filename can\'t start with \'%s\'.' % i)
49 return filename 51 return filename
50 52
51 def get(self): # pragma: no coverage 53 def get(self): # pragma: no coverage
52 raise NotImplementedError('Nothing to grab') 54 raise NotImplementedError('Nothing to grab')
53 55
54 def set_relpath(self, relpath): 56 def set_relpath(self, relpath):
55 if not relpath: 57 if not relpath:
56 return 58 return
57 relpath = relpath.replace('\\', '/') 59 relpath = relpath.replace('\\', '/')
58 if relpath[0] == '/': 60 if relpath[0] == '/':
59 self._fail('Relative path starts with %s' % relpath[0]) 61 self._fail('Relative path starts with %s' % relpath[0])
60 self.filename = self._process_filename( 62 self.filename = self._process_filename(
61 posixpath.join(relpath, self.filename)) 63 posixpath.join(relpath, self.filename))
64 if self.source_filename:
65 self.source_filename = self._process_filename(
66 posixpath.join(relpath, self.source_filename))
62 67
63 def _fail(self, msg): 68 def _fail(self, msg):
64 """Shortcut function to raise UnsupportedPatchFormat.""" 69 """Shortcut function to raise UnsupportedPatchFormat."""
65 raise UnsupportedPatchFormat(self.filename, msg) 70 raise UnsupportedPatchFormat(self.filename, msg)
66 71
67 72
68 class FilePatchDelete(FilePatchBase): 73 class FilePatchDelete(FilePatchBase):
69 """Deletes a file.""" 74 """Deletes a file."""
70 is_delete = True 75 is_delete = True
71 76
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
105 if self.is_git_diff: 110 if self.is_git_diff:
106 self._verify_git_header() 111 self._verify_git_header()
107 else: 112 else:
108 self._verify_svn_header() 113 self._verify_svn_header()
109 114
110 def get(self): 115 def get(self):
111 return self.diff_header + self.diff_hunks 116 return self.diff_header + self.diff_hunks
112 117
113 def set_relpath(self, relpath): 118 def set_relpath(self, relpath):
114 old_filename = self.filename 119 old_filename = self.filename
120 old_source_filename = self.source_filename or self.filename
115 super(FilePatchDiff, self).set_relpath(relpath) 121 super(FilePatchDiff, self).set_relpath(relpath)
116 # Update the header too. 122 # Update the header too.
117 self.diff_header = self.diff_header.replace(old_filename, self.filename) 123 source_filename = self.source_filename or self.filename
124 lines = self.diff_header.splitlines(True)
125 for i, line in enumerate(lines):
126 if line.startswith('diff --git'):
127 lines[i] = line.replace(
128 'a/' + old_source_filename, source_filename).replace(
129 'b/' + old_filename, self.filename)
130 elif re.match(r'^\w+ from .+$', line) or line.startswith('---'):
131 lines[i] = line.replace(old_source_filename, source_filename)
132 elif re.match(r'^\w+ to .+$', line) or line.startswith('+++'):
133 lines[i] = line.replace(old_filename, self.filename)
134 self.diff_header = ''.join(lines)
118 135
119 def _split_header(self, diff): 136 def _split_header(self, diff):
120 """Splits a diff in two: the header and the hunks.""" 137 """Splits a diff in two: the header and the hunks."""
121 header = [] 138 header = []
122 hunks = diff.splitlines(True) 139 hunks = diff.splitlines(True)
123 while hunks: 140 while hunks:
124 header.append(hunks.pop(0)) 141 header.append(hunks.pop(0))
125 if header[-1].startswith('--- '): 142 if header[-1].startswith('--- '):
126 break 143 break
127 else: 144 else:
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
179 """ 196 """
180 lines = self.diff_header.splitlines() 197 lines = self.diff_header.splitlines()
181 198
182 # Verify the diff --git line. 199 # Verify the diff --git line.
183 old = None 200 old = None
184 new = None 201 new = None
185 while lines: 202 while lines:
186 match = re.match(r'^diff \-\-git (.*?) (.*)$', lines.pop(0)) 203 match = re.match(r'^diff \-\-git (.*?) (.*)$', lines.pop(0))
187 if not match: 204 if not match:
188 continue 205 continue
189 old = match.group(1).replace('\\', '/') 206 if match.group(1).startswith('a/') and match.group(2).startswith('b/'):
190 new = match.group(2).replace('\\', '/')
191 if old.startswith('a/') and new.startswith('b/'):
192 self.patchlevel = 1 207 self.patchlevel = 1
193 old = old[2:] 208 old = self.mangle(match.group(1))
194 new = new[2:] 209 new = self.mangle(match.group(2))
210
195 # The rename is about the new file so the old file can be anything. 211 # The rename is about the new file so the old file can be anything.
196 if new not in (self.filename, 'dev/null'): 212 if new not in (self.filename, 'dev/null'):
197 self._fail('Unexpected git diff output name %s.' % new) 213 self._fail('Unexpected git diff output name %s.' % new)
198 if old == 'dev/null' and new == 'dev/null': 214 if old == 'dev/null' and new == 'dev/null':
199 self._fail('Unexpected /dev/null git diff.') 215 self._fail('Unexpected /dev/null git diff.')
200 break 216 break
201 217
202 if not old or not new: 218 if not old or not new:
203 self._fail('Unexpected git diff; couldn\'t find git header.') 219 self._fail('Unexpected git diff; couldn\'t find git header.')
204 220
221 if old not in (self.filename, 'dev/null'):
222 # Copy or rename.
223 self.source_filename = old
224
205 last_line = '' 225 last_line = ''
206 226
207 while lines: 227 while lines:
208 line = lines.pop(0) 228 line = lines.pop(0)
209 # TODO(maruel): old should be replace with self.source_file 229 self._verify_git_header_process_line(lines, line, last_line)
210 # TODO(maruel): new == self.filename and remove new
211 self._verify_git_header_process_line(lines, line, last_line, old, new)
212 last_line = line 230 last_line = line
213 231
214 # Cheap check to make sure the file name is at least mentioned in the 232 # Cheap check to make sure the file name is at least mentioned in the
215 # 'diff' header. That the only remaining invariant. 233 # 'diff' header. That the only remaining invariant.
216 if not self.filename in self.diff_header: 234 if not self.filename in self.diff_header:
217 self._fail('Diff seems corrupted.') 235 self._fail('Diff seems corrupted.')
218 236
219 def _verify_git_header_process_line(self, lines, line, last_line, old, new): 237 def _verify_git_header_process_line(self, lines, line, last_line):
220 """Processes a single line of the header. 238 """Processes a single line of the header.
221 239
222 Returns True if it should continue looping. 240 Returns True if it should continue looping.
223 241
224 Format is described to 242 Format is described to
225 http://www.kernel.org/pub/software/scm/git/docs/git-diff.html 243 http://www.kernel.org/pub/software/scm/git/docs/git-diff.html
226 """ 244 """
227 match = re.match(r'^(rename|copy) from (.+)$', line) 245 match = re.match(r'^(rename|copy) from (.+)$', line)
246 old = self.source_filename or self.filename
228 if match: 247 if match:
229 if old != match.group(2): 248 if old != match.group(2):
230 self._fail('Unexpected git diff input name for line %s.' % line) 249 self._fail('Unexpected git diff input name for line %s.' % line)
231 if not lines or not lines[0].startswith('%s to ' % match.group(1)): 250 if not lines or not lines[0].startswith('%s to ' % match.group(1)):
232 self._fail( 251 self._fail(
233 'Confused %s from/to git diff for line %s.' % 252 'Confused %s from/to git diff for line %s.' %
234 (match.group(1), line)) 253 (match.group(1), line))
235 return 254 return
236 255
237 match = re.match(r'^(rename|copy) to (.+)$', line) 256 match = re.match(r'^(rename|copy) to (.+)$', line)
238 if match: 257 if match:
239 if new != match.group(2): 258 if self.filename != match.group(2):
240 self._fail('Unexpected git diff output name for line %s.' % line) 259 self._fail('Unexpected git diff output name for line %s.' % line)
241 if not last_line.startswith('%s from ' % match.group(1)): 260 if not last_line.startswith('%s from ' % match.group(1)):
242 self._fail( 261 self._fail(
243 'Confused %s from/to git diff for line %s.' % 262 'Confused %s from/to git diff for line %s.' %
244 (match.group(1), line)) 263 (match.group(1), line))
245 return 264 return
246 265
247 match = re.match(r'^new(| file) mode (\d{6})$', line) 266 match = re.match(r'^new(| file) mode (\d{6})$', line)
248 if match: 267 if match:
249 mode = match.group(2) 268 mode = match.group(2)
250 # Only look at owner ACL for executable. 269 # Only look at owner ACL for executable.
251 # TODO(maruel): Add support to remove a property. 270 # TODO(maruel): Add support to remove a property.
252 if bool(int(mode[4]) & 1): 271 if bool(int(mode[4]) & 1):
253 self.svn_properties.append(('svn:executable', '*')) 272 self.svn_properties.append(('svn:executable', '*'))
254 273
255 match = re.match(r'^--- (.*)$', line) 274 match = re.match(r'^--- (.*)$', line)
256 if match: 275 if match:
257 if last_line[:3] in ('---', '+++'): 276 if last_line[:3] in ('---', '+++'):
258 self._fail('--- and +++ are reversed') 277 self._fail('--- and +++ are reversed')
259 self.is_new = match.group(1) == '/dev/null' 278 self.is_new = match.group(1) == '/dev/null'
260 # TODO(maruel): Use self.source_file. 279 # TODO(maruel): Use self.source_file.
261 if old != self.mangle(match.group(1)) and match.group(1) != '/dev/null': 280 if self.mangle(match.group(1)) not in (old, 'dev/null'):
262 self._fail('Unexpected git diff: %s != %s.' % (old, match.group(1))) 281 self._fail('Unexpected git diff: %s != %s.' % (old, match.group(1)))
263 if not lines or not lines[0].startswith('+++'): 282 if not lines or not lines[0].startswith('+++'):
264 self._fail('Missing git diff output name.') 283 self._fail('Missing git diff output name.')
265 return 284 return
266 285
267 match = re.match(r'^\+\+\+ (.*)$', line) 286 match = re.match(r'^\+\+\+ (.*)$', line)
268 if match: 287 if match:
269 if not last_line.startswith('---'): 288 if not last_line.startswith('---'):
270 self._fail('Unexpected git diff: --- not following +++.') 289 self._fail('Unexpected git diff: --- not following +++.')
271 # TODO(maruel): new == self.filename. 290 # TODO(maruel): new == self.filename.
272 if '/dev/null' == match.group(1): 291 if '/dev/null' == match.group(1):
273 self.is_delete = True 292 self.is_delete = True
274 elif new != self.mangle(match.group(1)): 293 elif self.filename != self.mangle(match.group(1)):
275 self._fail('Unexpected git diff: %s != %s.' % (new, match.group(1))) 294 self._fail(
295 'Unexpected git diff: %s != %s.' % (self.filename, match.group(1)))
276 if lines: 296 if lines:
277 self._fail('Crap after +++') 297 self._fail('Crap after +++')
278 # We're done. 298 # We're done.
279 return 299 return
280 300
281 def _verify_svn_header(self): 301 def _verify_svn_header(self):
282 """Sanity checks the header. 302 """Sanity checks the header.
283 303
284 A svn diff can contain only property changes, in that case there will be no 304 A svn diff can contain only property changes, in that case there will be no
285 proper header. To make things worse, this property change header is 305 proper header. To make things worse, this property change header is
(...skipping 15 matching lines...) Expand all
301 def _verify_svn_header_process_line(self, lines, line, last_line): 321 def _verify_svn_header_process_line(self, lines, line, last_line):
302 """Processes a single line of the header. 322 """Processes a single line of the header.
303 323
304 Returns True if it should continue looping. 324 Returns True if it should continue looping.
305 """ 325 """
306 match = re.match(r'^--- ([^\t]+).*$', line) 326 match = re.match(r'^--- ([^\t]+).*$', line)
307 if match: 327 if match:
308 if last_line[:3] in ('---', '+++'): 328 if last_line[:3] in ('---', '+++'):
309 self._fail('--- and +++ are reversed') 329 self._fail('--- and +++ are reversed')
310 self.is_new = match.group(1) == '/dev/null' 330 self.is_new = match.group(1) == '/dev/null'
311 # For copy and renames, it's possible that the -- line doesn't match 331 if (self.mangle(match.group(1)) != self.filename and
312 # +++, so don't check match.group(1) to match self.filename or 332 match.group(1) != '/dev/null'):
313 # '/dev/null', it can be anything else. 333 self.source_filename = match.group(1)
314 # TODO(maruel): Handle rename/copy explicitly.
315 # if (self.mangle(match.group(1)) != self.filename and
316 # match.group(1) != '/dev/null'):
317 # self.source_file = match.group(1)
318 if not lines or not lines[0].startswith('+++'): 334 if not lines or not lines[0].startswith('+++'):
319 self._fail('Nothing after header.') 335 self._fail('Nothing after header.')
320 return 336 return
321 337
322 match = re.match(r'^\+\+\+ ([^\t]+).*$', line) 338 match = re.match(r'^\+\+\+ ([^\t]+).*$', line)
323 if match: 339 if match:
324 if not last_line.startswith('---'): 340 if not last_line.startswith('---'):
325 self._fail('Unexpected diff: --- not following +++.') 341 self._fail('Unexpected diff: --- not following +++.')
326 if match.group(1) == '/dev/null': 342 if match.group(1) == '/dev/null':
327 self.is_delete = True 343 self.is_delete = True
(...skipping 18 matching lines...) Expand all
346 for patch in self.patches: 362 for patch in self.patches:
347 patch.set_relpath(relpath) 363 patch.set_relpath(relpath)
348 364
349 def __iter__(self): 365 def __iter__(self):
350 for patch in self.patches: 366 for patch in self.patches:
351 yield patch 367 yield patch
352 368
353 @property 369 @property
354 def filenames(self): 370 def filenames(self):
355 return [p.filename for p in self.patches] 371 return [p.filename for p in self.patches]
OLDNEW
« no previous file with comments | « no previous file | rietveld.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698