Index: tools/auto_bisect/source_control.py |
diff --git a/tools/auto_bisect/source_control.py b/tools/auto_bisect/source_control.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..66b7e73b35c232fb81e0c79d93df754f317ad05f |
--- /dev/null |
+++ b/tools/auto_bisect/source_control.py |
@@ -0,0 +1,287 @@ |
+# Copyright 2014 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""This module contains the SourceControl class and related functions.""" |
+ |
+import os |
+ |
+from . import bisect_utils |
+ |
+CROS_VERSION_PATTERN = 'new version number from %s' |
+ |
+ |
+def DetermineAndCreateSourceControl(opts): |
+ """Attempts to determine the underlying source control workflow and returns |
+ a SourceControl object. |
+ |
+ Returns: |
+ An instance of a SourceControl object, or None if the current workflow |
+ is unsupported. |
+ """ |
+ (output, _) = bisect_utils.RunGit(['rev-parse', '--is-inside-work-tree']) |
+ |
+ if output.strip() == 'true': |
+ return GitSourceControl(opts) |
+ |
+ return None |
+ |
+ |
+class SourceControl(object): |
shatch
2014/07/25 16:28:50
Not for this CL, but probably ok to kill the abstr
|
+ """SourceControl is an abstraction over the source control system.""" |
+ |
+ def __init__(self): |
+ super(SourceControl, self).__init__() |
+ |
+ def SyncToRevisionWithGClient(self, revision): |
+ """Uses gclient to sync to the specified revision. |
+ |
+ ie. gclient sync --revision <revision> |
+ |
+ Args: |
+ revision: The git SHA1 or svn CL (depending on workflow). |
+ |
+ Returns: |
+ The return code of the call. |
+ """ |
+ return bisect_utils.RunGClient(['sync', '--verbose', '--reset', '--force', |
+ '--delete_unversioned_trees', '--nohooks', '--revision', revision]) |
+ |
+ def SyncToRevisionWithRepo(self, timestamp): |
+ """Uses repo to sync all the underlying git depots to the specified |
+ time. |
+ |
+ Args: |
+ timestamp: The unix timestamp to sync to. |
+ |
+ Returns: |
+ The return code of the call. |
+ """ |
+ return bisect_utils.RunRepoSyncAtTimestamp(timestamp) |
+ |
+ |
+class GitSourceControl(SourceControl): |
+ """GitSourceControl is used to query the underlying source control.""" |
+ |
+ def __init__(self, opts): |
+ super(GitSourceControl, self).__init__() |
+ self.opts = opts |
+ |
+ def IsGit(self): |
+ return True |
+ |
+ def GetRevisionList(self, revision_range_end, revision_range_start, cwd=None): |
+ """Retrieves a list of revisions between |revision_range_start| and |
+ |revision_range_end|. |
+ |
+ Args: |
+ revision_range_end: The SHA1 for the end of the range. |
+ revision_range_start: The SHA1 for the beginning of the range. |
+ |
+ Returns: |
+ A list of the revisions between |revision_range_start| and |
+ |revision_range_end| (inclusive). |
+ """ |
+ revision_range = '%s..%s' % (revision_range_start, revision_range_end) |
+ cmd = ['log', '--format=%H', '-10000', '--first-parent', revision_range] |
+ log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
+ |
+ revision_hash_list = log_output.split() |
+ revision_hash_list.append(revision_range_start) |
+ |
+ return revision_hash_list |
+ |
+ def SyncToRevision(self, revision, sync_client=None): |
+ """Syncs to the specified revision. |
+ |
+ Args: |
+ revision: The revision to sync to. |
+ use_gclient: Specifies whether or not we should sync using gclient or |
+ just use source control directly. |
+ |
+ Returns: |
+ True if successful. |
+ """ |
+ |
+ if not sync_client: |
+ results = bisect_utils.RunGit(['checkout', revision])[1] |
+ elif sync_client == 'gclient': |
+ results = self.SyncToRevisionWithGClient(revision) |
+ elif sync_client == 'repo': |
+ results = self.SyncToRevisionWithRepo(revision) |
+ |
+ return not results |
+ |
+ def ResolveToRevision(self, revision_to_check, depot, depot_deps_dict, |
+ search, cwd=None): |
+ """If an SVN revision is supplied, try to resolve it to a git SHA1. |
+ |
+ Args: |
+ revision_to_check: The user supplied revision string that may need to be |
+ resolved to a git SHA1. |
+ depot: The depot the revision_to_check is from. |
+ depot_deps_dict: A dictionary with information about different depots. |
+ search: The number of changelists to try if the first fails to resolve |
+ to a git hash. If the value is negative, the function will search |
+ backwards chronologically, otherwise it will search forward. |
+ |
+ Returns: |
+ A string containing a git SHA1 hash, otherwise None. |
+ """ |
+ # Android-chrome is git only, so no need to resolve this to anything else. |
+ if depot == 'android-chrome': |
+ return revision_to_check |
+ |
+ if depot != 'cros': |
+ if not bisect_utils.IsStringInt(revision_to_check): |
+ return revision_to_check |
+ |
+ depot_svn = 'svn://svn.chromium.org/chrome/trunk/src' |
+ |
+ if depot != 'chromium': |
+ depot_svn = depot_deps_dict[depot]['svn'] |
+ |
+ svn_revision = int(revision_to_check) |
+ git_revision = None |
+ |
+ if search > 0: |
+ search_range = xrange(svn_revision, svn_revision + search, 1) |
+ else: |
+ search_range = xrange(svn_revision, svn_revision + search, -1) |
+ |
+ for i in search_range: |
+ svn_pattern = 'git-svn-id: %s@%d' % (depot_svn, i) |
+ cmd = ['log', '--format=%H', '-1', '--grep', svn_pattern, |
+ 'origin/master'] |
+ |
+ (log_output, return_code) = bisect_utils.RunGit(cmd, cwd=cwd) |
+ |
+ assert not return_code, 'An error occurred while running'\ |
+ ' "git %s"' % ' '.join(cmd) |
+ |
+ if not return_code: |
+ log_output = log_output.strip() |
+ |
+ if log_output: |
+ git_revision = log_output |
+ |
+ break |
+ |
+ return git_revision |
+ else: |
+ if bisect_utils.IsStringInt(revision_to_check): |
+ return int(revision_to_check) |
+ else: |
+ cwd = os.getcwd() |
+ os.chdir(os.path.join(os.getcwd(), 'src', 'third_party', |
+ 'chromiumos-overlay')) |
+ pattern = CROS_VERSION_PATTERN % revision_to_check |
+ cmd = ['log', '--format=%ct', '-1', '--grep', pattern] |
+ |
+ git_revision = None |
+ |
+ log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
+ if log_output: |
+ git_revision = log_output |
+ git_revision = int(log_output.strip()) |
+ os.chdir(cwd) |
+ |
+ return git_revision |
+ |
+ def IsInProperBranch(self): |
+ """Confirms they're in the master branch for performing the bisection. |
+ This is needed or gclient will fail to sync properly. |
+ |
+ Returns: |
+ True if the current branch on src is 'master' |
+ """ |
+ cmd = ['rev-parse', '--abbrev-ref', 'HEAD'] |
+ log_output = bisect_utils.CheckRunGit(cmd) |
+ log_output = log_output.strip() |
+ |
+ return log_output == "master" |
+ |
+ def SVNFindRev(self, revision, cwd=None): |
+ """Maps directly to the 'git svn find-rev' command. |
+ |
+ Args: |
+ revision: The git SHA1 to use. |
+ |
+ Returns: |
+ An integer changelist #, otherwise None. |
+ """ |
+ |
+ cmd = ['svn', 'find-rev', revision] |
+ |
+ output = bisect_utils.CheckRunGit(cmd, cwd) |
+ svn_revision = output.strip() |
+ |
+ if bisect_utils.IsStringInt(svn_revision): |
+ return int(svn_revision) |
+ |
+ return None |
+ |
+ def QueryRevisionInfo(self, revision, cwd=None): |
+ """Gathers information on a particular revision, such as author's name, |
+ email, subject, and date. |
+ |
+ Args: |
+ revision: Revision you want to gather information on. |
+ Returns: |
+ A dict in the following format: |
+ { |
+ 'author': %s, |
+ 'email': %s, |
+ 'date': %s, |
+ 'subject': %s, |
+ 'body': %s, |
+ } |
+ """ |
+ commit_info = {} |
+ |
+ formats = ['%cN', '%cE', '%s', '%cD', '%b'] |
+ targets = ['author', 'email', 'subject', 'date', 'body'] |
+ |
+ for i in xrange(len(formats)): |
+ cmd = ['log', '--format=%s' % formats[i], '-1', revision] |
+ output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
+ commit_info[targets[i]] = output.rstrip() |
+ |
+ return commit_info |
+ |
+ def CheckoutFileAtRevision(self, file_name, revision, cwd=None): |
+ """Performs a checkout on a file at the given revision. |
+ |
+ Returns: |
+ True if successful. |
+ """ |
+ return not bisect_utils.RunGit( |
+ ['checkout', revision, file_name], cwd=cwd)[1] |
+ |
+ def RevertFileToHead(self, file_name): |
+ """Unstages a file and returns it to HEAD. |
+ |
+ Returns: |
+ True if successful. |
+ """ |
+ # Reset doesn't seem to return 0 on success. |
+ bisect_utils.RunGit(['reset', 'HEAD', file_name]) |
+ |
+ return not bisect_utils.RunGit(['checkout', bisect_utils.FILE_DEPS_GIT])[1] |
+ |
+ def QueryFileRevisionHistory(self, filename, revision_start, revision_end): |
+ """Returns a list of commits that modified this file. |
+ |
+ Args: |
+ filename: Name of file. |
+ revision_start: Start of revision range. |
+ revision_end: End of revision range. |
+ |
+ Returns: |
+ Returns a list of commits that touched this file. |
+ """ |
+ cmd = ['log', '--format=%H', '%s~1..%s' % (revision_start, revision_end), |
+ filename] |
+ output = bisect_utils.CheckRunGit(cmd) |
+ |
+ return [o for o in output.split('\n') if o] |