OLD | NEW |
1 # coding=utf8 | 1 # coding=utf8 |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 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 import ConfigParser | 10 import ConfigParser |
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
113 | 113 |
114 Args: | 114 Args: |
115 patches: patch.PatchSet object. | 115 patches: patch.PatchSet object. |
116 """ | 116 """ |
117 raise NotImplementedError() | 117 raise NotImplementedError() |
118 | 118 |
119 def commit(self, commit_message, user): | 119 def commit(self, commit_message, user): |
120 """Commits the patch upstream, while impersonating 'user'.""" | 120 """Commits the patch upstream, while impersonating 'user'.""" |
121 raise NotImplementedError() | 121 raise NotImplementedError() |
122 | 122 |
| 123 def revisions(self, rev1, rev2): |
| 124 """Returns the count of revisions from rev1 to rev2, e.g. len(]rev1, rev2]). |
| 125 |
| 126 If rev2 is None, it means 'HEAD'. |
| 127 |
| 128 Returns None if there is no link between the two. |
| 129 """ |
| 130 raise NotImplementedError() |
| 131 |
123 | 132 |
124 class RawCheckout(CheckoutBase): | 133 class RawCheckout(CheckoutBase): |
125 """Used to apply a patch locally without any intent to commit it. | 134 """Used to apply a patch locally without any intent to commit it. |
126 | 135 |
127 To be used by the try server. | 136 To be used by the try server. |
128 """ | 137 """ |
129 def prepare(self, revision): | 138 def prepare(self, revision): |
130 """Stubbed out.""" | 139 """Stubbed out.""" |
131 pass | 140 pass |
132 | 141 |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
175 except OSError, e: | 184 except OSError, e: |
176 raise PatchApplicationFailed(p, '%s%s' % (stdout, e)) | 185 raise PatchApplicationFailed(p, '%s%s' % (stdout, e)) |
177 except subprocess.CalledProcessError, e: | 186 except subprocess.CalledProcessError, e: |
178 raise PatchApplicationFailed( | 187 raise PatchApplicationFailed( |
179 p, '%s%s' % (stdout, getattr(e, 'stdout', None))) | 188 p, '%s%s' % (stdout, getattr(e, 'stdout', None))) |
180 | 189 |
181 def commit(self, commit_message, user): | 190 def commit(self, commit_message, user): |
182 """Stubbed out.""" | 191 """Stubbed out.""" |
183 raise NotImplementedError('RawCheckout can\'t commit') | 192 raise NotImplementedError('RawCheckout can\'t commit') |
184 | 193 |
| 194 def revisions(self, _rev1, _rev2): |
| 195 return None |
| 196 |
185 | 197 |
186 class SvnConfig(object): | 198 class SvnConfig(object): |
187 """Parses a svn configuration file.""" | 199 """Parses a svn configuration file.""" |
188 def __init__(self, svn_config_dir=None): | 200 def __init__(self, svn_config_dir=None): |
189 super(SvnConfig, self).__init__() | 201 super(SvnConfig, self).__init__() |
190 self.svn_config_dir = svn_config_dir | 202 self.svn_config_dir = svn_config_dir |
191 self.default = not bool(self.svn_config_dir) | 203 self.default = not bool(self.svn_config_dir) |
192 if not self.svn_config_dir: | 204 if not self.svn_config_dir: |
193 if sys.platform == 'win32': | 205 if sys.platform == 'win32': |
194 self.svn_config_dir = os.path.join(os.environ['APPDATA'], 'Subversion') | 206 self.svn_config_dir = os.path.join(os.environ['APPDATA'], 'Subversion') |
(...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
418 return self._get_revision() | 430 return self._get_revision() |
419 | 431 |
420 def _get_revision(self): | 432 def _get_revision(self): |
421 out = self._check_output_svn(['info', '.']) | 433 out = self._check_output_svn(['info', '.']) |
422 revision = int(self._parse_svn_info(out, 'revision')) | 434 revision = int(self._parse_svn_info(out, 'revision')) |
423 if revision != self._last_seen_revision: | 435 if revision != self._last_seen_revision: |
424 logging.info('Updated to revision %d' % revision) | 436 logging.info('Updated to revision %d' % revision) |
425 self._last_seen_revision = revision | 437 self._last_seen_revision = revision |
426 return revision | 438 return revision |
427 | 439 |
| 440 def revisions(self, rev1, rev2): |
| 441 """Returns the number of actual commits, not just the difference between |
| 442 numbers. |
| 443 """ |
| 444 rev2 = rev2 or 'HEAD' |
| 445 # Revision range is inclusive and ordering doesn't matter, they'll appear in |
| 446 # the order specified. |
| 447 try: |
| 448 out = self._check_output_svn( |
| 449 ['log', '-q', self.svn_url, '-r', '%s:%s' % (rev1, rev2)]) |
| 450 except subprocess.CalledProcessError: |
| 451 return None |
| 452 # Ignore the '----' lines. |
| 453 return len([l for l in out.splitlines() if l.startswith('r')]) - 1 |
| 454 |
428 | 455 |
429 class GitCheckoutBase(CheckoutBase): | 456 class GitCheckoutBase(CheckoutBase): |
430 """Base class for git checkout. Not to be used as-is.""" | 457 """Base class for git checkout. Not to be used as-is.""" |
431 def __init__(self, root_dir, project_name, remote_branch, | 458 def __init__(self, root_dir, project_name, remote_branch, |
432 post_processors=None): | 459 post_processors=None): |
433 super(GitCheckoutBase, self).__init__( | 460 super(GitCheckoutBase, self).__init__( |
434 root_dir, project_name, post_processors) | 461 root_dir, project_name, post_processors) |
435 # There is no reason to not hardcode it. | 462 # There is no reason to not hardcode it. |
436 self.remote = 'origin' | 463 self.remote = 'origin' |
437 self.remote_branch = remote_branch | 464 self.remote_branch = remote_branch |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
558 """Returns the list of branches and the active one.""" | 585 """Returns the list of branches and the active one.""" |
559 out = self._check_output_git(['branch']).splitlines(False) | 586 out = self._check_output_git(['branch']).splitlines(False) |
560 branches = [l[2:] for l in out] | 587 branches = [l[2:] for l in out] |
561 active = None | 588 active = None |
562 for l in out: | 589 for l in out: |
563 if l.startswith('*'): | 590 if l.startswith('*'): |
564 active = l[2:] | 591 active = l[2:] |
565 break | 592 break |
566 return branches, active | 593 return branches, active |
567 | 594 |
| 595 def revisions(self, rev1, rev2): |
| 596 """Returns the number of actual commits between both hash.""" |
| 597 self._fetch_remote() |
| 598 |
| 599 rev2 = rev2 or '%s/%s' % (self.remote, self.remote_branch) |
| 600 # Revision range is ]rev1, rev2] and ordering matters. |
| 601 try: |
| 602 out = self._check_output_git( |
| 603 ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)]) |
| 604 except subprocess.CalledProcessError: |
| 605 return None |
| 606 return len(out.splitlines()) |
| 607 |
| 608 def _fetch_remote(self): |
| 609 """Fetches the remote without rebasing.""" |
| 610 raise NotImplementedError() |
| 611 |
| 612 |
| 613 class GitCheckout(GitCheckoutBase): |
| 614 """Git checkout implementation.""" |
| 615 def _fetch_remote(self): |
| 616 # git fetch is always verbose even with -q -q so redirect its output. |
| 617 self._check_output_git(['fetch', self.remote, self.remote_branch]) |
| 618 |
568 | 619 |
569 class ReadOnlyCheckout(object): | 620 class ReadOnlyCheckout(object): |
570 """Converts a checkout into a read-only one.""" | 621 """Converts a checkout into a read-only one.""" |
571 def __init__(self, checkout, post_processors=None): | 622 def __init__(self, checkout, post_processors=None): |
572 super(ReadOnlyCheckout, self).__init__() | 623 super(ReadOnlyCheckout, self).__init__() |
573 self.checkout = checkout | 624 self.checkout = checkout |
574 self.post_processors = (post_processors or []) + ( | 625 self.post_processors = (post_processors or []) + ( |
575 self.checkout.post_processors or []) | 626 self.checkout.post_processors or []) |
576 | 627 |
577 def prepare(self, revision): | 628 def prepare(self, revision): |
578 return self.checkout.prepare(revision) | 629 return self.checkout.prepare(revision) |
579 | 630 |
580 def get_settings(self, key): | 631 def get_settings(self, key): |
581 return self.checkout.get_settings(key) | 632 return self.checkout.get_settings(key) |
582 | 633 |
583 def apply_patch(self, patches, post_processors=None): | 634 def apply_patch(self, patches, post_processors=None): |
584 return self.checkout.apply_patch( | 635 return self.checkout.apply_patch( |
585 patches, post_processors or self.post_processors) | 636 patches, post_processors or self.post_processors) |
586 | 637 |
587 def commit(self, message, user): # pylint: disable=R0201 | 638 def commit(self, message, user): # pylint: disable=R0201 |
588 logging.info('Would have committed for %s with message: %s' % ( | 639 logging.info('Would have committed for %s with message: %s' % ( |
589 user, message)) | 640 user, message)) |
590 return 'FAKE' | 641 return 'FAKE' |
591 | 642 |
| 643 def revisions(self, rev1, rev2): |
| 644 return self.checkout.revisions(rev1, rev2) |
| 645 |
592 @property | 646 @property |
593 def project_name(self): | 647 def project_name(self): |
594 return self.checkout.project_name | 648 return self.checkout.project_name |
595 | 649 |
596 @property | 650 @property |
597 def project_path(self): | 651 def project_path(self): |
598 return self.checkout.project_path | 652 return self.checkout.project_path |
OLD | NEW |