Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 |
| OLD | NEW |