maruel@chromium.org | 7d65467 | 2012-01-05 19:07:23 +0000 | [diff] [blame] | 1 | # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
maruel@chromium.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 5 | """SCM-specific utility classes.""" |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 6 | |
maruel@chromium.org | 3c55d98 | 2010-05-06 14:25:44 +0000 | [diff] [blame] | 7 | import cStringIO |
maruel@chromium.org | fd9cbbb | 2010-01-08 23:04:03 +0000 | [diff] [blame] | 8 | import glob |
maruel@chromium.org | 07ab60e | 2011-02-08 21:54:00 +0000 | [diff] [blame] | 9 | import logging |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 10 | import os |
| 11 | import re |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 12 | import sys |
pkasting@chromium.org | 4755b58 | 2013-04-18 21:38:40 +0000 | [diff] [blame] | 13 | import tempfile |
maruel@chromium.org | fd87617 | 2010-04-30 14:01:05 +0000 | [diff] [blame] | 14 | import time |
maruel@chromium.org | ade9c59 | 2011-04-07 15:59:11 +0000 | [diff] [blame] | 15 | from xml.etree import ElementTree |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 16 | |
| 17 | import gclient_utils |
maruel@chromium.org | 31cb48a | 2011-04-04 18:01:36 +0000 | [diff] [blame] | 18 | import subprocess2 |
| 19 | |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 20 | |
maruel@chromium.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 21 | def ValidateEmail(email): |
maruel@chromium.org | 6e29d57 | 2010-06-04 17:32:20 +0000 | [diff] [blame] | 22 | return (re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) |
| 23 | is not None) |
maruel@chromium.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 24 | |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 25 | |
maruel@chromium.org | fd9cbbb | 2010-01-08 23:04:03 +0000 | [diff] [blame] | 26 | def 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.org | 3c55d98 | 2010-05-06 14:25:44 +0000 | [diff] [blame] | 46 | def GenFakeDiff(filename): |
| 47 | """Generates a fake diff from a file.""" |
| 48 | file_content = gclient_utils.FileRead(filename, 'rb').splitlines(True) |
maruel@chromium.org | c6d170e | 2010-06-03 00:06:00 +0000 | [diff] [blame] | 49 | filename = filename.replace(os.sep, '/') |
maruel@chromium.org | 3c55d98 | 2010-05-06 14:25:44 +0000 | [diff] [blame] | 50 | 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.org | 5c8c6de | 2011-03-18 16:20:18 +0000 | [diff] [blame] | 68 | def determine_scm(root): |
| 69 | """Similar to upload.py's version but much simpler. |
| 70 | |
Aaron Gable | 208db56 | 2016-12-21 14:46:36 -0800 | [diff] [blame] | 71 | Returns 'git' or None. |
maruel@chromium.org | 5c8c6de | 2011-03-18 16:20:18 +0000 | [diff] [blame] | 72 | """ |
Aaron Gable | 208db56 | 2016-12-21 14:46:36 -0800 | [diff] [blame] | 73 | if os.path.isdir(os.path.join(root, '.git')): |
maruel@chromium.org | 5c8c6de | 2011-03-18 16:20:18 +0000 | [diff] [blame] | 74 | return 'git' |
| 75 | else: |
maruel@chromium.org | c98c0c5 | 2011-04-06 13:39:43 +0000 | [diff] [blame] | 76 | try: |
maruel@chromium.org | 91def9b | 2011-09-14 16:28:07 +0000 | [diff] [blame] | 77 | subprocess2.check_call( |
maruel@chromium.org | 5c8c6de | 2011-03-18 16:20:18 +0000 | [diff] [blame] | 78 | ['git', 'rev-parse', '--show-cdup'], |
maruel@chromium.org | c98c0c5 | 2011-04-06 13:39:43 +0000 | [diff] [blame] | 79 | stdout=subprocess2.VOID, |
maruel@chromium.org | 87e6d33 | 2011-09-09 19:01:28 +0000 | [diff] [blame] | 80 | stderr=subprocess2.VOID, |
maruel@chromium.org | c98c0c5 | 2011-04-06 13:39:43 +0000 | [diff] [blame] | 81 | cwd=root) |
maruel@chromium.org | 5c8c6de | 2011-03-18 16:20:18 +0000 | [diff] [blame] | 82 | return 'git' |
maruel@chromium.org | c98c0c5 | 2011-04-06 13:39:43 +0000 | [diff] [blame] | 83 | except (OSError, subprocess2.CalledProcessError): |
maruel@chromium.org | 5c8c6de | 2011-03-18 16:20:18 +0000 | [diff] [blame] | 84 | return None |
| 85 | |
| 86 | |
maruel@chromium.org | 36ac239 | 2011-10-12 16:36:11 +0000 | [diff] [blame] | 87 | def only_int(val): |
| 88 | if val.isdigit(): |
| 89 | return int(val) |
| 90 | else: |
| 91 | return 0 |
| 92 | |
| 93 | |
maruel@chromium.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 94 | class GIT(object): |
maruel@chromium.org | 36ac239 | 2011-10-12 16:36:11 +0000 | [diff] [blame] | 95 | current_version = None |
| 96 | |
maruel@chromium.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 97 | @staticmethod |
szager@chromium.org | 6d8115d | 2014-04-23 20:59:23 +0000 | [diff] [blame] | 98 | 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.com | 82b91cd | 2013-07-09 06:33:41 +0000 | [diff] [blame] | 110 | # 'cat' is a magical git string that disables pagers on all platforms. |
szager@chromium.org | 6d8115d | 2014-04-23 20:59:23 +0000 | [diff] [blame] | 111 | 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.org | 4380c80 | 2013-07-12 23:38:41 +0000 | [diff] [blame] | 117 | output = subprocess2.check_output( |
bratell@opera.com | 82b91cd | 2013-07-09 06:33:41 +0000 | [diff] [blame] | 118 | ['git'] + args, |
ilevy@chromium.org | 4380c80 | 2013-07-12 23:38:41 +0000 | [diff] [blame] | 119 | cwd=cwd, stderr=subprocess2.PIPE, env=env, **kwargs) |
| 120 | return output.strip() if strip_out else output |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 121 | |
maruel@chromium.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 122 | @staticmethod |
maruel@chromium.org | 80a9ef1 | 2011-12-13 20:44:10 +0000 | [diff] [blame] | 123 | def CaptureStatus(files, cwd, upstream_branch): |
maruel@chromium.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 124 | """Returns git status. |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 125 | |
maruel@chromium.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 126 | @files can be a string (one file) or a list of files. |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 127 | |
maruel@chromium.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 128 | Returns an array of (status, file) tuples.""" |
msb@chromium.org | 786fb68 | 2010-06-02 15:16:23 +0000 | [diff] [blame] | 129 | if upstream_branch is None: |
maruel@chromium.org | 80a9ef1 | 2011-12-13 20:44:10 +0000 | [diff] [blame] | 130 | upstream_branch = GIT.GetUpstreamBranch(cwd) |
msb@chromium.org | 786fb68 | 2010-06-02 15:16:23 +0000 | [diff] [blame] | 131 | if upstream_branch is None: |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 132 | raise gclient_utils.Error('Cannot determine upstream branch') |
mcgrathr@chromium.org | 9249f64 | 2013-06-03 21:36:18 +0000 | [diff] [blame] | 133 | command = ['diff', '--name-status', '--no-renames', |
| 134 | '-r', '%s...' % upstream_branch] |
maruel@chromium.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 135 | if not files: |
| 136 | pass |
| 137 | elif isinstance(files, basestring): |
| 138 | command.append(files) |
| 139 | else: |
| 140 | command.extend(files) |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 141 | status = GIT.Capture(command, cwd) |
maruel@chromium.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 142 | results = [] |
| 143 | if status: |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 144 | for statusline in status.splitlines(): |
maruel@chromium.org | cc1614b | 2010-09-20 17:13:17 +0000 | [diff] [blame] | 145 | # 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.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 149 | if not m: |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 150 | raise gclient_utils.Error( |
| 151 | 'status currently unsupported: %s' % statusline) |
maruel@chromium.org | cc1614b | 2010-09-20 17:13:17 +0000 | [diff] [blame] | 152 | # Only grab the first letter. |
| 153 | results.append(('%s ' % m.group(1)[0], m.group(2))) |
maruel@chromium.org | 5aeb7dd | 2009-11-17 18:09:01 +0000 | [diff] [blame] | 154 | return results |
maruel@chromium.org | d5800f1 | 2009-11-12 20:03:43 +0000 | [diff] [blame] | 155 | |
maruel@chromium.org | c78f246 | 2009-11-21 01:20:57 +0000 | [diff] [blame] | 156 | @staticmethod |
nodir@chromium.org | ead4c7e | 2014-04-03 01:01:06 +0000 | [diff] [blame] | 157 | def IsWorkTreeDirty(cwd): |
| 158 | return GIT.Capture(['status', '-s'], cwd=cwd) != '' |
| 159 | |
| 160 | @staticmethod |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 161 | def GetEmail(cwd): |
maruel@chromium.org | c78f246 | 2009-11-21 01:20:57 +0000 | [diff] [blame] | 162 | """Retrieves the user email address if known.""" |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 163 | try: |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 164 | return GIT.Capture(['config', 'user.email'], cwd=cwd) |
maruel@chromium.org | da64d63 | 2011-09-08 17:41:15 +0000 | [diff] [blame] | 165 | except subprocess2.CalledProcessError: |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 166 | return '' |
maruel@chromium.org | f2f9d55 | 2009-12-22 00:12:57 +0000 | [diff] [blame] | 167 | |
| 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.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 175 | """Returns the full branch reference, e.g. 'refs/heads/master'.""" |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 176 | return GIT.Capture(['symbolic-ref', 'HEAD'], cwd=cwd) |
maruel@chromium.org | f2f9d55 | 2009-12-22 00:12:57 +0000 | [diff] [blame] | 177 | |
| 178 | @staticmethod |
maruel@chromium.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 179 | def GetBranch(cwd): |
| 180 | """Returns the short branch name, e.g. 'master'.""" |
maruel@chromium.org | c308a74 | 2009-12-22 18:29:33 +0000 | [diff] [blame] | 181 | return GIT.ShortBranchName(GIT.GetBranchRef(cwd)) |
maruel@chromium.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 182 | |
| 183 | @staticmethod |
maruel@chromium.org | f2f9d55 | 2009-12-22 00:12:57 +0000 | [diff] [blame] | 184 | def FetchUpstreamTuple(cwd): |
| 185 | """Returns a tuple containg remote and remote ref, |
| 186 | e.g. 'origin', 'refs/heads/master' |
| 187 | """ |
| 188 | remote = '.' |
maruel@chromium.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 189 | branch = GIT.GetBranch(cwd) |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 190 | try: |
| 191 | upstream_branch = GIT.Capture( |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 192 | ['config', '--local', 'branch.%s.merge' % branch], cwd=cwd) |
maruel@chromium.org | da64d63 | 2011-09-08 17:41:15 +0000 | [diff] [blame] | 193 | except subprocess2.CalledProcessError: |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 194 | upstream_branch = None |
maruel@chromium.org | f2f9d55 | 2009-12-22 00:12:57 +0000 | [diff] [blame] | 195 | if upstream_branch: |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 196 | try: |
| 197 | remote = GIT.Capture( |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 198 | ['config', '--local', 'branch.%s.remote' % branch], cwd=cwd) |
maruel@chromium.org | da64d63 | 2011-09-08 17:41:15 +0000 | [diff] [blame] | 199 | except subprocess2.CalledProcessError: |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 200 | pass |
maruel@chromium.org | f2f9d55 | 2009-12-22 00:12:57 +0000 | [diff] [blame] | 201 | else: |
bauerb@chromium.org | ade368c | 2011-03-01 08:57:50 +0000 | [diff] [blame] | 202 | try: |
| 203 | upstream_branch = GIT.Capture( |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 204 | ['config', '--local', 'rietveld.upstream-branch'], cwd=cwd) |
maruel@chromium.org | da64d63 | 2011-09-08 17:41:15 +0000 | [diff] [blame] | 205 | except subprocess2.CalledProcessError: |
bauerb@chromium.org | ade368c | 2011-03-01 08:57:50 +0000 | [diff] [blame] | 206 | upstream_branch = None |
| 207 | if upstream_branch: |
| 208 | try: |
| 209 | remote = GIT.Capture( |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 210 | ['config', '--local', 'rietveld.upstream-remote'], cwd=cwd) |
maruel@chromium.org | da64d63 | 2011-09-08 17:41:15 +0000 | [diff] [blame] | 211 | except subprocess2.CalledProcessError: |
bauerb@chromium.org | ade368c | 2011-03-01 08:57:50 +0000 | [diff] [blame] | 212 | pass |
maruel@chromium.org | 81e012c | 2010-04-29 16:07:24 +0000 | [diff] [blame] | 213 | else: |
Aaron Gable | 208db56 | 2016-12-21 14:46:36 -0800 | [diff] [blame] | 214 | # 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.org | a630bd7 | 2010-04-29 23:32:34 +0000 | [diff] [blame] | 220 | else: |
Aaron Gable | 208db56 | 2016-12-21 14:46:36 -0800 | [diff] [blame] | 221 | # Give up. |
| 222 | remote = None |
| 223 | upstream_branch = None |
maruel@chromium.org | f2f9d55 | 2009-12-22 00:12:57 +0000 | [diff] [blame] | 224 | return remote, upstream_branch |
| 225 | |
| 226 | @staticmethod |
mmoss@chromium.org | 6e7202b | 2014-09-09 18:23:39 +0000 | [diff] [blame] | 227 | 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.org | 81e012c | 2010-04-29 16:07:24 +0000 | [diff] [blame] | 248 | def GetUpstreamBranch(cwd): |
maruel@chromium.org | f2f9d55 | 2009-12-22 00:12:57 +0000 | [diff] [blame] | 249 | """Gets the current branch's upstream branch.""" |
| 250 | remote, upstream_branch = GIT.FetchUpstreamTuple(cwd) |
maruel@chromium.org | a630bd7 | 2010-04-29 23:32:34 +0000 | [diff] [blame] | 251 | if remote != '.' and upstream_branch: |
mmoss@chromium.org | 6e7202b | 2014-09-09 18:23:39 +0000 | [diff] [blame] | 252 | remote_ref = GIT.RefToRemoteRef(upstream_branch, remote) |
| 253 | if remote_ref: |
| 254 | upstream_branch = ''.join(remote_ref) |
maruel@chromium.org | f2f9d55 | 2009-12-22 00:12:57 +0000 | [diff] [blame] | 255 | return upstream_branch |
| 256 | |
| 257 | @staticmethod |
maruel@chromium.org | 8ede00e | 2010-01-12 14:35:28 +0000 | [diff] [blame] | 258 | def GenerateDiff(cwd, branch=None, branch_head='HEAD', full_move=False, |
| 259 | files=None): |
maruel@chromium.org | a937176 | 2009-12-22 18:27:38 +0000 | [diff] [blame] | 260 | """Diffs against the upstream branch or optionally another branch. |
| 261 | |
| 262 | full_move means that move or copy operations should completely recreate the |
| 263 | files, usually in the prospect to apply the patch for a try job.""" |
maruel@chromium.org | f2f9d55 | 2009-12-22 00:12:57 +0000 | [diff] [blame] | 264 | if not branch: |
maruel@chromium.org | 81e012c | 2010-04-29 16:07:24 +0000 | [diff] [blame] | 265 | branch = GIT.GetUpstreamBranch(cwd) |
scottbyer@chromium.org | 3316733 | 2012-02-23 21:15:30 +0000 | [diff] [blame] | 266 | command = ['diff', '-p', '--no-color', '--no-prefix', '--no-ext-diff', |
evan@chromium.org | 400f3e7 | 2010-05-19 14:23:36 +0000 | [diff] [blame] | 267 | branch + "..." + branch_head] |
mcgrathr@chromium.org | 9249f64 | 2013-06-03 21:36:18 +0000 | [diff] [blame] | 268 | if full_move: |
| 269 | command.append('--no-renames') |
| 270 | else: |
maruel@chromium.org | a937176 | 2009-12-22 18:27:38 +0000 | [diff] [blame] | 271 | command.append('-C') |
maruel@chromium.org | 8ede00e | 2010-01-12 14:35:28 +0000 | [diff] [blame] | 272 | # TODO(maruel): --binary support. |
| 273 | if files: |
| 274 | command.append('--') |
| 275 | command.extend(files) |
ilevy@chromium.org | 4380c80 | 2013-07-12 23:38:41 +0000 | [diff] [blame] | 276 | diff = GIT.Capture(command, cwd=cwd, strip_out=False).splitlines(True) |
maruel@chromium.org | f2f9d55 | 2009-12-22 00:12:57 +0000 | [diff] [blame] | 277 | for i in range(len(diff)): |
| 278 | # In the case of added files, replace /dev/null with the path to the |
| 279 | # file being added. |
| 280 | if diff[i].startswith('--- /dev/null'): |
| 281 | diff[i] = '--- %s' % diff[i+1][4:] |
| 282 | return ''.join(diff) |
maruel@chromium.org | c78f246 | 2009-11-21 01:20:57 +0000 | [diff] [blame] | 283 | |
maruel@chromium.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 284 | @staticmethod |
maruel@chromium.org | 8ede00e | 2010-01-12 14:35:28 +0000 | [diff] [blame] | 285 | def GetDifferentFiles(cwd, branch=None, branch_head='HEAD'): |
| 286 | """Returns the list of modified files between two branches.""" |
| 287 | if not branch: |
maruel@chromium.org | 81e012c | 2010-04-29 16:07:24 +0000 | [diff] [blame] | 288 | branch = GIT.GetUpstreamBranch(cwd) |
bauerb@chromium.org | 838f0f2 | 2010-04-09 17:02:50 +0000 | [diff] [blame] | 289 | command = ['diff', '--name-only', branch + "..." + branch_head] |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 290 | return GIT.Capture(command, cwd=cwd).splitlines(False) |
maruel@chromium.org | 8ede00e | 2010-01-12 14:35:28 +0000 | [diff] [blame] | 291 | |
| 292 | @staticmethod |
maruel@chromium.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 293 | def GetPatchName(cwd): |
| 294 | """Constructs a name for this patch.""" |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 295 | short_sha = GIT.Capture(['rev-parse', '--short=4', 'HEAD'], cwd=cwd) |
maruel@chromium.org | 862ff8e | 2010-08-06 15:29:16 +0000 | [diff] [blame] | 296 | return "%s#%s" % (GIT.GetBranch(cwd), short_sha) |
maruel@chromium.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 297 | |
| 298 | @staticmethod |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 299 | def GetCheckoutRoot(cwd): |
maruel@chromium.org | 01d8c1d | 2010-01-07 01:56:59 +0000 | [diff] [blame] | 300 | """Returns the top level directory of a git checkout as an absolute path. |
maruel@chromium.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 301 | """ |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 302 | root = GIT.Capture(['rev-parse', '--show-cdup'], cwd=cwd) |
maruel@chromium.org | ad80e3b | 2010-09-09 14:18:28 +0000 | [diff] [blame] | 303 | return os.path.abspath(os.path.join(cwd, root)) |
maruel@chromium.org | b24a8e1 | 2009-12-22 13:45:48 +0000 | [diff] [blame] | 304 | |
dbeam@chromium.org | e5d1e61 | 2011-12-19 19:49:19 +0000 | [diff] [blame] | 305 | @staticmethod |
nodir@chromium.org | ead4c7e | 2014-04-03 01:01:06 +0000 | [diff] [blame] | 306 | def GetGitDir(cwd): |
| 307 | return os.path.abspath(GIT.Capture(['rev-parse', '--git-dir'], cwd=cwd)) |
| 308 | |
| 309 | @staticmethod |
| 310 | def IsInsideWorkTree(cwd): |
| 311 | try: |
| 312 | return GIT.Capture(['rev-parse', '--is-inside-work-tree'], cwd=cwd) |
| 313 | except (OSError, subprocess2.CalledProcessError): |
| 314 | return False |
| 315 | |
| 316 | @staticmethod |
primiano@chromium.org | 1c12738 | 2015-02-17 11:15:40 +0000 | [diff] [blame] | 317 | def IsDirectoryVersioned(cwd, relative_dir): |
| 318 | """Checks whether the given |relative_dir| is part of cwd's repo.""" |
| 319 | return bool(GIT.Capture(['ls-tree', 'HEAD', relative_dir], cwd=cwd)) |
| 320 | |
| 321 | @staticmethod |
| 322 | def CleanupDir(cwd, relative_dir): |
| 323 | """Cleans up untracked file inside |relative_dir|.""" |
| 324 | return bool(GIT.Capture(['clean', '-df', relative_dir], cwd=cwd)) |
| 325 | |
| 326 | @staticmethod |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 327 | def IsValidRevision(cwd, rev, sha_only=False): |
| 328 | """Verifies the revision is a proper git revision. |
| 329 | |
| 330 | sha_only: Fail unless rev is a sha hash. |
| 331 | """ |
maruel@chromium.org | 8147386 | 2012-06-27 17:30:56 +0000 | [diff] [blame] | 332 | # 'git rev-parse foo' where foo is *any* 40 character hex string will return |
| 333 | # the string and return code 0. So strip one character to force 'git |
| 334 | # rev-parse' to do a hash table look-up and returns 128 if the hash is not |
| 335 | # present. |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 336 | lookup_rev = rev |
maruel@chromium.org | 8147386 | 2012-06-27 17:30:56 +0000 | [diff] [blame] | 337 | if re.match(r'^[0-9a-fA-F]{40}$', rev): |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 338 | lookup_rev = rev[:-1] |
dbeam@chromium.org | e5d1e61 | 2011-12-19 19:49:19 +0000 | [diff] [blame] | 339 | try: |
ilevy@chromium.org | 224ba24 | 2013-07-08 22:02:31 +0000 | [diff] [blame] | 340 | sha = GIT.Capture(['rev-parse', lookup_rev], cwd=cwd).lower() |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 341 | if lookup_rev != rev: |
| 342 | # Make sure we get the original 40 chars back. |
hinoka@google.com | 6895317 | 2014-06-11 22:14:35 +0000 | [diff] [blame] | 343 | return rev.lower() == sha |
ilevy@chromium.org | a41249c | 2013-07-03 00:09:12 +0000 | [diff] [blame] | 344 | if sha_only: |
hinoka@google.com | 6895317 | 2014-06-11 22:14:35 +0000 | [diff] [blame] | 345 | return sha.startswith(rev.lower()) |
| 346 | return True |
dbeam@chromium.org | e5d1e61 | 2011-12-19 19:49:19 +0000 | [diff] [blame] | 347 | except subprocess2.CalledProcessError: |
| 348 | return False |
| 349 | |
maruel@chromium.org | 36ac239 | 2011-10-12 16:36:11 +0000 | [diff] [blame] | 350 | @classmethod |
| 351 | def AssertVersion(cls, min_version): |
maruel@chromium.org | d0f854a | 2010-03-11 19:35:53 +0000 | [diff] [blame] | 352 | """Asserts git's version is at least min_version.""" |
maruel@chromium.org | 36ac239 | 2011-10-12 16:36:11 +0000 | [diff] [blame] | 353 | if cls.current_version is None: |
bashi@chromium.org | fcffd48 | 2012-02-24 01:47:00 +0000 | [diff] [blame] | 354 | current_version = cls.Capture(['--version'], '.') |
| 355 | matched = re.search(r'version ([0-9\.]+)', current_version) |
| 356 | cls.current_version = matched.group(1) |
maruel@chromium.org | 36ac239 | 2011-10-12 16:36:11 +0000 | [diff] [blame] | 357 | current_version_list = map(only_int, cls.current_version.split('.')) |
maruel@chromium.org | d0f854a | 2010-03-11 19:35:53 +0000 | [diff] [blame] | 358 | for min_ver in map(int, min_version.split('.')): |
| 359 | ver = current_version_list.pop(0) |
| 360 | if ver < min_ver: |
maruel@chromium.org | 36ac239 | 2011-10-12 16:36:11 +0000 | [diff] [blame] | 361 | return (False, cls.current_version) |
maruel@chromium.org | d0f854a | 2010-03-11 19:35:53 +0000 | [diff] [blame] | 362 | elif ver > min_ver: |
maruel@chromium.org | 36ac239 | 2011-10-12 16:36:11 +0000 | [diff] [blame] | 363 | return (True, cls.current_version) |
| 364 | return (True, cls.current_version) |