1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """This module contains the SourceControl class and related functions."""
9 from . import bisect_utils
11 CROS_VERSION_PATTERN = 'new version number from %s'
14 def DetermineAndCreateSourceControl(opts):
15 """Attempts to determine the underlying source control workflow and returns
16 a SourceControl object.
19 An instance of a SourceControl object, or None if the current workflow
22 (output, _) = bisect_utils.RunGit(['rev-parse', '--is-inside-work-tree'])
24 if output.strip() == 'true':
25 return GitSourceControl(opts)
30 class SourceControl(object):
31 """SourceControl is an abstraction over the source control system."""
34 super(SourceControl, self).__init__()
36 def SyncToRevisionWithGClient(self, revision):
37 """Uses gclient to sync to the specified revision.
39 ie. gclient sync --revision <revision>
42 revision: The git SHA1 or svn CL (depending on workflow).
45 The return code of the call.
47 return bisect_utils.RunGClient(['sync', '--verbose', '--reset', '--force',
48 '--delete_unversioned_trees', '--nohooks', '--revision', revision])
50 def SyncToRevisionWithRepo(self, timestamp):
51 """Uses repo to sync all the underlying git depots to the specified
55 timestamp: The unix timestamp to sync to.
58 The return code of the call.
60 return bisect_utils.RunRepoSyncAtTimestamp(timestamp)
63 class GitSourceControl(SourceControl):
64 """GitSourceControl is used to query the underlying source control."""
66 def __init__(self, opts):
67 super(GitSourceControl, self).__init__()
73 def GetRevisionList(self, revision_range_end, revision_range_start, cwd=None):
74 """Retrieves a list of revisions between |revision_range_start| and
78 revision_range_end: The SHA1 for the end of the range.
79 revision_range_start: The SHA1 for the beginning of the range.
82 A list of the revisions between |revision_range_start| and
83 |revision_range_end| (inclusive).
85 revision_range = '%s..%s' % (revision_range_start, revision_range_end)
86 cmd = ['log', '--format=%H', '-10000', '--first-parent', revision_range]
87 log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
89 revision_hash_list = log_output.split()
90 revision_hash_list.append(revision_range_start)
92 return revision_hash_list
94 def SyncToRevision(self, revision, sync_client=None):
95 """Syncs to the specified revision.
98 revision: The revision to sync to.
99 use_gclient: Specifies whether or not we should sync using gclient or
100 just use source control directly.
107 results = bisect_utils.RunGit(['checkout', revision])[1]
108 elif sync_client == 'gclient':
109 results = self.SyncToRevisionWithGClient(revision)
110 elif sync_client == 'repo':
111 results = self.SyncToRevisionWithRepo(revision)
115 def ResolveToRevision(self, revision_to_check, depot, depot_deps_dict,
117 """If an SVN revision is supplied, try to resolve it to a git SHA1.
120 revision_to_check: The user supplied revision string that may need to be
121 resolved to a git SHA1.
122 depot: The depot the revision_to_check is from.
123 depot_deps_dict: A dictionary with information about different depots.
124 search: The number of changelists to try if the first fails to resolve
125 to a git hash. If the value is negative, the function will search
126 backwards chronologically, otherwise it will search forward.
129 A string containing a git SHA1 hash, otherwise None.
131 # Android-chrome is git only, so no need to resolve this to anything else.
132 if depot == 'android-chrome':
133 return revision_to_check
136 if not bisect_utils.IsStringInt(revision_to_check):
137 return revision_to_check
139 depot_svn = 'svn://svn.chromium.org/chrome/trunk/src'
141 if depot != 'chromium':
142 depot_svn = depot_deps_dict[depot]['svn']
144 svn_revision = int(revision_to_check)
148 search_range = xrange(svn_revision, svn_revision + search, 1)
150 search_range = xrange(svn_revision, svn_revision + search, -1)
152 for i in search_range:
153 svn_pattern = 'git-svn-id: %s@%d' % (depot_svn, i)
154 cmd = ['log', '--format=%H', '-1', '--grep', svn_pattern,
157 (log_output, return_code) = bisect_utils.RunGit(cmd, cwd=cwd)
159 assert not return_code, 'An error occurred while running'\
160 ' "git %s"' % ' '.join(cmd)
163 log_output = log_output.strip()
166 git_revision = log_output
172 if bisect_utils.IsStringInt(revision_to_check):
173 return int(revision_to_check)
176 os.chdir(os.path.join(os.getcwd(), 'src', 'third_party',
177 'chromiumos-overlay'))
178 pattern = CROS_VERSION_PATTERN % revision_to_check
179 cmd = ['log', '--format=%ct', '-1', '--grep', pattern]
183 log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
185 git_revision = log_output
186 git_revision = int(log_output.strip())
191 def IsInProperBranch(self):
192 """Confirms they're in the master branch for performing the bisection.
193 This is needed or gclient will fail to sync properly.
196 True if the current branch on src is 'master'
198 cmd = ['rev-parse', '--abbrev-ref', 'HEAD']
199 log_output = bisect_utils.CheckRunGit(cmd)
200 log_output = log_output.strip()
202 return log_output == "master"
204 def SVNFindRev(self, revision, cwd=None):
205 """Maps directly to the 'git svn find-rev' command.
208 revision: The git SHA1 to use.
211 An integer changelist #, otherwise None.
214 cmd = ['svn', 'find-rev', revision]
216 output = bisect_utils.CheckRunGit(cmd, cwd)
217 svn_revision = output.strip()
219 if bisect_utils.IsStringInt(svn_revision):
220 return int(svn_revision)
224 def QueryRevisionInfo(self, revision, cwd=None):
225 """Gathers information on a particular revision, such as author's name,
226 email, subject, and date.
229 revision: Revision you want to gather information on.
231 A dict in the following format:
242 formats = ['%cN', '%cE', '%s', '%cD', '%b']
243 targets = ['author', 'email', 'subject', 'date', 'body']
245 for i in xrange(len(formats)):
246 cmd = ['log', '--format=%s' % formats[i], '-1', revision]
247 output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
248 commit_info[targets[i]] = output.rstrip()
252 def CheckoutFileAtRevision(self, file_name, revision, cwd=None):
253 """Performs a checkout on a file at the given revision.
258 return not bisect_utils.RunGit(
259 ['checkout', revision, file_name], cwd=cwd)[1]
261 def RevertFileToHead(self, file_name):
262 """Unstages a file and returns it to HEAD.
267 # Reset doesn't seem to return 0 on success.
268 bisect_utils.RunGit(['reset', 'HEAD', file_name])
270 return not bisect_utils.RunGit(['checkout', bisect_utils.FILE_DEPS_GIT])[1]
272 def QueryFileRevisionHistory(self, filename, revision_start, revision_end):
273 """Returns a list of commits that modified this file.
276 filename: Name of file.
277 revision_start: Start of revision range.
278 revision_end: End of revision range.
281 Returns a list of commits that touched this file.
283 cmd = ['log', '--format=%H', '%s~1..%s' % (revision_start, revision_end),
285 output = bisect_utils.CheckRunGit(cmd)
287 return [o for o in output.split('\n') if o]