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

Side by Side Diff: checkout.py

Issue 8068008: Add post_processors override to apply_patch() and add more testing. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 9 years, 2 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 | tests/checkout_test.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 """Manages a project checkout. 5 """Manages a project checkout.
6 6
7 Includes support for svn, git-svn and git. 7 Includes support for svn, git-svn and git.
8 """ 8 """
9 9
10 from __future__ import with_statement 10 from __future__ import with_statement
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
85 """Checks out a clean copy of the tree and removes any local modification. 85 """Checks out a clean copy of the tree and removes any local modification.
86 86
87 This function shouldn't throw unless the remote repository is inaccessible, 87 This function shouldn't throw unless the remote repository is inaccessible,
88 there is no free disk space or hard issues like that. 88 there is no free disk space or hard issues like that.
89 89
90 Args: 90 Args:
91 revision: The revision it should sync to, SCM specific. 91 revision: The revision it should sync to, SCM specific.
92 """ 92 """
93 raise NotImplementedError() 93 raise NotImplementedError()
94 94
95 def apply_patch(self, patches): 95 def apply_patch(self, patches, post_processors=None):
96 """Applies a patch and returns the list of modified files. 96 """Applies a patch and returns the list of modified files.
97 97
98 This function should throw patch.UnsupportedPatchFormat or 98 This function should throw patch.UnsupportedPatchFormat or
99 PatchApplicationFailed when relevant. 99 PatchApplicationFailed when relevant.
100 100
101 Args: 101 Args:
102 patches: patch.PatchSet object. 102 patches: patch.PatchSet object.
103 """ 103 """
104 raise NotImplementedError() 104 raise NotImplementedError()
105 105
106 def commit(self, commit_message, user): 106 def commit(self, commit_message, user):
107 """Commits the patch upstream, while impersonating 'user'.""" 107 """Commits the patch upstream, while impersonating 'user'."""
108 raise NotImplementedError() 108 raise NotImplementedError()
109 109
110 110
111 class RawCheckout(CheckoutBase): 111 class RawCheckout(CheckoutBase):
112 """Used to apply a patch locally without any intent to commit it. 112 """Used to apply a patch locally without any intent to commit it.
113 113
114 To be used by the try server. 114 To be used by the try server.
115 """ 115 """
116 def prepare(self, revision): 116 def prepare(self, revision):
117 """Stubbed out.""" 117 """Stubbed out."""
118 pass 118 pass
119 119
120 def apply_patch(self, patches): 120 def apply_patch(self, patches, post_processors=None):
121 """Ignores svn properties.""" 121 """Ignores svn properties."""
122 post_processors = post_processors or self.post_processors or []
122 for p in patches: 123 for p in patches:
123 try: 124 try:
124 stdout = '' 125 stdout = ''
125 filename = os.path.join(self.project_path, p.filename) 126 filename = os.path.join(self.project_path, p.filename)
126 if p.is_delete: 127 if p.is_delete:
127 os.remove(filename) 128 os.remove(filename)
128 else: 129 else:
129 dirname = os.path.dirname(p.filename) 130 dirname = os.path.dirname(p.filename)
130 full_dir = os.path.join(self.project_path, dirname) 131 full_dir = os.path.join(self.project_path, dirname)
131 if dirname and not os.path.isdir(full_dir): 132 if dirname and not os.path.isdir(full_dir):
132 os.makedirs(full_dir) 133 os.makedirs(full_dir)
133 134
134 filepath = os.path.join(self.project_path, p.filename) 135 filepath = os.path.join(self.project_path, p.filename)
135 if p.is_binary: 136 if p.is_binary:
136 with open(filepath, 'wb') as f: 137 with open(filepath, 'wb') as f:
137 f.write(p.get()) 138 f.write(p.get())
138 else: 139 else:
139 if p.diff_hunks: 140 if p.diff_hunks:
140 stdout = subprocess2.check_output( 141 stdout = subprocess2.check_output(
141 ['patch', '-p%s' % p.patchlevel], 142 ['patch', '-p%s' % p.patchlevel],
142 stdin=p.get(), 143 stdin=p.get(),
143 stderr=subprocess2.STDOUT, 144 stderr=subprocess2.STDOUT,
144 cwd=self.project_path) 145 cwd=self.project_path)
145 elif p.is_new and not os.path.exists(filepath): 146 elif p.is_new and not os.path.exists(filepath):
146 # There is only a header. Just create the file. 147 # There is only a header. Just create the file.
147 open(filepath, 'w').close() 148 open(filepath, 'w').close()
148 for post in (self.post_processors or []): 149 for post in post_processors:
149 post(self, p) 150 post(self, p)
150 except OSError, e: 151 except OSError, e:
151 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) 152 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e))
152 except subprocess.CalledProcessError, e: 153 except subprocess.CalledProcessError, e:
153 raise PatchApplicationFailed( 154 raise PatchApplicationFailed(
154 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None))) 155 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None)))
155 156
156 def commit(self, commit_message, user): 157 def commit(self, commit_message, user):
157 """Stubbed out.""" 158 """Stubbed out."""
158 raise NotImplementedError('RawCheckout can\'t commit') 159 raise NotImplementedError('RawCheckout can\'t commit')
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
254 assert bool(self.commit_user) >= bool(self.commit_pwd) 255 assert bool(self.commit_user) >= bool(self.commit_pwd)
255 256
256 def prepare(self, revision): 257 def prepare(self, revision):
257 # Will checkout if the directory is not present. 258 # Will checkout if the directory is not present.
258 assert self.svn_url 259 assert self.svn_url
259 if not os.path.isdir(self.project_path): 260 if not os.path.isdir(self.project_path):
260 logging.info('Checking out %s in %s' % 261 logging.info('Checking out %s in %s' %
261 (self.project_name, self.project_path)) 262 (self.project_name, self.project_path))
262 return self._revert(revision) 263 return self._revert(revision)
263 264
264 def apply_patch(self, patches): 265 def apply_patch(self, patches, post_processors=None):
266 post_processors = post_processors or self.post_processors or []
265 for p in patches: 267 for p in patches:
266 try: 268 try:
267 # It is important to use credentials=False otherwise credentials could 269 # It is important to use credentials=False otherwise credentials could
268 # leak in the error message. Credentials are not necessary here for the 270 # leak in the error message. Credentials are not necessary here for the
269 # following commands anyway. 271 # following commands anyway.
270 stdout = '' 272 stdout = ''
271 if p.is_delete: 273 if p.is_delete:
272 stdout += self._check_output_svn( 274 stdout += self._check_output_svn(
273 ['delete', p.filename, '--force'], credentials=False) 275 ['delete', p.filename, '--force'], credentials=False)
274 else: 276 else:
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
307 ['propset', prop[0], prop[1], p.filename], credentials=False) 309 ['propset', prop[0], prop[1], p.filename], credentials=False)
308 for prop, values in self.svn_config.auto_props.iteritems(): 310 for prop, values in self.svn_config.auto_props.iteritems():
309 if fnmatch.fnmatch(p.filename, prop): 311 if fnmatch.fnmatch(p.filename, prop):
310 for value in values.split(';'): 312 for value in values.split(';'):
311 if '=' not in value: 313 if '=' not in value:
312 params = [value, '*'] 314 params = [value, '*']
313 else: 315 else:
314 params = value.split('=', 1) 316 params = value.split('=', 1)
315 stdout += self._check_output_svn( 317 stdout += self._check_output_svn(
316 ['propset'] + params + [p.filename], credentials=False) 318 ['propset'] + params + [p.filename], credentials=False)
317 for post in (self.post_processors or []): 319 for post in post_processors:
318 post(self, p) 320 post(self, p)
319 except OSError, e: 321 except OSError, e:
320 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) 322 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e))
321 except subprocess.CalledProcessError, e: 323 except subprocess.CalledProcessError, e:
322 raise PatchApplicationFailed( 324 raise PatchApplicationFailed(
323 p.filename, 325 p.filename,
324 'While running %s;\n%s%s' % ( 326 'While running %s;\n%s%s' % (
325 ' '.join(e.cmd), stdout, getattr(e, 'stdout', ''))) 327 ' '.join(e.cmd), stdout, getattr(e, 'stdout', '')))
326 328
327 def commit(self, commit_message, user): 329 def commit(self, commit_message, user):
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
411 revision = self._check_output_git(['rev-parse', revision]) 413 revision = self._check_output_git(['rev-parse', revision])
412 self._check_call_git(['checkout', '--force', '--quiet', revision]) 414 self._check_call_git(['checkout', '--force', '--quiet', revision])
413 else: 415 else:
414 branches, active = self._branches() 416 branches, active = self._branches()
415 if active != 'master': 417 if active != 'master':
416 self._check_call_git(['checkout', '--force', '--quiet', 'master']) 418 self._check_call_git(['checkout', '--force', '--quiet', 'master'])
417 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) 419 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet'])
418 if self.working_branch in branches: 420 if self.working_branch in branches:
419 self._call_git(['branch', '-D', self.working_branch]) 421 self._call_git(['branch', '-D', self.working_branch])
420 422
421 def apply_patch(self, patches): 423 def apply_patch(self, patches, post_processors=None):
422 """Applies a patch on 'working_branch' and switch to it. 424 """Applies a patch on 'working_branch' and switch to it.
423 425
424 Also commits the changes on the local branch. 426 Also commits the changes on the local branch.
425 427
426 Ignores svn properties and raise an exception on unexpected ones. 428 Ignores svn properties and raise an exception on unexpected ones.
427 """ 429 """
430 post_processors = post_processors or self.post_processors or []
428 # It this throws, the checkout is corrupted. Maybe worth deleting it and 431 # It this throws, the checkout is corrupted. Maybe worth deleting it and
429 # trying again? 432 # trying again?
430 if self.remote_branch: 433 if self.remote_branch:
431 self._check_call_git( 434 self._check_call_git(
432 ['checkout', '-b', self.working_branch, 435 ['checkout', '-b', self.working_branch,
433 '%s/%s' % (self.remote, self.remote_branch), '--quiet']) 436 '%s/%s' % (self.remote, self.remote_branch), '--quiet'])
434 for p in patches: 437 for p in patches:
435 try: 438 try:
436 stdout = '' 439 stdout = ''
437 if p.is_delete: 440 if p.is_delete:
(...skipping 16 matching lines...) Expand all
454 # Ignore some known auto-props flags through .subversion/config, 457 # Ignore some known auto-props flags through .subversion/config,
455 # bails out on the other ones. 458 # bails out on the other ones.
456 # TODO(maruel): Read ~/.subversion/config and detect the rules that 459 # TODO(maruel): Read ~/.subversion/config and detect the rules that
457 # applies here to figure out if the property will be correctly 460 # applies here to figure out if the property will be correctly
458 # handled. 461 # handled.
459 if not prop[0] in ('svn:eol-style', 'svn:executable'): 462 if not prop[0] in ('svn:eol-style', 'svn:executable'):
460 raise patch.UnsupportedPatchFormat( 463 raise patch.UnsupportedPatchFormat(
461 p.filename, 464 p.filename,
462 'Cannot apply svn property %s to file %s.' % ( 465 'Cannot apply svn property %s to file %s.' % (
463 prop[0], p.filename)) 466 prop[0], p.filename))
464 for post in (self.post_processors or []): 467 for post in post_processors:
465 post(self, p) 468 post(self, p)
466 except OSError, e: 469 except OSError, e:
467 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) 470 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e))
468 except subprocess.CalledProcessError, e: 471 except subprocess.CalledProcessError, e:
469 raise PatchApplicationFailed( 472 raise PatchApplicationFailed(
470 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None))) 473 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None)))
471 # Once all the patches are processed and added to the index, commit the 474 # Once all the patches are processed and added to the index, commit the
472 # index. 475 # index.
473 self._check_call_git(['commit', '-m', 'Committed patch']) 476 self._check_call_git(['commit', '-m', 'Committed patch'])
474 # TODO(maruel): Weirdly enough they don't match, need to investigate. 477 # TODO(maruel): Weirdly enough they don't match, need to investigate.
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after
686 '-T', self.trunk, 689 '-T', self.trunk,
687 self.svn_url, self.project_path, 690 self.svn_url, self.project_path,
688 '--quiet'], 691 '--quiet'],
689 cwd=self.root_dir, 692 cwd=self.root_dir,
690 stderr=subprocess2.STDOUT) 693 stderr=subprocess2.STDOUT)
691 return super(GitSvnCheckout, self).prepare(revision) 694 return super(GitSvnCheckout, self).prepare(revision)
692 695
693 696
694 class ReadOnlyCheckout(object): 697 class ReadOnlyCheckout(object):
695 """Converts a checkout into a read-only one.""" 698 """Converts a checkout into a read-only one."""
696 def __init__(self, checkout): 699 def __init__(self, checkout, post_processors=None):
697 super(ReadOnlyCheckout, self).__init__() 700 super(ReadOnlyCheckout, self).__init__()
698 self.checkout = checkout 701 self.checkout = checkout
702 self.post_processors = (post_processors or []) + (
703 self.checkout.post_processors or [])
699 704
700 def prepare(self, revision): 705 def prepare(self, revision):
701 return self.checkout.prepare(revision) 706 return self.checkout.prepare(revision)
702 707
703 def get_settings(self, key): 708 def get_settings(self, key):
704 return self.checkout.get_settings(key) 709 return self.checkout.get_settings(key)
705 710
706 def apply_patch(self, patches): 711 def apply_patch(self, patches, post_processors=None):
M-A Ruel 2011/09/28 19:28:07 It's mainly for this edge case.
707 return self.checkout.apply_patch(patches) 712 return self.checkout.apply_patch(
713 patches, post_processors or self.post_processors)
708 714
709 def commit(self, message, user): # pylint: disable=R0201 715 def commit(self, message, user): # pylint: disable=R0201
710 logging.info('Would have committed for %s with message: %s' % ( 716 logging.info('Would have committed for %s with message: %s' % (
711 user, message)) 717 user, message))
712 return 'FAKE' 718 return 'FAKE'
713 719
714 @property 720 @property
715 def project_name(self): 721 def project_name(self):
716 return self.checkout.project_name 722 return self.checkout.project_name
717 723
718 @property 724 @property
719 def project_path(self): 725 def project_path(self):
720 return self.checkout.project_path 726 return self.checkout.project_path
OLDNEW
« no previous file with comments | « no previous file | tests/checkout_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698