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

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: Rebased 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'
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])
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 if not sync_client:
62 _, return_code = bisect_utils.RunGit(['checkout', revision])[1]
63 elif sync_client == 'gclient':
64 return_code = SyncToRevisionWithGClient(revision)
65 elif sync_client == 'repo':
66 return_code = SyncToRevisionWithRepo(revision)
67 else:
68 raise NotImplementedError('Unsupported sync_client: "%s"' % sync_client)
69
70 return not return_code
71
72
73 def ResolveToRevision(revision_to_check, depot, depot_deps_dict,
74 search, cwd=None):
75 """Tries to resolve an SVN revision or commit position to a git SHA1.
76
77 Args:
78 revision_to_check: The user supplied revision string that may need to be
79 resolved to a git commit hash. This may be an SVN revision, git commit
80 position, or a git commit hash.
81 depot: The depot (dependency repository) that |revision_to_check| is from.
82 depot_deps_dict: A dictionary with information about different depots.
83 search: How many revisions forward or backward to search. If the value is
84 negative, the function will search backwards chronologically, otherwise
85 it will search forward.
86
87 Returns:
88 A string containing a git SHA1 hash, otherwise None.
89 """
90 # Android-chrome is git only, so no need to resolve this to anything else.
91 if depot == 'android-chrome':
92 return revision_to_check
93
94 if depot == 'cros':
95 return ResolveToRevisionCrOS(revision_to_check, cwd)
96
97 # If the given revision can't be parsed as an integer, then it may already
98 # be a git commit hash.
99 if not bisect_utils.IsStringInt(revision_to_check):
100 return revision_to_check
101
102 depot_svn = 'svn://svn.chromium.org/chrome/trunk/src'
103
104 if depot != 'chromium':
105 depot_svn = depot_deps_dict[depot]['svn']
106 svn_revision = int(revision_to_check)
107 git_revision = None
108
109 if search > 0:
110 search_range = xrange(svn_revision, svn_revision + search, 1)
111 else:
112 search_range = xrange(svn_revision, svn_revision + search, -1)
113
114 for i in search_range:
115 # NOTE: Checking for the git-svn-id footer is for backwards compatibility.
116 # When we can assume that all the revisions we care about are from after
117 # git commit positions started getting added, we don't need to check this.
118 svn_pattern = 'git-svn-id: %s@%d' % (depot_svn, i)
119 commit_position_pattern = '^Cr-Commit-Position: .*@{#%d}' % i
120 cmd = ['log', '--format=%H', '-1', '--grep', svn_pattern,
121 '--grep', commit_position_pattern, 'origin/master']
122 log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
123 log_output = log_output.strip()
124
125 if log_output:
126 git_revision = log_output
127 break
128
129 return git_revision
130
131
132 def ResolveToRevisionCrOS(revision_to_check, cwd=None):
133 """Return a git commit hash corresponding to the give version or revision.
134
135 TODO(qyearsley): Either verify that this works or delete it.
136 """
137 if bisect_utils.IsStringInt(revision_to_check):
138 return int(revision_to_check)
139
140 cwd = os.getcwd()
141 os.chdir(os.path.join(os.getcwd(), 'src', 'third_party',
142 'chromiumos-overlay'))
143 pattern = CROS_VERSION_PATTERN % revision_to_check
144 cmd = ['log', '--format=%ct', '-1', '--grep', pattern]
145
146 git_revision = None
147
148 log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
149 if log_output:
150 git_revision = log_output
151 git_revision = int(log_output.strip())
152 os.chdir(cwd)
153
154 return git_revision
155
156
157 def IsInProperBranch():
158 """Checks whether the current branch is "master"."""
159 cmd = ['rev-parse', '--abbrev-ref', 'HEAD']
160 log_output = bisect_utils.CheckRunGit(cmd)
161 log_output = log_output.strip()
162 return log_output == 'master'
163
164
165 def GetCommitPosition(git_revision, cwd=None):
166 """Finds git commit postion for the given git hash.
167
168 This function executes "git footer --position-num <git hash>" command to get
169 commit position the given revision.
170
171 Args:
172 git_revision: The git SHA1 to use.
173 cwd: Working directory to run the command from.
174
175 Returns:
176 Git commit position as integer or None.
177 """
178 cmd = ['footers', '--position-num', git_revision]
179 output = bisect_utils.CheckRunGit(cmd, cwd)
180 commit_position = output.strip()
181
182 if bisect_utils.IsStringInt(commit_position):
183 return int(commit_position)
26 184
27 return None 185 return None
28 186
29 187
30 # TODO(qyearsley): Almost all of the methods below could be top-level functions 188 def QueryRevisionInfo(revision, cwd=None):
31 # (or class methods). Refactoring may make this simpler. 189 """Gathers information on a particular revision, such as author's name,
32 # pylint: disable=R0201 190 email, subject, and date.
33 class SourceControl(object): 191
34 """SourceControl is an abstraction over the source control system.""" 192 Args:
35 193 revision: Revision you want to gather information on; a git commit hash.
36 def __init__(self): 194
37 super(SourceControl, self).__init__() 195 Returns:
38 196 A dict in the following format:
39 def SyncToRevisionWithGClient(self, revision): 197 {
40 """Uses gclient to sync to the specified revision. 198 'author': %s,
41 199 'email': %s,
42 This is like running gclient sync --revision <revision>. 200 'date': %s,
43 201 'subject': %s,
44 Args: 202 'body': %s,
45 revision: A git SHA1 hash or SVN revision number (depending on workflow). 203 }
46 204 """
47 Returns: 205 commit_info = {}
48 The return code of the call. 206
49 """ 207 formats = ['%aN', '%aE', '%s', '%cD', '%b']
50 return bisect_utils.RunGClient(['sync', '--verbose', '--reset', '--force', 208 targets = ['author', 'email', 'subject', 'date', 'body']
51 '--delete_unversioned_trees', '--nohooks', '--revision', revision]) 209
52 210 for i in xrange(len(formats)):
53 def SyncToRevisionWithRepo(self, timestamp): 211 cmd = ['log', '--format=%s' % formats[i], '-1', revision]
54 """Uses the repo command to sync all the underlying git depots to the 212 output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
55 specified time. 213 commit_info[targets[i]] = output.rstrip()
56 214
57 Args: 215 return commit_info
58 timestamp: The Unix timestamp to sync to. 216
59 217
60 Returns: 218 def CheckoutFileAtRevision(file_name, revision, cwd=None):
61 The return code of the call. 219 """Performs a checkout on a file at the given revision.
62 """ 220
63 return bisect_utils.RunRepoSyncAtTimestamp(timestamp) 221 Returns:
64 222 True if successful.
65 223 """
66 class GitSourceControl(SourceControl): 224 command = ['checkout', revision, file_name]
67 """GitSourceControl is used to query the underlying source control.""" 225 _, return_code = bisect_utils.RunGit(command, cwd=cwd)
68 226 return not return_code
69 def __init__(self, opts): 227
70 super(GitSourceControl, self).__init__() 228
71 self.opts = opts 229 def RevertFileToHead(file_name):
72 230 """Un-stages a file and resets the file's state to HEAD.
73 def IsGit(self): 231
74 return True 232 Returns:
75 233 True if successful.
76 def GetRevisionList(self, revision_range_end, revision_range_start, cwd=None): 234 """
77 """Retrieves a list of revisions between |revision_range_start| and 235 # Reset doesn't seem to return 0 on success.
78 |revision_range_end|. 236 bisect_utils.RunGit(['reset', 'HEAD', file_name])
79 237 _, return_code = bisect_utils.RunGit(
80 Args: 238 ['checkout', bisect_utils.FILE_DEPS_GIT])
81 revision_range_end: The SHA1 for the end of the range. 239 return not return_code
82 revision_range_start: The SHA1 for the beginning of the range. 240
83 241
84 Returns: 242 def QueryFileRevisionHistory(filename, revision_start, revision_end):
85 A list of the revisions between |revision_range_start| and 243 """Returns a list of commits that modified this file.
86 |revision_range_end| (inclusive). 244
87 """ 245 Args:
88 revision_range = '%s..%s' % (revision_range_start, revision_range_end) 246 filename: Name of file.
89 cmd = ['log', '--format=%H', '-10000', '--first-parent', revision_range] 247 revision_start: Start of revision range (inclusive).
90 log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd) 248 revision_end: End of revision range.
91 249
92 revision_hash_list = log_output.split() 250 Returns:
93 revision_hash_list.append(revision_range_start) 251 Returns a list of commits that touched this file.
94 252 """
95 return revision_hash_list 253 cmd = [
96 254 'log',
97 def SyncToRevision(self, revision, sync_client=None): 255 '--format=%H',
98 """Syncs to the specified revision. 256 '%s~1..%s' % (revision_start, revision_end),
99 257 '--',
100 Args: 258 filename,
101 revision: The revision to sync to. 259 ]
102 use_gclient: Specifies whether or not we should sync using gclient or 260 output = bisect_utils.CheckRunGit(cmd)
103 just use source control directly. 261 lines = output.split('\n')
104 262 return [o for o in lines if o]
105 Returns: 263
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