From bc32ad17e6baf387f172cbc9ae0971e1e743b9aa Mon Sep 17 00:00:00 2001 From: "maruel@chromium.org" Date: Thu, 26 Jul 2012 13:22:47 +0000 Subject: [PATCH] Add .revisions() implementation to *Checkout classes. This permits to count the number of check-ins between two revisions. This will be used by the CQ to determine if a try job 'expired', e.g. it is so old that it can't be used anymore. The .revisions() function specifically count the number of commits, instead of just doing arithmetic on the svn revision numbers. R=cmp@chromium.org BUG= TEST=Manually: import checkout s = checkout.SvnCheckout('/path/to/chrome/src', None, None, None, 'svn://svn.chromium.org/chrome/trunk/src') s.revisions(148323, 148330) s.revisions(148323, None) g = checkout.GitCheckout('.', None, 'master') g.revisions('HEAD^^^^^^^^', None) g.revisions('HEAD^^', 'HEAD') Review URL: https://chromiumcodereview.appspot.com/10821011 git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@148546 0039d316-1c4b-4281-b951-d872f2087c98 --- checkout.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/checkout.py b/checkout.py index 6f82383db..c61552f32 100644 --- a/checkout.py +++ b/checkout.py @@ -120,6 +120,15 @@ class CheckoutBase(object): """Commits the patch upstream, while impersonating 'user'.""" raise NotImplementedError() + def revisions(self, rev1, rev2): + """Returns the count of revisions from rev1 to rev2, e.g. len(]rev1, rev2]). + + If rev2 is None, it means 'HEAD'. + + Returns None if there is no link between the two. + """ + raise NotImplementedError() + class RawCheckout(CheckoutBase): """Used to apply a patch locally without any intent to commit it. @@ -182,6 +191,9 @@ class RawCheckout(CheckoutBase): """Stubbed out.""" raise NotImplementedError('RawCheckout can\'t commit') + def revisions(self, _rev1, _rev2): + return None + class SvnConfig(object): """Parses a svn configuration file.""" @@ -425,6 +437,21 @@ class SvnCheckout(CheckoutBase, SvnMixIn): self._last_seen_revision = revision return revision + def revisions(self, rev1, rev2): + """Returns the number of actual commits, not just the difference between + numbers. + """ + rev2 = rev2 or 'HEAD' + # Revision range is inclusive and ordering doesn't matter, they'll appear in + # the order specified. + try: + out = self._check_output_svn( + ['log', '-q', self.svn_url, '-r', '%s:%s' % (rev1, rev2)]) + except subprocess.CalledProcessError: + return None + # Ignore the '----' lines. + return len([l for l in out.splitlines() if l.startswith('r')]) - 1 + class GitCheckoutBase(CheckoutBase): """Base class for git checkout. Not to be used as-is.""" @@ -565,6 +592,30 @@ class GitCheckoutBase(CheckoutBase): break return branches, active + def revisions(self, rev1, rev2): + """Returns the number of actual commits between both hash.""" + self._fetch_remote() + + rev2 = rev2 or '%s/%s' % (self.remote, self.remote_branch) + # Revision range is ]rev1, rev2] and ordering matters. + try: + out = self._check_output_git( + ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)]) + except subprocess.CalledProcessError: + return None + return len(out.splitlines()) + + def _fetch_remote(self): + """Fetches the remote without rebasing.""" + raise NotImplementedError() + + +class GitCheckout(GitCheckoutBase): + """Git checkout implementation.""" + def _fetch_remote(self): + # git fetch is always verbose even with -q -q so redirect its output. + self._check_output_git(['fetch', self.remote, self.remote_branch]) + class ReadOnlyCheckout(object): """Converts a checkout into a read-only one.""" @@ -589,6 +640,9 @@ class ReadOnlyCheckout(object): user, message)) return 'FAKE' + def revisions(self, rev1, rev2): + return self.checkout.revisions(rev1, rev2) + @property def project_name(self): return self.checkout.project_name