Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Side by Side Diff: tools/auto_bisect/source_control.py

Issue 650223005: Refactor source_control.py and add a test. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/auto_bisect/bisect_results.py ('k') | tools/auto_bisect/source_control_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """This module contains the SourceControl class and related functions.""" 5 """This module contains functions for performing source control operations."""
6 6
7 import os 7 import os
8 8
9 import bisect_utils 9 import bisect_utils
10 10
11 CROS_VERSION_PATTERN = 'new version number from %s' 11 CROS_VERSION_PATTERN = 'new version number from %s'
12 12
13 13
14 def DetermineAndCreateSourceControl(opts): 14 def IsInGitRepository():
15 """Attempts to determine the underlying source control workflow and returns 15 output, _ = bisect_utils.RunGit(['rev-parse', '--is-inside-work-tree'])
16 a SourceControl object. 16 return output.strip() == 'true'
qyearsley 2014/10/14 17:48:34 This function is used to make the same check that
17 17
18 Returns: 18
19 An instance of a SourceControl object, or None if the current workflow 19 def SyncToRevisionWithGClient(revision):
20 is unsupported. 20 """Uses gclient to sync to the specified revision.
21 """ 21
22 (output, _) = bisect_utils.RunGit(['rev-parse', '--is-inside-work-tree']) 22 This is like running gclient sync --revision <revision>.
23 23
24 if output.strip() == 'true': 24 Args:
25 return GitSourceControl(opts) 25 revision: A git SHA1 hash or SVN revision number (depending on workflow).
26
27 Returns:
28 The return code of the call.
29 """
30 return bisect_utils.RunGClient(
31 ['sync', '--verbose', '--reset', '--force',
32 '--delete_unversioned_trees', '--nohooks', '--revision', revision])
qyearsley 2014/10/14 17:48:34 Sorry that the alignment in this diff is off -- mo
33
34
35 def SyncToRevisionWithRepo(timestamp):
36 return bisect_utils.RunRepoSyncAtTimestamp(timestamp)
37
38
39 def GetRevisionList(end_revision_hash, start_revision_hash, cwd=None):
40 """Retrieves a list of git commit hashes in a range.
41
42 Args:
43 end_revision_hash: The SHA1 for the end of the range, inclusive.
44 start_revision_hash: The SHA1 for the beginning of the range, inclusive.
45
46 Returns:
47 A list of the git commit hashes in the range, in reverse time order --
48 that is, starting with |end_revision_hash|.
49 """
50 revision_range = '%s..%s' % (start_revision_hash, end_revision_hash)
51 cmd = ['log', '--format=%H', '-10000', '--first-parent', revision_range]
52 log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
53
54 revision_hash_list = log_output.split()
55 revision_hash_list.append(start_revision_hash)
56
57 return revision_hash_list
58
59
60 def SyncToRevision(revision, sync_client=None):
61 """Syncs to the specified revision.
ojan 2014/10/14 22:59:09 IMO, docstring comments like this should be remove
qyearsley 2014/10/14 23:14:55 Ah, you're right. Docstring removed. In some othe
62
63 Args:
64 revision: The revision to sync to.
65 use_gclient: Specifies whether or not we should sync using gclient or
66 just use source control directly.
67
68 Returns:
69 True if successful.
70 """
71 if not sync_client:
72 _, return_code = bisect_utils.RunGit(['checkout', revision])[1]
73 elif sync_client == 'gclient':
74 return_code = SyncToRevisionWithGClient(revision)
75 elif sync_client == 'repo':
76 return_code = SyncToRevisionWithRepo(revision)
77 else:
78 raise NotImplementedError('Unsupported sync_client: "%s"' % sync_client)
79
80 return not return_code
81
82
83 def ResolveToRevision(revision_to_check, depot, depot_deps_dict,
84 search, cwd=None):
qyearsley 2014/10/14 17:48:34 This is the only function in here that I made a no
85 """Tries to resolve an SVN revision or commit position to a git SHA1.
86
87 Args:
88 revision_to_check: The user supplied revision string that may need to be
89 resolved to a git commit hash. This may be an SVN revision, git commit
90 position, or a git commit hash.
91 depot: The depot (dependency repository) that |revision_to_check| is from.
92 depot_deps_dict: A dictionary with information about different depots.
93 search: How many revisions forward or backward to search. If the value is
94 negative, the function will search backwards chronologically, otherwise
95 it will search forward.
96
97 Returns:
98 A string containing a git SHA1 hash, otherwise None.
99 """
100 # Android-chrome is git only, so no need to resolve this to anything else.
101 if depot == 'android-chrome':
102 return revision_to_check
103
104 if depot == 'cros':
105 return ResolveToRevisionCrOS(revision_to_check, cwd)
106
107 # If the given revision can't be parsed as an integer, then it may already
108 # be a git commit hash.
109 if not bisect_utils.IsStringInt(revision_to_check):
110 return revision_to_check
111
112 depot_svn = 'svn://svn.chromium.org/chrome/trunk/src'
113
114 if depot != 'chromium':
115 depot_svn = depot_deps_dict[depot]['svn']
116 svn_revision = int(revision_to_check)
117 git_revision = None
118
119 if search > 0:
120 search_range = xrange(svn_revision, svn_revision + search, 1)
121 else:
122 search_range = xrange(svn_revision, svn_revision + search, -1)
123
124 for i in search_range:
125 # NOTE: Checking for the git-svn-id footer is for backwards compatibility.
126 # When we can assume that all the revisions we care about are from after
127 # git commit positions started getting added, we don't need to check this.
128 svn_pattern = 'git-svn-id: %s@%d' % (depot_svn, i)
129 commit_position_pattern = '^Cr-Commit-Position: .*@{#%d}' % i
130 cmd = ['log', '--format=%H', '-1', '--grep', svn_pattern,
131 '--grep', commit_position_pattern, 'origin/master']
132 log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
133 log_output = log_output.strip()
134
135 if log_output:
136 git_revision = log_output
137 break
138
139 return git_revision
140
141
142 def ResolveToRevisionCrOS(revision_to_check, cwd=None):
143 """Return a git commit hash corresponding to the give version or revision.
144
145 TODO(qyearsley): Either verify that this works or delete it.
146 """
147 if bisect_utils.IsStringInt(revision_to_check):
148 return int(revision_to_check)
149
150 cwd = os.getcwd()
151 os.chdir(os.path.join(os.getcwd(), 'src', 'third_party',
152 'chromiumos-overlay'))
153 pattern = CROS_VERSION_PATTERN % revision_to_check
154 cmd = ['log', '--format=%ct', '-1', '--grep', pattern]
155
156 git_revision = None
157
158 log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
159 if log_output:
160 git_revision = log_output
161 git_revision = int(log_output.strip())
162 os.chdir(cwd)
163
164 return git_revision
165
166
167 def IsInProperBranch():
168 """Checks whether the current branch is "master"."""
169 cmd = ['rev-parse', '--abbrev-ref', 'HEAD']
170 log_output = bisect_utils.CheckRunGit(cmd)
171 log_output = log_output.strip()
172 return log_output == 'master'
173
174
175 def GetCommitPosition(git_revision, cwd=None):
176 """Finds git commit postion for the given git hash.
177
178 This function executes "git footer --position-num <git hash>" command to get
179 commit position the given revision.
180
181 Args:
182 git_revision: The git SHA1 to use.
183 cwd: Working directory to run the command from.
184
185 Returns:
186 Git commit position as integer or None.
187 """
188 cmd = ['footers', '--position-num', git_revision]
189 output = bisect_utils.CheckRunGit(cmd, cwd)
190 commit_position = output.strip()
191
192 if bisect_utils.IsStringInt(commit_position):
193 return int(commit_position)
26 194
27 return None 195 return None
28 196
29 197
30 # TODO(qyearsley): Almost all of the methods below could be top-level functions 198 def QueryRevisionInfo(revision, cwd=None):
31 # (or class methods). Refactoring may make this simpler. 199 """Gathers information on a particular revision, such as author's name,
32 # pylint: disable=R0201 200 email, subject, and date.
33 class SourceControl(object): 201
34 """SourceControl is an abstraction over the source control system.""" 202 Args:
35 203 revision: Revision you want to gather information on; a git commit hash.
36 def __init__(self): 204
37 super(SourceControl, self).__init__() 205 Returns:
38 206 A dict in the following format:
39 def SyncToRevisionWithGClient(self, revision): 207 {
40 """Uses gclient to sync to the specified revision. 208 'author': %s,
41 209 'email': %s,
42 This is like running gclient sync --revision <revision>. 210 'date': %s,
43 211 'subject': %s,
44 Args: 212 'body': %s,
45 revision: A git SHA1 hash or SVN revision number (depending on workflow). 213 }
46 214 """
47 Returns: 215 commit_info = {}
48 The return code of the call. 216
49 """ 217 formats = ['%aN', '%aE', '%s', '%cD', '%b']
50 return bisect_utils.RunGClient(['sync', '--verbose', '--reset', '--force', 218 targets = ['author', 'email', 'subject', 'date', 'body']
51 '--delete_unversioned_trees', '--nohooks', '--revision', revision]) 219
52 220 for i in xrange(len(formats)):
53 def SyncToRevisionWithRepo(self, timestamp): 221 cmd = ['log', '--format=%s' % formats[i], '-1', revision]
54 """Uses the repo command to sync all the underlying git depots to the 222 output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
55 specified time. 223 commit_info[targets[i]] = output.rstrip()
56 224
57 Args: 225 return commit_info
58 timestamp: The Unix timestamp to sync to. 226
59 227
60 Returns: 228 def CheckoutFileAtRevision(file_name, revision, cwd=None):
61 The return code of the call. 229 """Performs a checkout on a file at the given revision.
62 """ 230
63 return bisect_utils.RunRepoSyncAtTimestamp(timestamp) 231 Returns:
64 232 True if successful.
65 233 """
66 class GitSourceControl(SourceControl): 234 command = ['checkout', revision, file_name]
67 """GitSourceControl is used to query the underlying source control.""" 235 _, return_code = bisect_utils.RunGit(command, cwd=cwd)
68 236 return not return_code
69 def __init__(self, opts): 237
70 super(GitSourceControl, self).__init__() 238
71 self.opts = opts 239 def RevertFileToHead(file_name):
72 240 """Un-stages a file and resets the file's state to HEAD.
73 def IsGit(self): 241
74 return True 242 Returns:
75 243 True if successful.
76 def GetRevisionList(self, revision_range_end, revision_range_start, cwd=None): 244 """
77 """Retrieves a list of revisions between |revision_range_start| and 245 # Reset doesn't seem to return 0 on success.
78 |revision_range_end|. 246 bisect_utils.RunGit(['reset', 'HEAD', file_name])
79 247 _, return_code = bisect_utils.RunGit(
80 Args: 248 ['checkout', bisect_utils.FILE_DEPS_GIT])
81 revision_range_end: The SHA1 for the end of the range. 249 return not return_code
82 revision_range_start: The SHA1 for the beginning of the range. 250
83 251
84 Returns: 252 def QueryFileRevisionHistory(filename, revision_start, revision_end):
85 A list of the revisions between |revision_range_start| and 253 """Returns a list of commits that modified this file.
86 |revision_range_end| (inclusive). 254
87 """ 255 Args:
88 revision_range = '%s..%s' % (revision_range_start, revision_range_end) 256 filename: Name of file.
89 cmd = ['log', '--format=%H', '-10000', '--first-parent', revision_range] 257 revision_start: Start of revision range (inclusive).
90 log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd) 258 revision_end: End of revision range.
91 259
92 revision_hash_list = log_output.split() 260 Returns:
93 revision_hash_list.append(revision_range_start) 261 Returns a list of commits that touched this file.
94 262 """
95 return revision_hash_list 263 cmd = [
96 264 'log',
97 def SyncToRevision(self, revision, sync_client=None): 265 '--format=%H',
98 """Syncs to the specified revision. 266 '%s~1..%s' % (revision_start, revision_end),
99 267 '--',
100 Args: 268 filename,
101 revision: The revision to sync to. 269 ]
102 use_gclient: Specifies whether or not we should sync using gclient or 270 output = bisect_utils.CheckRunGit(cmd)
103 just use source control directly. 271 lines = output.split('\n')
104 272 return [o for o in lines if o]
105 Returns: 273
106 True if successful.
107 """
108
109 if not sync_client:
110 results = bisect_utils.RunGit(['checkout', revision])[1]
111 elif sync_client == 'gclient':
112 results = self.SyncToRevisionWithGClient(revision)
113 elif sync_client == 'repo':
114 results = self.SyncToRevisionWithRepo(revision)
115
116 return not results
117
118 def ResolveToRevision(self, revision_to_check, depot, depot_deps_dict,
119 search, cwd=None):
120 """Tries to resolve an SVN revision or commit position to a git SHA1.
121
122 Args:
123 revision_to_check: The user supplied revision string that may need to be
124 resolved to a git SHA1.
125 depot: The depot the revision_to_check is from.
126 depot_deps_dict: A dictionary with information about different depots.
127 search: The number of changelists to try if the first fails to resolve
128 to a git hash. If the value is negative, the function will search
129 backwards chronologically, otherwise it will search forward.
130
131 Returns:
132 A string containing a git SHA1 hash, otherwise None.
133 """
134 # Android-chrome is git only, so no need to resolve this to anything else.
135 if depot == 'android-chrome':
136 return revision_to_check
137
138 if depot != 'cros':
139 if not bisect_utils.IsStringInt(revision_to_check):
140 return revision_to_check
141
142 depot_svn = 'svn://svn.chromium.org/chrome/trunk/src'
143
144 if depot != 'chromium':
145 depot_svn = depot_deps_dict[depot]['svn']
146
147 svn_revision = int(revision_to_check)
148 git_revision = None
149
150 if search > 0:
151 search_range = xrange(svn_revision, svn_revision + search, 1)
152 else:
153 search_range = xrange(svn_revision, svn_revision + search, -1)
154
155 for i in search_range:
156 svn_pattern = 'git-svn-id: %s@%d' % (depot_svn, i)
157 commit_position_pattern = '^Cr-Commit-Position: .*@{#%d}' % i
158 cmd = ['log', '--format=%H', '-1', '--grep', svn_pattern,
159 '--grep', commit_position_pattern, 'origin/master']
160
161 (log_output, return_code) = bisect_utils.RunGit(cmd, cwd=cwd)
162
163 assert not return_code, 'An error occurred while running'\
164 ' "git %s"' % ' '.join(cmd)
165
166 if not return_code:
167 log_output = log_output.strip()
168
169 if log_output:
170 git_revision = log_output
171
172 break
173
174 return git_revision
175 else:
176 if bisect_utils.IsStringInt(revision_to_check):
177 return int(revision_to_check)
178 else:
179 cwd = os.getcwd()
180 os.chdir(os.path.join(os.getcwd(), 'src', 'third_party',
181 'chromiumos-overlay'))
182 pattern = CROS_VERSION_PATTERN % revision_to_check
183 cmd = ['log', '--format=%ct', '-1', '--grep', pattern]
184
185 git_revision = None
186
187 log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
188 if log_output:
189 git_revision = log_output
190 git_revision = int(log_output.strip())
191 os.chdir(cwd)
192
193 return git_revision
194
195 def IsInProperBranch(self):
196 """Confirms they're in the master branch for performing the bisection.
197 This is needed or gclient will fail to sync properly.
198
199 Returns:
200 True if the current branch on src is 'master'
201 """
202 cmd = ['rev-parse', '--abbrev-ref', 'HEAD']
203 log_output = bisect_utils.CheckRunGit(cmd)
204 log_output = log_output.strip()
205
206 return log_output == "master"
207
208 def GetCommitPosition(self, git_revision, cwd=None):
209 """Finds git commit postion for the given git hash.
210
211 This function executes "git footer --position-num <git hash>" command to get
212 commit position the given revision.
213
214 Args:
215 git_revision: The git SHA1 to use.
216 cwd: Working directory to run the command from.
217
218 Returns:
219 Git commit position as integer or None.
220 """
221 cmd = ['footers', '--position-num', git_revision]
222 output = bisect_utils.CheckRunGit(cmd, cwd)
223 commit_position = output.strip()
224
225 if bisect_utils.IsStringInt(commit_position):
226 return int(commit_position)
227
228 return None
229
230 def QueryRevisionInfo(self, revision, cwd=None):
231 """Gathers information on a particular revision, such as author's name,
232 email, subject, and date.
233
234 Args:
235 revision: Revision you want to gather information on.
236
237 Returns:
238 A dict in the following format:
239 {
240 'author': %s,
241 'email': %s,
242 'date': %s,
243 'subject': %s,
244 'body': %s,
245 }
246 """
247 commit_info = {}
248
249 formats = ['%aN', '%aE', '%s', '%cD', '%b']
250 targets = ['author', 'email', 'subject', 'date', 'body']
251
252 for i in xrange(len(formats)):
253 cmd = ['log', '--format=%s' % formats[i], '-1', revision]
254 output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
255 commit_info[targets[i]] = output.rstrip()
256
257 return commit_info
258
259 def CheckoutFileAtRevision(self, file_name, revision, cwd=None):
260 """Performs a checkout on a file at the given revision.
261
262 Returns:
263 True if successful.
264 """
265 return not bisect_utils.RunGit(
266 ['checkout', revision, file_name], cwd=cwd)[1]
267
268 def RevertFileToHead(self, file_name):
269 """Un-stages a file and resets the file's state to HEAD.
270
271 Returns:
272 True if successful.
273 """
274 # Reset doesn't seem to return 0 on success.
275 bisect_utils.RunGit(['reset', 'HEAD', file_name])
276
277 return not bisect_utils.RunGit(['checkout', bisect_utils.FILE_DEPS_GIT])[1]
278
279 def QueryFileRevisionHistory(self, filename, revision_start, revision_end):
280 """Returns a list of commits that modified this file.
281
282 Args:
283 filename: Name of file.
284 revision_start: Start of revision range.
285 revision_end: End of revision range.
286
287 Returns:
288 Returns a list of commits that touched this file.
289 """
290 cmd = ['log', '--format=%H', '%s~1..%s' % (revision_start, revision_end),
291 '--', filename]
292 output = bisect_utils.CheckRunGit(cmd)
293
294 return [o for o in output.split('\n') if o]
OLDNEW
« no previous file with comments | « tools/auto_bisect/bisect_results.py ('k') | tools/auto_bisect/source_control_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698