blob: eb413a235eb315bb907aa50c3664ca3f8cba86d8 [file] [log] [blame]
maruel@chromium.org7d654672012-01-05 19:07:23 +00001# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgd5800f12009-11-12 20:03:43 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +00005"""SCM-specific utility classes."""
maruel@chromium.orgd5800f12009-11-12 20:03:43 +00006
maruel@chromium.orgfd9cbbb2010-01-08 23:04:03 +00007import glob
Raul Tambreb946b232019-03-26 14:48:46 +00008import io
maruel@chromium.orgd5800f12009-11-12 20:03:43 +00009import os
Pierre-Antoine Manzagolfc1c6f42017-05-30 12:29:58 -040010import platform
maruel@chromium.orgd5800f12009-11-12 20:03:43 +000011import re
maruel@chromium.orgd5800f12009-11-12 20:03:43 +000012import sys
maruel@chromium.orgd5800f12009-11-12 20:03:43 +000013
14import gclient_utils
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000015import subprocess2
16
maruel@chromium.orgd5800f12009-11-12 20:03:43 +000017
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +000018def ValidateEmail(email):
Andrii Shyshkalov2f727912018-10-15 17:02:33 +000019 return (
20 re.match(r"^[a-zA-Z0-9._%\-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email)
21 is not None)
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +000022
maruel@chromium.orgd5800f12009-11-12 20:03:43 +000023
maruel@chromium.orgfd9cbbb2010-01-08 23:04:03 +000024def GetCasedPath(path):
25 """Elcheapos way to get the real path case on Windows."""
26 if sys.platform.startswith('win') and os.path.exists(path):
27 # Reconstruct the path.
28 path = os.path.abspath(path)
29 paths = path.split('\\')
30 for i in range(len(paths)):
31 if i == 0:
32 # Skip drive letter.
33 continue
34 subpath = '\\'.join(paths[:i+1])
35 prev = len('\\'.join(paths[:i]))
36 # glob.glob will return the cased path for the last item only. This is why
37 # we are calling it in a loop. Extract the data we want and put it back
38 # into the list.
39 paths[i] = glob.glob(subpath + '*')[0][prev+1:len(subpath)]
40 path = '\\'.join(paths)
41 return path
42
43
maruel@chromium.org3c55d982010-05-06 14:25:44 +000044def GenFakeDiff(filename):
45 """Generates a fake diff from a file."""
46 file_content = gclient_utils.FileRead(filename, 'rb').splitlines(True)
maruel@chromium.orgc6d170e2010-06-03 00:06:00 +000047 filename = filename.replace(os.sep, '/')
maruel@chromium.org3c55d982010-05-06 14:25:44 +000048 nb_lines = len(file_content)
49 # We need to use / since patch on unix will fail otherwise.
Raul Tambreb946b232019-03-26 14:48:46 +000050 data = io.StringIO()
maruel@chromium.org3c55d982010-05-06 14:25:44 +000051 data.write("Index: %s\n" % filename)
52 data.write('=' * 67 + '\n')
53 # Note: Should we use /dev/null instead?
54 data.write("--- %s\n" % filename)
55 data.write("+++ %s\n" % filename)
56 data.write("@@ -0,0 +1,%d @@\n" % nb_lines)
57 # Prepend '+' to every lines.
58 for line in file_content:
59 data.write('+')
60 data.write(line)
61 result = data.getvalue()
62 data.close()
63 return result
64
65
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000066def determine_scm(root):
67 """Similar to upload.py's version but much simpler.
68
Aaron Gable208db562016-12-21 14:46:36 -080069 Returns 'git' or None.
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000070 """
Aaron Gable208db562016-12-21 14:46:36 -080071 if os.path.isdir(os.path.join(root, '.git')):
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000072 return 'git'
73 else:
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000074 try:
maruel@chromium.org91def9b2011-09-14 16:28:07 +000075 subprocess2.check_call(
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000076 ['git', 'rev-parse', '--show-cdup'],
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000077 stdout=subprocess2.VOID,
maruel@chromium.org87e6d332011-09-09 19:01:28 +000078 stderr=subprocess2.VOID,
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000079 cwd=root)
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000080 return 'git'
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000081 except (OSError, subprocess2.CalledProcessError):
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000082 return None
83
84
maruel@chromium.org36ac2392011-10-12 16:36:11 +000085def only_int(val):
86 if val.isdigit():
87 return int(val)
88 else:
89 return 0
90
91
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000092class GIT(object):
maruel@chromium.org36ac2392011-10-12 16:36:11 +000093 current_version = None
94
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000095 @staticmethod
szager@chromium.org6d8115d2014-04-23 20:59:23 +000096 def ApplyEnvVars(kwargs):
97 env = kwargs.pop('env', None) or os.environ.copy()
98 # Don't prompt for passwords; just fail quickly and noisily.
99 # By default, git will use an interactive terminal prompt when a username/
100 # password is needed. That shouldn't happen in the chromium workflow,
101 # and if it does, then gclient may hide the prompt in the midst of a flood
102 # of terminal spew. The only indication that something has gone wrong
103 # will be when gclient hangs unresponsively. Instead, we disable the
104 # password prompt and simply allow git to fail noisily. The error
105 # message produced by git will be copied to gclient's output.
106 env.setdefault('GIT_ASKPASS', 'true')
107 env.setdefault('SSH_ASKPASS', 'true')
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000108 # 'cat' is a magical git string that disables pagers on all platforms.
szager@chromium.org6d8115d2014-04-23 20:59:23 +0000109 env.setdefault('GIT_PAGER', 'cat')
110 return env
111
112 @staticmethod
113 def Capture(args, cwd, strip_out=True, **kwargs):
114 env = GIT.ApplyEnvVars(kwargs)
ilevy@chromium.org4380c802013-07-12 23:38:41 +0000115 output = subprocess2.check_output(
Edward Lesmescba46082020-03-25 00:29:06 +0000116 ['git'] + args, cwd=cwd, stderr=subprocess2.PIPE, env=env,
117 **kwargs).decode('utf-8', 'replace')
ilevy@chromium.org4380c802013-07-12 23:38:41 +0000118 return output.strip() if strip_out else output
maruel@chromium.orgd5800f12009-11-12 20:03:43 +0000119
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000120 @staticmethod
Edward Lemur7f6dec02020-02-06 20:23:58 +0000121 def CaptureStatus(cwd, upstream_branch):
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000122 """Returns git status.
maruel@chromium.orgd5800f12009-11-12 20:03:43 +0000123
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000124 Returns an array of (status, file) tuples."""
msb@chromium.org786fb682010-06-02 15:16:23 +0000125 if upstream_branch is None:
maruel@chromium.org80a9ef12011-12-13 20:44:10 +0000126 upstream_branch = GIT.GetUpstreamBranch(cwd)
msb@chromium.org786fb682010-06-02 15:16:23 +0000127 if upstream_branch is None:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000128 raise gclient_utils.Error('Cannot determine upstream branch')
Aaron Gable7817f022017-12-12 09:43:17 -0800129 command = ['-c', 'core.quotePath=false', 'diff',
130 '--name-status', '--no-renames', '-r', '%s...' % upstream_branch]
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000131 status = GIT.Capture(command, cwd)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000132 results = []
133 if status:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000134 for statusline in status.splitlines():
maruel@chromium.orgcc1614b2010-09-20 17:13:17 +0000135 # 3-way merges can cause the status can be 'MMM' instead of 'M'. This
136 # can happen when the user has 2 local branches and he diffs between
137 # these 2 branches instead diffing to upstream.
Bruce Dawson9c062012019-05-02 19:20:28 +0000138 m = re.match(r'^(\w)+\t(.+)$', statusline)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000139 if not m:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000140 raise gclient_utils.Error(
141 'status currently unsupported: %s' % statusline)
maruel@chromium.orgcc1614b2010-09-20 17:13:17 +0000142 # Only grab the first letter.
143 results.append(('%s ' % m.group(1)[0], m.group(2)))
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000144 return results
maruel@chromium.orgd5800f12009-11-12 20:03:43 +0000145
maruel@chromium.orgc78f2462009-11-21 01:20:57 +0000146 @staticmethod
Edward Lemur15a9b8c2020-02-13 00:52:30 +0000147 def GetConfig(cwd, key, default=None):
148 try:
149 return GIT.Capture(['config', key], cwd=cwd)
150 except subprocess2.CalledProcessError:
151 return default
152
153 @staticmethod
154 def GetBranchConfig(cwd, branch, key, default=None):
155 assert branch, 'A branch must be given'
156 key = 'branch.%s.%s' % (branch, key)
157 return GIT.GetConfig(cwd, key, default)
158
159 @staticmethod
160 def SetConfig(cwd, key, value=None):
161 if value is None:
162 args = ['config', '--unset', key]
163 else:
164 args = ['config', key, value]
165 GIT.Capture(args, cwd=cwd)
166
167 @staticmethod
168 def SetBranchConfig(cwd, branch, key, value=None):
169 assert branch, 'A branch must be given'
170 key = 'branch.%s.%s' % (branch, key)
171 GIT.SetConfig(cwd, key, value)
172
173 @staticmethod
nodir@chromium.orgead4c7e2014-04-03 01:01:06 +0000174 def IsWorkTreeDirty(cwd):
175 return GIT.Capture(['status', '-s'], cwd=cwd) != ''
176
177 @staticmethod
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000178 def GetEmail(cwd):
maruel@chromium.orgc78f2462009-11-21 01:20:57 +0000179 """Retrieves the user email address if known."""
Edward Lemur15a9b8c2020-02-13 00:52:30 +0000180 return GIT.GetConfig(cwd, 'user.email', '')
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000181
182 @staticmethod
183 def ShortBranchName(branch):
184 """Converts a name like 'refs/heads/foo' to just 'foo'."""
185 return branch.replace('refs/heads/', '')
186
187 @staticmethod
188 def GetBranchRef(cwd):
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000189 """Returns the full branch reference, e.g. 'refs/heads/master'."""
Edward Lemur85153282020-02-14 22:06:29 +0000190 try:
191 return GIT.Capture(['symbolic-ref', 'HEAD'], cwd=cwd)
192 except subprocess2.CalledProcessError:
193 return None
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000194
195 @staticmethod
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000196 def GetBranch(cwd):
197 """Returns the short branch name, e.g. 'master'."""
Edward Lemur85153282020-02-14 22:06:29 +0000198 branchref = GIT.GetBranchRef(cwd)
199 if branchref:
200 return GIT.ShortBranchName(branchref)
201 return None
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000202
203 @staticmethod
Edward Lemur15a9b8c2020-02-13 00:52:30 +0000204 def GetRemoteBranches(cwd):
205 return GIT.Capture(['branch', '-r'], cwd=cwd).split()
206
207 @staticmethod
208 def FetchUpstreamTuple(cwd, branch=None):
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000209 """Returns a tuple containg remote and remote ref,
210 e.g. 'origin', 'refs/heads/master'
211 """
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000212 try:
Edward Lemur15a9b8c2020-02-13 00:52:30 +0000213 branch = branch or GIT.GetBranch(cwd)
maruel@chromium.orgda64d632011-09-08 17:41:15 +0000214 except subprocess2.CalledProcessError:
Edward Lemur15a9b8c2020-02-13 00:52:30 +0000215 pass
216 if branch:
217 upstream_branch = GIT.GetBranchConfig(cwd, branch, 'merge')
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000218 if upstream_branch:
Edward Lemur15a9b8c2020-02-13 00:52:30 +0000219 remote = GIT.GetBranchConfig(cwd, branch, 'remote', '.')
220 return remote, upstream_branch
221
222 upstream_branch = GIT.GetConfig(cwd, 'rietveld.upstream-branch')
223 if upstream_branch:
224 remote = GIT.GetConfig(cwd, 'rietveld.upstream-remote', '.')
225 return remote, upstream_branch
226
227 # Else, try to guess the origin remote.
228 if 'origin/master' in GIT.GetRemoteBranches(cwd):
229 # Fall back on origin/master if it exits.
230 return 'origin', 'refs/heads/master'
231
232 return None, None
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000233
234 @staticmethod
Edward Lemur9a5e3bd2019-04-02 23:37:45 +0000235 def RefToRemoteRef(ref, remote):
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000236 """Convert a checkout ref to the equivalent remote ref.
237
238 Returns:
239 A tuple of the remote ref's (common prefix, unique suffix), or None if it
240 doesn't appear to refer to a remote ref (e.g. it's a commit hash).
241 """
242 # TODO(mmoss): This is just a brute-force mapping based of the expected git
243 # config. It's a bit better than the even more brute-force replace('heads',
244 # ...), but could still be smarter (like maybe actually using values gleaned
245 # from the git config).
246 m = re.match('^(refs/(remotes/)?)?branch-heads/', ref or '')
247 if m:
248 return ('refs/remotes/branch-heads/', ref.replace(m.group(0), ''))
Edward Lemur9a5e3bd2019-04-02 23:37:45 +0000249
250 m = re.match('^((refs/)?remotes/)?%s/|(refs/)?heads/' % remote, ref or '')
251 if m:
252 return ('refs/remotes/%s/' % remote, ref.replace(m.group(0), ''))
253
254 return None
255
256 @staticmethod
257 def RemoteRefToRef(ref, remote):
258 assert remote, 'A remote must be given'
259 if not ref or not ref.startswith('refs/'):
260 return None
261 if not ref.startswith('refs/remotes/'):
262 return ref
263 if ref.startswith('refs/remotes/branch-heads/'):
264 return 'refs' + ref[len('refs/remotes'):]
265 if ref.startswith('refs/remotes/%s/' % remote):
266 return 'refs/heads' + ref[len('refs/remotes/%s' % remote):]
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000267 return None
268
269 @staticmethod
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000270 def GetUpstreamBranch(cwd):
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000271 """Gets the current branch's upstream branch."""
272 remote, upstream_branch = GIT.FetchUpstreamTuple(cwd)
maruel@chromium.orga630bd72010-04-29 23:32:34 +0000273 if remote != '.' and upstream_branch:
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000274 remote_ref = GIT.RefToRemoteRef(upstream_branch, remote)
275 if remote_ref:
276 upstream_branch = ''.join(remote_ref)
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000277 return upstream_branch
278
279 @staticmethod
Edward Lemurca7d8812018-07-24 17:42:45 +0000280 def IsAncestor(cwd, maybe_ancestor, ref):
281 """Verifies if |maybe_ancestor| is an ancestor of |ref|."""
282 try:
283 GIT.Capture(['merge-base', '--is-ancestor', maybe_ancestor, ref], cwd=cwd)
284 return True
285 except subprocess2.CalledProcessError:
286 return False
287
288 @staticmethod
Daniel Cheng7a1f04d2017-03-21 19:12:31 -0700289 def GetOldContents(cwd, filename, branch=None):
290 if not branch:
291 branch = GIT.GetUpstreamBranch(cwd)
Pierre-Antoine Manzagolfc1c6f42017-05-30 12:29:58 -0400292 if platform.system() == 'Windows':
293 # git show <sha>:<path> wants a posix path.
294 filename = filename.replace('\\', '/')
Daniel Cheng7a1f04d2017-03-21 19:12:31 -0700295 command = ['show', '%s:%s' % (branch, filename)]
Daniel Chengd67e7152017-04-13 01:21:03 -0700296 try:
297 return GIT.Capture(command, cwd=cwd, strip_out=False)
298 except subprocess2.CalledProcessError:
299 return ''
Daniel Cheng7a1f04d2017-03-21 19:12:31 -0700300
301 @staticmethod
maruel@chromium.org8ede00e2010-01-12 14:35:28 +0000302 def GenerateDiff(cwd, branch=None, branch_head='HEAD', full_move=False,
303 files=None):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000304 """Diffs against the upstream branch or optionally another branch.
305
306 full_move means that move or copy operations should completely recreate the
307 files, usually in the prospect to apply the patch for a try job."""
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000308 if not branch:
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000309 branch = GIT.GetUpstreamBranch(cwd)
Aaron Gablef4068aa2017-12-12 15:14:09 -0800310 command = ['-c', 'core.quotePath=false', 'diff',
311 '-p', '--no-color', '--no-prefix', '--no-ext-diff',
evan@chromium.org400f3e72010-05-19 14:23:36 +0000312 branch + "..." + branch_head]
mcgrathr@chromium.org9249f642013-06-03 21:36:18 +0000313 if full_move:
314 command.append('--no-renames')
315 else:
maruel@chromium.orga9371762009-12-22 18:27:38 +0000316 command.append('-C')
maruel@chromium.org8ede00e2010-01-12 14:35:28 +0000317 # TODO(maruel): --binary support.
318 if files:
319 command.append('--')
320 command.extend(files)
ilevy@chromium.org4380c802013-07-12 23:38:41 +0000321 diff = GIT.Capture(command, cwd=cwd, strip_out=False).splitlines(True)
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000322 for i in range(len(diff)):
323 # In the case of added files, replace /dev/null with the path to the
324 # file being added.
325 if diff[i].startswith('--- /dev/null'):
326 diff[i] = '--- %s' % diff[i+1][4:]
327 return ''.join(diff)
maruel@chromium.orgc78f2462009-11-21 01:20:57 +0000328
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000329 @staticmethod
maruel@chromium.org8ede00e2010-01-12 14:35:28 +0000330 def GetDifferentFiles(cwd, branch=None, branch_head='HEAD'):
331 """Returns the list of modified files between two branches."""
332 if not branch:
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000333 branch = GIT.GetUpstreamBranch(cwd)
Aaron Gablef4068aa2017-12-12 15:14:09 -0800334 command = ['-c', 'core.quotePath=false', 'diff',
335 '--name-only', branch + "..." + branch_head]
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000336 return GIT.Capture(command, cwd=cwd).splitlines(False)
maruel@chromium.org8ede00e2010-01-12 14:35:28 +0000337
338 @staticmethod
Edward Lemur98cfac12020-01-17 19:27:01 +0000339 def GetAllFiles(cwd):
340 """Returns the list of all files under revision control."""
341 command = ['-c', 'core.quotePath=false', 'ls-files', '--', '.']
342 return GIT.Capture(command, cwd=cwd).splitlines(False)
343
344 @staticmethod
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000345 def GetPatchName(cwd):
346 """Constructs a name for this patch."""
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000347 short_sha = GIT.Capture(['rev-parse', '--short=4', 'HEAD'], cwd=cwd)
maruel@chromium.org862ff8e2010-08-06 15:29:16 +0000348 return "%s#%s" % (GIT.GetBranch(cwd), short_sha)
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000349
350 @staticmethod
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000351 def GetCheckoutRoot(cwd):
maruel@chromium.org01d8c1d2010-01-07 01:56:59 +0000352 """Returns the top level directory of a git checkout as an absolute path.
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000353 """
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000354 root = GIT.Capture(['rev-parse', '--show-cdup'], cwd=cwd)
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000355 return os.path.abspath(os.path.join(cwd, root))
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000356
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000357 @staticmethod
nodir@chromium.orgead4c7e2014-04-03 01:01:06 +0000358 def GetGitDir(cwd):
359 return os.path.abspath(GIT.Capture(['rev-parse', '--git-dir'], cwd=cwd))
360
361 @staticmethod
362 def IsInsideWorkTree(cwd):
363 try:
364 return GIT.Capture(['rev-parse', '--is-inside-work-tree'], cwd=cwd)
365 except (OSError, subprocess2.CalledProcessError):
366 return False
367
368 @staticmethod
primiano@chromium.org1c127382015-02-17 11:15:40 +0000369 def IsDirectoryVersioned(cwd, relative_dir):
370 """Checks whether the given |relative_dir| is part of cwd's repo."""
371 return bool(GIT.Capture(['ls-tree', 'HEAD', relative_dir], cwd=cwd))
372
373 @staticmethod
374 def CleanupDir(cwd, relative_dir):
375 """Cleans up untracked file inside |relative_dir|."""
376 return bool(GIT.Capture(['clean', '-df', relative_dir], cwd=cwd))
377
378 @staticmethod
Edward Lemurd52edda2020-03-11 20:13:02 +0000379 def ResolveCommit(cwd, rev):
380 if sys.platform.startswith('win'):
381 # Windows .bat scripts use ^ as escape sequence, which means we have to
382 # escape it with itself for every .bat invocation.
383 needle = '%s^^{commit}' % rev
384 else:
385 needle = '%s^{commit}' % rev
386 try:
387 return GIT.Capture(['rev-parse', '--quiet', '--verify', needle], cwd=cwd)
388 except subprocess2.CalledProcessError:
389 return None
390
391 @staticmethod
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000392 def IsValidRevision(cwd, rev, sha_only=False):
393 """Verifies the revision is a proper git revision.
394
395 sha_only: Fail unless rev is a sha hash.
396 """
Edward Lemurd52edda2020-03-11 20:13:02 +0000397 sha = GIT.ResolveCommit(cwd, rev)
398 if sha is None:
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000399 return False
Edward Lemurd52edda2020-03-11 20:13:02 +0000400 if sha_only:
401 return sha == rev.lower()
402 return True
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000403
maruel@chromium.org36ac2392011-10-12 16:36:11 +0000404 @classmethod
405 def AssertVersion(cls, min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +0000406 """Asserts git's version is at least min_version."""
maruel@chromium.org36ac2392011-10-12 16:36:11 +0000407 if cls.current_version is None:
bashi@chromium.orgfcffd482012-02-24 01:47:00 +0000408 current_version = cls.Capture(['--version'], '.')
Edward Lesmescba46082020-03-25 00:29:06 +0000409 matched = re.search(r'version ([0-9\.]+)', current_version)
410 cls.current_version = matched.group(1)
411 current_version_list = list(map(only_int, cls.current_version.split('.')))
412 for min_ver in map(int, min_version.split('.')):
413 ver = current_version_list.pop(0)
414 if ver < min_ver:
415 return (False, cls.current_version)
416 elif ver > min_ver:
417 return (True, cls.current_version)
418 return (True, cls.current_version)