| 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 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 73 return get_code_review_setting(self.project_path, key) | 73 return get_code_review_setting(self.project_path, key) |
| 74 | 74 |
| 75 def prepare(self): | 75 def prepare(self): |
| 76 """Checks out a clean copy of the tree and removes any local modification. | 76 """Checks out a clean copy of the tree and removes any local modification. |
| 77 | 77 |
| 78 This function shouldn't throw unless the remote repository is inaccessible, | 78 This function shouldn't throw unless the remote repository is inaccessible, |
| 79 there is no free disk space or hard issues like that. | 79 there is no free disk space or hard issues like that. |
| 80 """ | 80 """ |
| 81 raise NotImplementedError() | 81 raise NotImplementedError() |
| 82 | 82 |
| 83 def apply_patch(self, patches): | 83 def apply_patch(self, patches, post_processor=None): |
| 84 """Applies a patch and returns the list of modified files. | 84 """Applies a patch and returns the list of modified files. |
| 85 | 85 |
| 86 This function should throw patch.UnsupportedPatchFormat or | 86 This function should throw patch.UnsupportedPatchFormat or |
| 87 PatchApplicationFailed when relevant. | 87 PatchApplicationFailed when relevant. |
| 88 |
| 89 Args: |
| 90 patches: patch.PatchSet object. |
| 91 post_processor: list of lambda(checkout, patches) to call on each of the |
| 92 modified files. |
| 88 """ | 93 """ |
| 89 raise NotImplementedError() | 94 raise NotImplementedError() |
| 90 | 95 |
| 91 def commit(self, commit_message, user): | 96 def commit(self, commit_message, user): |
| 92 """Commits the patch upstream, while impersonating 'user'.""" | 97 """Commits the patch upstream, while impersonating 'user'.""" |
| 93 raise NotImplementedError() | 98 raise NotImplementedError() |
| 94 | 99 |
| 95 | 100 |
| 96 class RawCheckout(CheckoutBase): | 101 class RawCheckout(CheckoutBase): |
| 97 """Used to apply a patch locally without any intent to commit it. | 102 """Used to apply a patch locally without any intent to commit it. |
| 98 | 103 |
| 99 To be used by the try server. | 104 To be used by the try server. |
| 100 """ | 105 """ |
| 101 def prepare(self): | 106 def prepare(self): |
| 102 """Stubbed out.""" | 107 """Stubbed out.""" |
| 103 pass | 108 pass |
| 104 | 109 |
| 105 def apply_patch(self, patches): | 110 def apply_patch(self, patches, post_processor=None): |
| 111 """Ignores svn properties.""" |
| 112 post_processor = post_processor or [] |
| 106 for p in patches: | 113 for p in patches: |
| 107 try: | 114 try: |
| 108 stdout = '' | 115 stdout = '' |
| 109 filename = os.path.join(self.project_path, p.filename) | 116 filename = os.path.join(self.project_path, p.filename) |
| 110 if p.is_delete: | 117 if p.is_delete: |
| 111 os.remove(filename) | 118 os.remove(filename) |
| 112 else: | 119 else: |
| 113 dirname = os.path.dirname(p.filename) | 120 dirname = os.path.dirname(p.filename) |
| 114 full_dir = os.path.join(self.project_path, dirname) | 121 full_dir = os.path.join(self.project_path, dirname) |
| 115 if dirname and not os.path.isdir(full_dir): | 122 if dirname and not os.path.isdir(full_dir): |
| 116 os.makedirs(full_dir) | 123 os.makedirs(full_dir) |
| 117 if p.is_binary: | 124 if p.is_binary: |
| 118 with open(os.path.join(filename), 'wb') as f: | 125 with open(os.path.join(filename), 'wb') as f: |
| 119 f.write(p.get()) | 126 f.write(p.get()) |
| 120 else: | 127 else: |
| 121 stdout = subprocess2.check_output( | 128 stdout = subprocess2.check_output( |
| 122 ['patch', '-p%s' % p.patchlevel], | 129 ['patch', '-p%s' % p.patchlevel], |
| 123 stdin=p.get(), | 130 stdin=p.get(), |
| 124 cwd=self.project_path) | 131 cwd=self.project_path) |
| 125 # Ignore p.svn_properties. | 132 for post in post_processor: |
| 133 post(self, p) |
| 126 except OSError, e: | 134 except OSError, e: |
| 127 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) | 135 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) |
| 128 except subprocess.CalledProcessError, e: | 136 except subprocess.CalledProcessError, e: |
| 129 raise PatchApplicationFailed( | 137 raise PatchApplicationFailed( |
| 130 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None))) | 138 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None))) |
| 131 | 139 |
| 132 def commit(self, commit_message, user): | 140 def commit(self, commit_message, user): |
| 133 """Stubbed out.""" | 141 """Stubbed out.""" |
| 134 raise NotImplementedError('RawCheckout can\'t commit') | 142 raise NotImplementedError('RawCheckout can\'t commit') |
| 135 | 143 |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 216 """Manages a subversion checkout.""" | 224 """Manages a subversion checkout.""" |
| 217 def __init__(self, root_dir, project_name, commit_user, commit_pwd, svn_url): | 225 def __init__(self, root_dir, project_name, commit_user, commit_pwd, svn_url): |
| 218 super(SvnCheckout, self).__init__(root_dir, project_name) | 226 super(SvnCheckout, self).__init__(root_dir, project_name) |
| 219 self.commit_user = commit_user | 227 self.commit_user = commit_user |
| 220 self.commit_pwd = commit_pwd | 228 self.commit_pwd = commit_pwd |
| 221 self.svn_url = svn_url | 229 self.svn_url = svn_url |
| 222 assert bool(self.commit_user) >= bool(self.commit_pwd) | 230 assert bool(self.commit_user) >= bool(self.commit_pwd) |
| 223 assert self.svn_url | 231 assert self.svn_url |
| 224 | 232 |
| 225 def prepare(self): | 233 def prepare(self): |
| 226 """Creates the initial checkouts for the repo.""" | |
| 227 # Will checkout if the directory is not present. | 234 # Will checkout if the directory is not present. |
| 228 if not os.path.isdir(self.project_path): | 235 if not os.path.isdir(self.project_path): |
| 229 logging.info('Checking out %s in %s' % | 236 logging.info('Checking out %s in %s' % |
| 230 (self.project_name, self.project_path)) | 237 (self.project_name, self.project_path)) |
| 231 revision = self._revert() | 238 revision = self._revert() |
| 232 if revision != self._last_seen_revision: | 239 if revision != self._last_seen_revision: |
| 233 logging.info('Updated at revision %d' % revision) | 240 logging.info('Updated at revision %d' % revision) |
| 234 self._last_seen_revision = revision | 241 self._last_seen_revision = revision |
| 235 return revision | 242 return revision |
| 236 | 243 |
| 237 def apply_patch(self, patches): | 244 def apply_patch(self, patches, post_processor=None): |
| 238 """Applies a patch.""" | 245 post_processor = post_processor or [] |
| 239 for p in patches: | 246 for p in patches: |
| 240 try: | 247 try: |
| 241 stdout = '' | 248 stdout = '' |
| 242 if p.is_delete: | 249 if p.is_delete: |
| 243 stdout += self._check_output_svn(['delete', p.filename, '--force']) | 250 stdout += self._check_output_svn(['delete', p.filename, '--force']) |
| 244 else: | 251 else: |
| 245 new = not os.path.exists(p.filename) | 252 new = not os.path.exists(p.filename) |
| 246 | 253 |
| 247 # svn add while creating directories otherwise svn add on the | 254 # svn add while creating directories otherwise svn add on the |
| 248 # contained files will silently fail. | 255 # contained files will silently fail. |
| (...skipping 18 matching lines...) Expand all Loading... |
| 267 cmd, stdin=p.get(), cwd=self.project_path) | 274 cmd, stdin=p.get(), cwd=self.project_path) |
| 268 if new: | 275 if new: |
| 269 stdout += self._check_output_svn(['add', p.filename, '--force']) | 276 stdout += self._check_output_svn(['add', p.filename, '--force']) |
| 270 for prop in p.svn_properties: | 277 for prop in p.svn_properties: |
| 271 stdout += self._check_output_svn( | 278 stdout += self._check_output_svn( |
| 272 ['propset', prop[0], prop[1], p.filename]) | 279 ['propset', prop[0], prop[1], p.filename]) |
| 273 for prop, value in self.svn_config.auto_props.iteritems(): | 280 for prop, value in self.svn_config.auto_props.iteritems(): |
| 274 if fnmatch.fnmatch(p.filename, prop): | 281 if fnmatch.fnmatch(p.filename, prop): |
| 275 stdout += self._check_output_svn( | 282 stdout += self._check_output_svn( |
| 276 ['propset'] + value.split('=', 1) + [p.filename]) | 283 ['propset'] + value.split('=', 1) + [p.filename]) |
| 284 for post in post_processor: |
| 285 post(self, p) |
| 277 except OSError, e: | 286 except OSError, e: |
| 278 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) | 287 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) |
| 279 except subprocess.CalledProcessError, e: | 288 except subprocess.CalledProcessError, e: |
| 280 raise PatchApplicationFailed( | 289 raise PatchApplicationFailed( |
| 281 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', ''))) | 290 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', ''))) |
| 282 | 291 |
| 283 def commit(self, commit_message, user): | 292 def commit(self, commit_message, user): |
| 284 logging.info('Committing patch for %s' % user) | 293 logging.info('Committing patch for %s' % user) |
| 285 assert self.commit_user | 294 assert self.commit_user |
| 286 handle, commit_filename = tempfile.mkstemp(text=True) | 295 handle, commit_filename = tempfile.mkstemp(text=True) |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 345 """ | 354 """ |
| 346 assert os.path.isdir(self.project_path) | 355 assert os.path.isdir(self.project_path) |
| 347 self._check_call_git(['reset', '--hard', '--quiet']) | 356 self._check_call_git(['reset', '--hard', '--quiet']) |
| 348 branches, active = self._branches() | 357 branches, active = self._branches() |
| 349 if active != 'master': | 358 if active != 'master': |
| 350 self._check_call_git(['checkout', 'master', '--force', '--quiet']) | 359 self._check_call_git(['checkout', 'master', '--force', '--quiet']) |
| 351 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) | 360 self._check_call_git(['pull', self.remote, self.remote_branch, '--quiet']) |
| 352 if self.working_branch in branches: | 361 if self.working_branch in branches: |
| 353 self._call_git(['branch', '-D', self.working_branch]) | 362 self._call_git(['branch', '-D', self.working_branch]) |
| 354 | 363 |
| 355 def apply_patch(self, patches): | 364 def apply_patch(self, patches, post_processor=None): |
| 356 """Applies a patch on 'working_branch' and switch to it.""" | 365 """Applies a patch on 'working_branch' and switch to it. |
| 366 |
| 367 Also commits the changes on the local branch. |
| 368 |
| 369 Ignores svn properties and raise an exception on unexpected ones. |
| 370 """ |
| 371 post_processor = post_processor or [] |
| 357 # It this throws, the checkout is corrupted. Maybe worth deleting it and | 372 # It this throws, the checkout is corrupted. Maybe worth deleting it and |
| 358 # trying again? | 373 # trying again? |
| 359 self._check_call_git( | 374 self._check_call_git( |
| 360 ['checkout', '-b', self.working_branch, | 375 ['checkout', '-b', self.working_branch, |
| 361 '%s/%s' % (self.remote, self.remote_branch)]) | 376 '%s/%s' % (self.remote, self.remote_branch), '--quiet']) |
| 362 for p in patches: | 377 for p in patches: |
| 363 try: | 378 try: |
| 364 stdout = '' | 379 stdout = '' |
| 365 if p.is_delete: | 380 if p.is_delete: |
| 366 stdout += self._check_output_git(['rm', p.filename]) | 381 stdout += self._check_output_git(['rm', p.filename]) |
| 367 else: | 382 else: |
| 368 dirname = os.path.dirname(p.filename) | 383 dirname = os.path.dirname(p.filename) |
| 369 full_dir = os.path.join(self.project_path, dirname) | 384 full_dir = os.path.join(self.project_path, dirname) |
| 370 if dirname and not os.path.isdir(full_dir): | 385 if dirname and not os.path.isdir(full_dir): |
| 371 os.makedirs(full_dir) | 386 os.makedirs(full_dir) |
| 372 if p.is_binary: | 387 if p.is_binary: |
| 373 with open(os.path.join(self.project_path, p.filename), 'wb') as f: | 388 with open(os.path.join(self.project_path, p.filename), 'wb') as f: |
| 374 f.write(p.get()) | 389 f.write(p.get()) |
| 375 stdout += self._check_output_git(['add', p.filename]) | 390 stdout += self._check_output_git(['add', p.filename]) |
| 376 else: | 391 else: |
| 377 stdout += self._check_output_git( | 392 stdout += self._check_output_git( |
| 378 ['apply', '--index', '-p%s' % p.patchlevel], stdin=p.get()) | 393 ['apply', '--index', '-p%s' % p.patchlevel], stdin=p.get()) |
| 379 for prop in p.svn_properties: | 394 for prop in p.svn_properties: |
| 380 # Ignore some known auto-props flags through .subversion/config, | 395 # Ignore some known auto-props flags through .subversion/config, |
| 381 # bails out on the other ones. | 396 # bails out on the other ones. |
| 382 # TODO(maruel): Read ~/.subversion/config and detect the rules that | 397 # TODO(maruel): Read ~/.subversion/config and detect the rules that |
| 383 # applies here to figure out if the property will be correctly | 398 # applies here to figure out if the property will be correctly |
| 384 # handled. | 399 # handled. |
| 385 if not prop[0] in ('svn:eol-style', 'svn:executable'): | 400 if not prop[0] in ('svn:eol-style', 'svn:executable'): |
| 386 raise patch.UnsupportedPatchFormat( | 401 raise patch.UnsupportedPatchFormat( |
| 387 p.filename, | 402 p.filename, |
| 388 'Cannot apply svn property %s to file %s.' % ( | 403 'Cannot apply svn property %s to file %s.' % ( |
| 389 prop[0], p.filename)) | 404 prop[0], p.filename)) |
| 405 for post in post_processor: |
| 406 post(self, p) |
| 390 except OSError, e: | 407 except OSError, e: |
| 391 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) | 408 raise PatchApplicationFailed(p.filename, '%s%s' % (stdout, e)) |
| 392 except subprocess.CalledProcessError, e: | 409 except subprocess.CalledProcessError, e: |
| 393 raise PatchApplicationFailed( | 410 raise PatchApplicationFailed( |
| 394 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None))) | 411 p.filename, '%s%s' % (stdout, getattr(e, 'stdout', None))) |
| 395 # Once all the patches are processed and added to the index, commit the | 412 # Once all the patches are processed and added to the index, commit the |
| 396 # index. | 413 # index. |
| 397 self._check_call_git(['commit', '-m', 'Committed patch']) | 414 self._check_call_git(['commit', '-m', 'Committed patch']) |
| 398 # TODO(maruel): Weirdly enough they don't match, need to investigate. | 415 # TODO(maruel): Weirdly enough they don't match, need to investigate. |
| 399 #found_files = self._check_output_git( | 416 #found_files = self._check_output_git( |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 485 # Fix the commit message and author. It returns the git hash, which we | 502 # Fix the commit message and author. It returns the git hash, which we |
| 486 # ignore unless it's None. | 503 # ignore unless it's None. |
| 487 if not super(GitSvnCheckoutBase, self).commit(commit_message, user): | 504 if not super(GitSvnCheckoutBase, self).commit(commit_message, user): |
| 488 return None | 505 return None |
| 489 # TODO(maruel): git-svn ignores --config-dir as of git-svn version 1.7.4 and | 506 # TODO(maruel): git-svn ignores --config-dir as of git-svn version 1.7.4 and |
| 490 # doesn't support --with-revprop. | 507 # doesn't support --with-revprop. |
| 491 # Either learn perl and upstream or suck it. | 508 # Either learn perl and upstream or suck it. |
| 492 kwargs = {} | 509 kwargs = {} |
| 493 if self.commit_pwd: | 510 if self.commit_pwd: |
| 494 kwargs['stdin'] = self.commit_pwd + '\n' | 511 kwargs['stdin'] = self.commit_pwd + '\n' |
| 512 kwargs['stderr'] = subprocess2.STDOUT |
| 495 self._check_call_git_svn( | 513 self._check_call_git_svn( |
| 496 ['dcommit', '--rmdir', '--find-copies-harder', | 514 ['dcommit', '--rmdir', '--find-copies-harder', |
| 497 '--username', self.commit_user], | 515 '--username', self.commit_user], |
| 498 **kwargs) | 516 **kwargs) |
| 499 revision = int(self._git_svn_info('revision')) | 517 revision = int(self._git_svn_info('revision')) |
| 500 return revision | 518 return revision |
| 501 | 519 |
| 502 def _cache_svn_auth(self): | 520 def _cache_svn_auth(self): |
| 503 """Caches the svn credentials. It is necessary since git-svn doesn't prompt | 521 """Caches the svn credentials. It is necessary since git-svn doesn't prompt |
| 504 for it.""" | 522 for it.""" |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 540 assert self.git_url | 558 assert self.git_url |
| 541 | 559 |
| 542 def prepare(self): | 560 def prepare(self): |
| 543 """Creates the initial checkout for the repo.""" | 561 """Creates the initial checkout for the repo.""" |
| 544 if not os.path.isdir(self.project_path): | 562 if not os.path.isdir(self.project_path): |
| 545 logging.info('Checking out %s in %s' % | 563 logging.info('Checking out %s in %s' % |
| 546 (self.project_name, self.project_path)) | 564 (self.project_name, self.project_path)) |
| 547 assert self.remote == 'origin' | 565 assert self.remote == 'origin' |
| 548 # self.project_path doesn't exist yet. | 566 # self.project_path doesn't exist yet. |
| 549 self._check_call_git( | 567 self._check_call_git( |
| 550 ['clone', self.git_url, self.project_name], | 568 ['clone', self.git_url, self.project_name, '--quiet'], |
| 551 cwd=self.root_dir) | 569 cwd=self.root_dir, |
| 570 stderr=subprocess2.STDOUT) |
| 552 try: | 571 try: |
| 553 configured_svn_url = self._check_output_git( | 572 configured_svn_url = self._check_output_git( |
| 554 ['config', 'svn-remote.svn.url']).strip() | 573 ['config', 'svn-remote.svn.url']).strip() |
| 555 except subprocess.CalledProcessError: | 574 except subprocess.CalledProcessError: |
| 556 configured_svn_url = '' | 575 configured_svn_url = '' |
| 557 | 576 |
| 558 if configured_svn_url.strip() != self.svn_url: | 577 if configured_svn_url.strip() != self.svn_url: |
| 559 self._check_call_git_svn( | 578 self._check_call_git_svn( |
| 560 ['init', | 579 ['init', |
| 561 '--prefix', self.remote + '/', | 580 '--prefix', self.remote + '/', |
| (...skipping 22 matching lines...) Expand all Loading... |
| 584 """Creates the initial checkout for the repo.""" | 603 """Creates the initial checkout for the repo.""" |
| 585 if not os.path.isdir(self.project_path): | 604 if not os.path.isdir(self.project_path): |
| 586 logging.info('Checking out %s in %s' % | 605 logging.info('Checking out %s in %s' % |
| 587 (self.project_name, self.project_path)) | 606 (self.project_name, self.project_path)) |
| 588 # TODO: Create a shallow clone. | 607 # TODO: Create a shallow clone. |
| 589 # self.project_path doesn't exist yet. | 608 # self.project_path doesn't exist yet. |
| 590 self._check_call_git_svn( | 609 self._check_call_git_svn( |
| 591 ['clone', | 610 ['clone', |
| 592 '--prefix', self.remote + '/', | 611 '--prefix', self.remote + '/', |
| 593 '-T', self.trunk, | 612 '-T', self.trunk, |
| 594 self.svn_url, self.project_path], | 613 self.svn_url, self.project_path, |
| 595 cwd=self.root_dir) | 614 '--quiet'], |
| 615 cwd=self.root_dir, |
| 616 stderr=subprocess2.STDOUT) |
| 596 super(GitSvnCheckout, self).prepare() | 617 super(GitSvnCheckout, self).prepare() |
| 597 return self._get_revision() | 618 return self._get_revision() |
| 598 | 619 |
| 599 | 620 |
| 600 class ReadOnlyCheckout(object): | 621 class ReadOnlyCheckout(object): |
| 601 """Converts a checkout into a read-only one.""" | 622 """Converts a checkout into a read-only one.""" |
| 602 def __init__(self, checkout): | 623 def __init__(self, checkout): |
| 603 self.checkout = checkout | 624 self.checkout = checkout |
| 604 | 625 |
| 605 def prepare(self): | 626 def prepare(self): |
| 606 return self.checkout.prepare() | 627 return self.checkout.prepare() |
| 607 | 628 |
| 608 def get_settings(self, key): | 629 def get_settings(self, key): |
| 609 return self.checkout.get_settings(key) | 630 return self.checkout.get_settings(key) |
| 610 | 631 |
| 611 def apply_patch(self, patches): | 632 def apply_patch(self, patches, post_processor=None): |
| 612 return self.checkout.apply_patch(patches) | 633 return self.checkout.apply_patch(patches, post_processor) |
| 613 | 634 |
| 614 def commit(self, message, user): # pylint: disable=R0201 | 635 def commit(self, message, user): # pylint: disable=R0201 |
| 615 logging.info('Would have committed for %s with message: %s' % ( | 636 logging.info('Would have committed for %s with message: %s' % ( |
| 616 user, message)) | 637 user, message)) |
| 617 return 'FAKE' | 638 return 'FAKE' |
| 618 | 639 |
| 619 @property | 640 @property |
| 620 def project_name(self): | 641 def project_name(self): |
| 621 return self.checkout.project_name | 642 return self.checkout.project_name |
| 622 | 643 |
| 623 @property | 644 @property |
| 624 def project_path(self): | 645 def project_path(self): |
| 625 return self.checkout.project_path | 646 return self.checkout.project_path |
| OLD | NEW |