Upstream version 9.38.198.0
[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 from . 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 class SourceControl(object):
31   """SourceControl is an abstraction over the source control system."""
32
33   def __init__(self):
34     super(SourceControl, self).__init__()
35
36   def SyncToRevisionWithGClient(self, revision):
37     """Uses gclient to sync to the specified revision.
38
39     ie. gclient sync --revision <revision>
40
41     Args:
42       revision: The git SHA1 or svn CL (depending on workflow).
43
44     Returns:
45       The return code of the call.
46     """
47     return bisect_utils.RunGClient(['sync', '--verbose', '--reset', '--force',
48         '--delete_unversioned_trees', '--nohooks', '--revision', revision])
49
50   def SyncToRevisionWithRepo(self, timestamp):
51     """Uses repo to sync all the underlying git depots to the specified
52     time.
53
54     Args:
55       timestamp: The unix timestamp to sync to.
56
57     Returns:
58       The return code of the call.
59     """
60     return bisect_utils.RunRepoSyncAtTimestamp(timestamp)
61
62
63 class GitSourceControl(SourceControl):
64   """GitSourceControl is used to query the underlying source control."""
65
66   def __init__(self, opts):
67     super(GitSourceControl, self).__init__()
68     self.opts = opts
69
70   def IsGit(self):
71     return True
72
73   def GetRevisionList(self, revision_range_end, revision_range_start, cwd=None):
74     """Retrieves a list of revisions between |revision_range_start| and
75     |revision_range_end|.
76
77     Args:
78       revision_range_end: The SHA1 for the end of the range.
79       revision_range_start: The SHA1 for the beginning of the range.
80
81     Returns:
82       A list of the revisions between |revision_range_start| and
83       |revision_range_end| (inclusive).
84     """
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)
88
89     revision_hash_list = log_output.split()
90     revision_hash_list.append(revision_range_start)
91
92     return revision_hash_list
93
94   def SyncToRevision(self, revision, sync_client=None):
95     """Syncs to the specified revision.
96
97     Args:
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.
101
102     Returns:
103       True if successful.
104     """
105
106     if not sync_client:
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)
112
113     return not results
114
115   def ResolveToRevision(self, revision_to_check, depot, depot_deps_dict,
116                         search, cwd=None):
117     """If an SVN revision is supplied, try to resolve it to a git SHA1.
118
119     Args:
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.
127
128     Returns:
129       A string containing a git SHA1 hash, otherwise None.
130     """
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
134
135     if depot != 'cros':
136       if not bisect_utils.IsStringInt(revision_to_check):
137         return revision_to_check
138
139       depot_svn = 'svn://svn.chromium.org/chrome/trunk/src'
140
141       if depot != 'chromium':
142         depot_svn = depot_deps_dict[depot]['svn']
143
144       svn_revision = int(revision_to_check)
145       git_revision = None
146
147       if search > 0:
148         search_range = xrange(svn_revision, svn_revision + search, 1)
149       else:
150         search_range = xrange(svn_revision, svn_revision + search, -1)
151
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,
155                'origin/master']
156
157         (log_output, return_code) = bisect_utils.RunGit(cmd, cwd=cwd)
158
159         assert not return_code, 'An error occurred while running'\
160                                 ' "git %s"' % ' '.join(cmd)
161
162         if not return_code:
163           log_output = log_output.strip()
164
165           if log_output:
166             git_revision = log_output
167
168             break
169
170       return git_revision
171     else:
172       if bisect_utils.IsStringInt(revision_to_check):
173         return int(revision_to_check)
174       else:
175         cwd = os.getcwd()
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]
180
181         git_revision = None
182
183         log_output = bisect_utils.CheckRunGit(cmd, cwd=cwd)
184         if log_output:
185           git_revision = log_output
186           git_revision = int(log_output.strip())
187         os.chdir(cwd)
188
189         return git_revision
190
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.
194
195     Returns:
196       True if the current branch on src is 'master'
197     """
198     cmd = ['rev-parse', '--abbrev-ref', 'HEAD']
199     log_output = bisect_utils.CheckRunGit(cmd)
200     log_output = log_output.strip()
201
202     return log_output == "master"
203
204   def SVNFindRev(self, revision, cwd=None):
205     """Maps directly to the 'git svn find-rev' command.
206
207     Args:
208       revision: The git SHA1 to use.
209
210     Returns:
211       An integer changelist #, otherwise None.
212     """
213
214     cmd = ['svn', 'find-rev', revision]
215
216     output = bisect_utils.CheckRunGit(cmd, cwd)
217     svn_revision = output.strip()
218
219     if bisect_utils.IsStringInt(svn_revision):
220       return int(svn_revision)
221
222     return None
223
224   def QueryRevisionInfo(self, revision, cwd=None):
225     """Gathers information on a particular revision, such as author's name,
226     email, subject, and date.
227
228     Args:
229       revision: Revision you want to gather information on.
230     Returns:
231       A dict in the following format:
232       {
233         'author': %s,
234         'email': %s,
235         'date': %s,
236         'subject': %s,
237         'body': %s,
238       }
239     """
240     commit_info = {}
241
242     formats = ['%cN', '%cE', '%s', '%cD', '%b']
243     targets = ['author', 'email', 'subject', 'date', 'body']
244
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()
249
250     return commit_info
251
252   def CheckoutFileAtRevision(self, file_name, revision, cwd=None):
253     """Performs a checkout on a file at the given revision.
254
255     Returns:
256       True if successful.
257     """
258     return not bisect_utils.RunGit(
259         ['checkout', revision, file_name], cwd=cwd)[1]
260
261   def RevertFileToHead(self, file_name):
262     """Unstages a file and returns it to HEAD.
263
264     Returns:
265       True if successful.
266     """
267     # Reset doesn't seem to return 0 on success.
268     bisect_utils.RunGit(['reset', 'HEAD', file_name])
269
270     return not bisect_utils.RunGit(['checkout', bisect_utils.FILE_DEPS_GIT])[1]
271
272   def QueryFileRevisionHistory(self, filename, revision_start, revision_end):
273     """Returns a list of commits that modified this file.
274
275     Args:
276         filename: Name of file.
277         revision_start: Start of revision range.
278         revision_end: End of revision range.
279
280     Returns:
281         Returns a list of commits that touched this file.
282     """
283     cmd = ['log', '--format=%H', '%s~1..%s' % (revision_start, revision_end),
284            filename]
285     output = bisect_utils.CheckRunGit(cmd)
286
287     return [o for o in output.split('\n') if o]