10b978074c225685e0686cae354f86d646b4461a
[platform/framework/web/crosswalk.git] / src / tools / auto_bisect / source_control.py
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.
4
5 """This module contains the SourceControl class and related functions."""
6
7 import os
8
9 import bisect_utils
10
11 CROS_VERSION_PATTERN = 'new version number from %s'
12
13
14 def DetermineAndCreateSourceControl(opts):
15   """Attempts to determine the underlying source control workflow and returns
16   a SourceControl object.
17
18   Returns:
19     An instance of a SourceControl object, or None if the current workflow
20     is unsupported.
21   """
22   (output, _) = bisect_utils.RunGit(['rev-parse', '--is-inside-work-tree'])
23
24   if output.strip() == 'true':
25     return GitSourceControl(opts)
26
27   return None
28
29
30 # TODO(qyearsley): Almost all of the methods below could be top-level functions
31 # (or class methods). Refactoring may make this simpler.
32 # pylint: disable=R0201
33 class SourceControl(object):
34   """SourceControl is an abstraction over the source control system."""
35
36   def __init__(self):
37     super(SourceControl, self).__init__()
38
39   def SyncToRevisionWithGClient(self, revision):
40     """Uses gclient to sync to the specified revision.
41
42     This is like running gclient sync --revision <revision>.
43
44     Args:
45       revision: A git SHA1 hash or SVN revision number (depending on workflow).
46
47     Returns:
48       The return code of the call.
49     """
50     return bisect_utils.RunGClient(['sync', '--verbose', '--reset', '--force',
51         '--delete_unversioned_trees', '--nohooks', '--revision', revision])
52
53   def SyncToRevisionWithRepo(self, timestamp):
54     """Uses the repo command to sync all the underlying git depots to the
55     specified time.
56
57     Args:
58       timestamp: The Unix timestamp to sync to.
59
60     Returns:
61       The return code of the call.
62     """
63     return bisect_utils.RunRepoSyncAtTimestamp(timestamp)
64
65
66 class GitSourceControl(SourceControl):
67   """GitSourceControl is used to query the underlying source control."""
68
69   def __init__(self, opts):
70     super(GitSourceControl, self).__init__()
71     self.opts = opts
72
73   def IsGit(self):
74     return True
75
76   def GetRevisionList(self, revision_range_end, revision_range_start, cwd=None):
77     """Retrieves a list of revisions between |revision_range_start| and
78     |revision_range_end|.
79
80     Args:
81       revision_range_end: The SHA1 for the end of the range.
82       revision_range_start: The SHA1 for the beginning of the range.
83
84     Returns:
85       A list of the revisions between |revision_range_start| and
86       |revision_range_end| (inclusive).
87     """
88     revision_range = '%s..%s' % (revision_range_start, revision_range_end)
89     cmd = ['log', '--format=%H', '-10000', '--first-parent', revision_range]
90     log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
91
92     revision_hash_list = log_output.split()
93     revision_hash_list.append(revision_range_start)
94
95     return revision_hash_list
96
97   def SyncToRevision(self, revision, sync_client=None):
98     """Syncs to the specified revision.
99
100     Args:
101       revision: The revision to sync to.
102       use_gclient: Specifies whether or not we should sync using gclient or
103         just use source control directly.
104
105     Returns:
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]