blob: 6132845565fe466f64d7900feacf7d6a8e5ca9cb [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.org3c55d982010-05-06 14:25:44 +00007import cStringIO
maruel@chromium.orgfd9cbbb2010-01-08 23:04:03 +00008import glob
maruel@chromium.org07ab60e2011-02-08 21:54:00 +00009import logging
maruel@chromium.orgd5800f12009-11-12 20:03:43 +000010import os
11import re
maruel@chromium.orgd5800f12009-11-12 20:03:43 +000012import sys
pkasting@chromium.org4755b582013-04-18 21:38:40 +000013import tempfile
maruel@chromium.orgfd876172010-04-30 14:01:05 +000014import time
maruel@chromium.orgade9c592011-04-07 15:59:11 +000015from xml.etree import ElementTree
maruel@chromium.orgd5800f12009-11-12 20:03:43 +000016
17import gclient_utils
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000018import subprocess2
19
maruel@chromium.orgd5800f12009-11-12 20:03:43 +000020
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +000021def ValidateEmail(email):
maruel@chromium.org6e29d572010-06-04 17:32:20 +000022 return (re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email)
23 is not None)
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +000024
maruel@chromium.orgd5800f12009-11-12 20:03:43 +000025
maruel@chromium.orgfd9cbbb2010-01-08 23:04:03 +000026def GetCasedPath(path):
27 """Elcheapos way to get the real path case on Windows."""
28 if sys.platform.startswith('win') and os.path.exists(path):
29 # Reconstruct the path.
30 path = os.path.abspath(path)
31 paths = path.split('\\')
32 for i in range(len(paths)):
33 if i == 0:
34 # Skip drive letter.
35 continue
36 subpath = '\\'.join(paths[:i+1])
37 prev = len('\\'.join(paths[:i]))
38 # glob.glob will return the cased path for the last item only. This is why
39 # we are calling it in a loop. Extract the data we want and put it back
40 # into the list.
41 paths[i] = glob.glob(subpath + '*')[0][prev+1:len(subpath)]
42 path = '\\'.join(paths)
43 return path
44
45
maruel@chromium.org3c55d982010-05-06 14:25:44 +000046def GenFakeDiff(filename):
47 """Generates a fake diff from a file."""
48 file_content = gclient_utils.FileRead(filename, 'rb').splitlines(True)
maruel@chromium.orgc6d170e2010-06-03 00:06:00 +000049 filename = filename.replace(os.sep, '/')
maruel@chromium.org3c55d982010-05-06 14:25:44 +000050 nb_lines = len(file_content)
51 # We need to use / since patch on unix will fail otherwise.
52 data = cStringIO.StringIO()
53 data.write("Index: %s\n" % filename)
54 data.write('=' * 67 + '\n')
55 # Note: Should we use /dev/null instead?
56 data.write("--- %s\n" % filename)
57 data.write("+++ %s\n" % filename)
58 data.write("@@ -0,0 +1,%d @@\n" % nb_lines)
59 # Prepend '+' to every lines.
60 for line in file_content:
61 data.write('+')
62 data.write(line)
63 result = data.getvalue()
64 data.close()
65 return result
66
67
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000068def determine_scm(root):
69 """Similar to upload.py's version but much simpler.
70
Aaron Gable208db562016-12-21 14:46:36 -080071 Returns 'git' or None.
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000072 """
Aaron Gable208db562016-12-21 14:46:36 -080073 if os.path.isdir(os.path.join(root, '.git')):
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000074 return 'git'
75 else:
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000076 try:
maruel@chromium.org91def9b2011-09-14 16:28:07 +000077 subprocess2.check_call(
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000078 ['git', 'rev-parse', '--show-cdup'],
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000079 stdout=subprocess2.VOID,
maruel@chromium.org87e6d332011-09-09 19:01:28 +000080 stderr=subprocess2.VOID,
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000081 cwd=root)
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000082 return 'git'
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000083 except (OSError, subprocess2.CalledProcessError):
maruel@chromium.org5c8c6de2011-03-18 16:20:18 +000084 return None
85
86
maruel@chromium.org36ac2392011-10-12 16:36:11 +000087def only_int(val):
88 if val.isdigit():
89 return int(val)
90 else:
91 return 0
92
93
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000094class GIT(object):
maruel@chromium.org36ac2392011-10-12 16:36:11 +000095 current_version = None
96
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000097 @staticmethod
szager@chromium.org6d8115d2014-04-23 20:59:23 +000098 def ApplyEnvVars(kwargs):
99 env = kwargs.pop('env', None) or os.environ.copy()
100 # Don't prompt for passwords; just fail quickly and noisily.
101 # By default, git will use an interactive terminal prompt when a username/
102 # password is needed. That shouldn't happen in the chromium workflow,
103 # and if it does, then gclient may hide the prompt in the midst of a flood
104 # of terminal spew. The only indication that something has gone wrong
105 # will be when gclient hangs unresponsively. Instead, we disable the
106 # password prompt and simply allow git to fail noisily. The error
107 # message produced by git will be copied to gclient's output.
108 env.setdefault('GIT_ASKPASS', 'true')
109 env.setdefault('SSH_ASKPASS', 'true')
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000110 # 'cat' is a magical git string that disables pagers on all platforms.
szager@chromium.org6d8115d2014-04-23 20:59:23 +0000111 env.setdefault('GIT_PAGER', 'cat')
112 return env
113
114 @staticmethod
115 def Capture(args, cwd, strip_out=True, **kwargs):
116 env = GIT.ApplyEnvVars(kwargs)
ilevy@chromium.org4380c802013-07-12 23:38:41 +0000117 output = subprocess2.check_output(
bratell@opera.com82b91cd2013-07-09 06:33:41 +0000118 ['git'] + args,
ilevy@chromium.org4380c802013-07-12 23:38:41 +0000119 cwd=cwd, stderr=subprocess2.PIPE, env=env, **kwargs)
120 return output.strip() if strip_out else output
maruel@chromium.orgd5800f12009-11-12 20:03:43 +0000121
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000122 @staticmethod
maruel@chromium.org80a9ef12011-12-13 20:44:10 +0000123 def CaptureStatus(files, cwd, upstream_branch):
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000124 """Returns git status.
maruel@chromium.orgd5800f12009-11-12 20:03:43 +0000125
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000126 @files can be a string (one file) or a list of files.
maruel@chromium.orgd5800f12009-11-12 20:03:43 +0000127
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000128 Returns an array of (status, file) tuples."""
msb@chromium.org786fb682010-06-02 15:16:23 +0000129 if upstream_branch is None:
maruel@chromium.org80a9ef12011-12-13 20:44:10 +0000130 upstream_branch = GIT.GetUpstreamBranch(cwd)
msb@chromium.org786fb682010-06-02 15:16:23 +0000131 if upstream_branch is None:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000132 raise gclient_utils.Error('Cannot determine upstream branch')
mcgrathr@chromium.org9249f642013-06-03 21:36:18 +0000133 command = ['diff', '--name-status', '--no-renames',
134 '-r', '%s...' % upstream_branch]
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000135 if not files:
136 pass
137 elif isinstance(files, basestring):
138 command.append(files)
139 else:
140 command.extend(files)
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000141 status = GIT.Capture(command, cwd)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000142 results = []
143 if status:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000144 for statusline in status.splitlines():
maruel@chromium.orgcc1614b2010-09-20 17:13:17 +0000145 # 3-way merges can cause the status can be 'MMM' instead of 'M'. This
146 # can happen when the user has 2 local branches and he diffs between
147 # these 2 branches instead diffing to upstream.
148 m = re.match('^(\w)+\t(.+)$', statusline)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000149 if not m:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000150 raise gclient_utils.Error(
151 'status currently unsupported: %s' % statusline)
maruel@chromium.orgcc1614b2010-09-20 17:13:17 +0000152 # Only grab the first letter.
153 results.append(('%s ' % m.group(1)[0], m.group(2)))
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000154 return results
maruel@chromium.orgd5800f12009-11-12 20:03:43 +0000155
maruel@chromium.orgc78f2462009-11-21 01:20:57 +0000156 @staticmethod
nodir@chromium.orgead4c7e2014-04-03 01:01:06 +0000157 def IsWorkTreeDirty(cwd):
158 return GIT.Capture(['status', '-s'], cwd=cwd) != ''
159
160 @staticmethod
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000161 def GetEmail(cwd):
maruel@chromium.orgc78f2462009-11-21 01:20:57 +0000162 """Retrieves the user email address if known."""
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000163 try:
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000164 return GIT.Capture(['config', 'user.email'], cwd=cwd)
maruel@chromium.orgda64d632011-09-08 17:41:15 +0000165 except subprocess2.CalledProcessError:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000166 return ''
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000167
168 @staticmethod
169 def ShortBranchName(branch):
170 """Converts a name like 'refs/heads/foo' to just 'foo'."""
171 return branch.replace('refs/heads/', '')
172
173 @staticmethod
174 def GetBranchRef(cwd):
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000175 """Returns the full branch reference, e.g. 'refs/heads/master'."""
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000176 return GIT.Capture(['symbolic-ref', 'HEAD'], cwd=cwd)
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000177
178 @staticmethod
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000179 def GetBranch(cwd):
180 """Returns the short branch name, e.g. 'master'."""
maruel@chromium.orgc308a742009-12-22 18:29:33 +0000181 return GIT.ShortBranchName(GIT.GetBranchRef(cwd))
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000182
183 @staticmethod
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000184 def FetchUpstreamTuple(cwd):
185 """Returns a tuple containg remote and remote ref,
186 e.g. 'origin', 'refs/heads/master'
187 """
188 remote = '.'
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000189 branch = GIT.GetBranch(cwd)
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000190 try:
191 upstream_branch = GIT.Capture(
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000192 ['config', '--local', 'branch.%s.merge' % branch], cwd=cwd)
maruel@chromium.orgda64d632011-09-08 17:41:15 +0000193 except subprocess2.CalledProcessError:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000194 upstream_branch = None
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000195 if upstream_branch:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000196 try:
197 remote = GIT.Capture(
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000198 ['config', '--local', 'branch.%s.remote' % branch], cwd=cwd)
maruel@chromium.orgda64d632011-09-08 17:41:15 +0000199 except subprocess2.CalledProcessError:
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000200 pass
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000201 else:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000202 try:
203 upstream_branch = GIT.Capture(
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000204 ['config', '--local', 'rietveld.upstream-branch'], cwd=cwd)
maruel@chromium.orgda64d632011-09-08 17:41:15 +0000205 except subprocess2.CalledProcessError:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000206 upstream_branch = None
207 if upstream_branch:
208 try:
209 remote = GIT.Capture(
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000210 ['config', '--local', 'rietveld.upstream-remote'], cwd=cwd)
maruel@chromium.orgda64d632011-09-08 17:41:15 +0000211 except subprocess2.CalledProcessError:
bauerb@chromium.orgade368c2011-03-01 08:57:50 +0000212 pass
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000213 else:
Aaron Gable208db562016-12-21 14:46:36 -0800214 # Else, try to guess the origin remote.
215 remote_branches = GIT.Capture(['branch', '-r'], cwd=cwd).split()
216 if 'origin/master' in remote_branches:
217 # Fall back on origin/master if it exits.
218 remote = 'origin'
219 upstream_branch = 'refs/heads/master'
maruel@chromium.orga630bd72010-04-29 23:32:34 +0000220 else:
Aaron Gable208db562016-12-21 14:46:36 -0800221 # Give up.
222 remote = None
223 upstream_branch = None
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000224 return remote, upstream_branch
225
226 @staticmethod
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000227 def RefToRemoteRef(ref, remote=None):
228 """Convert a checkout ref to the equivalent remote ref.
229
230 Returns:
231 A tuple of the remote ref's (common prefix, unique suffix), or None if it
232 doesn't appear to refer to a remote ref (e.g. it's a commit hash).
233 """
234 # TODO(mmoss): This is just a brute-force mapping based of the expected git
235 # config. It's a bit better than the even more brute-force replace('heads',
236 # ...), but could still be smarter (like maybe actually using values gleaned
237 # from the git config).
238 m = re.match('^(refs/(remotes/)?)?branch-heads/', ref or '')
239 if m:
240 return ('refs/remotes/branch-heads/', ref.replace(m.group(0), ''))
241 if remote:
242 m = re.match('^((refs/)?remotes/)?%s/|(refs/)?heads/' % remote, ref or '')
243 if m:
244 return ('refs/remotes/%s/' % remote, ref.replace(m.group(0), ''))
245 return None
246
247 @staticmethod
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000248 def GetUpstreamBranch(cwd):
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000249 """Gets the current branch's upstream branch."""
250 remote, upstream_branch = GIT.FetchUpstreamTuple(cwd)
maruel@chromium.orga630bd72010-04-29 23:32:34 +0000251 if remote != '.' and upstream_branch:
mmoss@chromium.org6e7202b2014-09-09 18:23:39 +0000252 remote_ref = GIT.RefToRemoteRef(upstream_branch, remote)
253 if remote_ref:
254 upstream_branch = ''.join(remote_ref)
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000255 return upstream_branch
256
257 @staticmethod
Daniel Cheng7a1f04d2017-03-21 19:12:31 -0700258 def GetOldContents(cwd, filename, branch=None):
259 if not branch:
260 branch = GIT.GetUpstreamBranch(cwd)
261 command = ['show', '%s:%s' % (branch, filename)]
262 return GIT.Capture(command, cwd=cwd, strip_out=False)
263
264 @staticmethod
maruel@chromium.org8ede00e2010-01-12 14:35:28 +0000265 def GenerateDiff(cwd, branch=None, branch_head='HEAD', full_move=False,
266 files=None):
maruel@chromium.orga9371762009-12-22 18:27:38 +0000267 """Diffs against the upstream branch or optionally another branch.
268
269 full_move means that move or copy operations should completely recreate the
270 files, usually in the prospect to apply the patch for a try job."""
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000271 if not branch:
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000272 branch = GIT.GetUpstreamBranch(cwd)
scottbyer@chromium.org33167332012-02-23 21:15:30 +0000273 command = ['diff', '-p', '--no-color', '--no-prefix', '--no-ext-diff',
evan@chromium.org400f3e72010-05-19 14:23:36 +0000274 branch + "..." + branch_head]
mcgrathr@chromium.org9249f642013-06-03 21:36:18 +0000275 if full_move:
276 command.append('--no-renames')
277 else:
maruel@chromium.orga9371762009-12-22 18:27:38 +0000278 command.append('-C')
maruel@chromium.org8ede00e2010-01-12 14:35:28 +0000279 # TODO(maruel): --binary support.
280 if files:
281 command.append('--')
282 command.extend(files)
ilevy@chromium.org4380c802013-07-12 23:38:41 +0000283 diff = GIT.Capture(command, cwd=cwd, strip_out=False).splitlines(True)
maruel@chromium.orgf2f9d552009-12-22 00:12:57 +0000284 for i in range(len(diff)):
285 # In the case of added files, replace /dev/null with the path to the
286 # file being added.
287 if diff[i].startswith('--- /dev/null'):
288 diff[i] = '--- %s' % diff[i+1][4:]
289 return ''.join(diff)
maruel@chromium.orgc78f2462009-11-21 01:20:57 +0000290
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000291 @staticmethod
maruel@chromium.org8ede00e2010-01-12 14:35:28 +0000292 def GetDifferentFiles(cwd, branch=None, branch_head='HEAD'):
293 """Returns the list of modified files between two branches."""
294 if not branch:
maruel@chromium.org81e012c2010-04-29 16:07:24 +0000295 branch = GIT.GetUpstreamBranch(cwd)
bauerb@chromium.org838f0f22010-04-09 17:02:50 +0000296 command = ['diff', '--name-only', branch + "..." + branch_head]
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000297 return GIT.Capture(command, cwd=cwd).splitlines(False)
maruel@chromium.org8ede00e2010-01-12 14:35:28 +0000298
299 @staticmethod
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000300 def GetPatchName(cwd):
301 """Constructs a name for this patch."""
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000302 short_sha = GIT.Capture(['rev-parse', '--short=4', 'HEAD'], cwd=cwd)
maruel@chromium.org862ff8e2010-08-06 15:29:16 +0000303 return "%s#%s" % (GIT.GetBranch(cwd), short_sha)
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000304
305 @staticmethod
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000306 def GetCheckoutRoot(cwd):
maruel@chromium.org01d8c1d2010-01-07 01:56:59 +0000307 """Returns the top level directory of a git checkout as an absolute path.
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000308 """
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000309 root = GIT.Capture(['rev-parse', '--show-cdup'], cwd=cwd)
maruel@chromium.orgad80e3b2010-09-09 14:18:28 +0000310 return os.path.abspath(os.path.join(cwd, root))
maruel@chromium.orgb24a8e12009-12-22 13:45:48 +0000311
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000312 @staticmethod
nodir@chromium.orgead4c7e2014-04-03 01:01:06 +0000313 def GetGitDir(cwd):
314 return os.path.abspath(GIT.Capture(['rev-parse', '--git-dir'], cwd=cwd))
315
316 @staticmethod
317 def IsInsideWorkTree(cwd):
318 try:
319 return GIT.Capture(['rev-parse', '--is-inside-work-tree'], cwd=cwd)
320 except (OSError, subprocess2.CalledProcessError):
321 return False
322
323 @staticmethod
primiano@chromium.org1c127382015-02-17 11:15:40 +0000324 def IsDirectoryVersioned(cwd, relative_dir):
325 """Checks whether the given |relative_dir| is part of cwd's repo."""
326 return bool(GIT.Capture(['ls-tree', 'HEAD', relative_dir], cwd=cwd))
327
328 @staticmethod
329 def CleanupDir(cwd, relative_dir):
330 """Cleans up untracked file inside |relative_dir|."""
331 return bool(GIT.Capture(['clean', '-df', relative_dir], cwd=cwd))
332
333 @staticmethod
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000334 def IsValidRevision(cwd, rev, sha_only=False):
335 """Verifies the revision is a proper git revision.
336
337 sha_only: Fail unless rev is a sha hash.
338 """
maruel@chromium.org81473862012-06-27 17:30:56 +0000339 # 'git rev-parse foo' where foo is *any* 40 character hex string will return
340 # the string and return code 0. So strip one character to force 'git
341 # rev-parse' to do a hash table look-up and returns 128 if the hash is not
342 # present.
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000343 lookup_rev = rev
maruel@chromium.org81473862012-06-27 17:30:56 +0000344 if re.match(r'^[0-9a-fA-F]{40}$', rev):
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000345 lookup_rev = rev[:-1]
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000346 try:
ilevy@chromium.org224ba242013-07-08 22:02:31 +0000347 sha = GIT.Capture(['rev-parse', lookup_rev], cwd=cwd).lower()
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000348 if lookup_rev != rev:
349 # Make sure we get the original 40 chars back.
hinoka@google.com68953172014-06-11 22:14:35 +0000350 return rev.lower() == sha
ilevy@chromium.orga41249c2013-07-03 00:09:12 +0000351 if sha_only:
hinoka@google.com68953172014-06-11 22:14:35 +0000352 return sha.startswith(rev.lower())
353 return True
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000354 except subprocess2.CalledProcessError:
355 return False
356
maruel@chromium.org36ac2392011-10-12 16:36:11 +0000357 @classmethod
358 def AssertVersion(cls, min_version):
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +0000359 """Asserts git's version is at least min_version."""
maruel@chromium.org36ac2392011-10-12 16:36:11 +0000360 if cls.current_version is None:
bashi@chromium.orgfcffd482012-02-24 01:47:00 +0000361 current_version = cls.Capture(['--version'], '.')
362 matched = re.search(r'version ([0-9\.]+)', current_version)
363 cls.current_version = matched.group(1)
maruel@chromium.org36ac2392011-10-12 16:36:11 +0000364 current_version_list = map(only_int, cls.current_version.split('.'))
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +0000365 for min_ver in map(int, min_version.split('.')):
366 ver = current_version_list.pop(0)
367 if ver < min_ver:
maruel@chromium.org36ac2392011-10-12 16:36:11 +0000368 return (False, cls.current_version)
maruel@chromium.orgd0f854a2010-03-11 19:35:53 +0000369 elif ver > min_ver:
maruel@chromium.org36ac2392011-10-12 16:36:11 +0000370 return (True, cls.current_version)
371 return (True, cls.current_version)