[email protected] | 7d65467 | 2012-01-05 19:07:23 | [diff] [blame] | 1 | # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [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 | |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 5 | """SCM-specific utility classes.""" |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 6 | |
[email protected] | 3c55d98 | 2010-05-06 14:25:44 | [diff] [blame] | 7 | import cStringIO |
[email protected] | fd9cbbb | 2010-01-08 23:04:03 | [diff] [blame] | 8 | import glob |
[email protected] | 07ab60e | 2011-02-08 21:54:00 | [diff] [blame] | 9 | import logging |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 10 | import os |
Pierre-Antoine Manzagol | fc1c6f4 | 2017-05-30 16:29:58 | [diff] [blame] | 11 | import platform |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 12 | import re |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 13 | import sys |
[email protected] | 4755b58 | 2013-04-18 21:38:40 | [diff] [blame] | 14 | import tempfile |
[email protected] | fd87617 | 2010-04-30 14:01:05 | [diff] [blame] | 15 | import time |
[email protected] | ade9c59 | 2011-04-07 15:59:11 | [diff] [blame] | 16 | from xml.etree import ElementTree |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 17 | |
| 18 | import gclient_utils |
[email protected] | 31cb48a | 2011-04-04 18:01:36 | [diff] [blame] | 19 | import subprocess2 |
| 20 | |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 21 | |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 22 | def ValidateEmail(email): |
[email protected] | 6e29d57 | 2010-06-04 17:32:20 | [diff] [blame] | 23 | return (re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) |
| 24 | is not None) |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 25 | |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 26 | |
[email protected] | fd9cbbb | 2010-01-08 23:04:03 | [diff] [blame] | 27 | def GetCasedPath(path): |
| 28 | """Elcheapos way to get the real path case on Windows.""" |
| 29 | if sys.platform.startswith('win') and os.path.exists(path): |
| 30 | # Reconstruct the path. |
| 31 | path = os.path.abspath(path) |
| 32 | paths = path.split('\\') |
| 33 | for i in range(len(paths)): |
| 34 | if i == 0: |
| 35 | # Skip drive letter. |
| 36 | continue |
| 37 | subpath = '\\'.join(paths[:i+1]) |
| 38 | prev = len('\\'.join(paths[:i])) |
| 39 | # glob.glob will return the cased path for the last item only. This is why |
| 40 | # we are calling it in a loop. Extract the data we want and put it back |
| 41 | # into the list. |
| 42 | paths[i] = glob.glob(subpath + '*')[0][prev+1:len(subpath)] |
| 43 | path = '\\'.join(paths) |
| 44 | return path |
| 45 | |
| 46 | |
[email protected] | 3c55d98 | 2010-05-06 14:25:44 | [diff] [blame] | 47 | def GenFakeDiff(filename): |
| 48 | """Generates a fake diff from a file.""" |
| 49 | file_content = gclient_utils.FileRead(filename, 'rb').splitlines(True) |
[email protected] | c6d170e | 2010-06-03 00:06:00 | [diff] [blame] | 50 | filename = filename.replace(os.sep, '/') |
[email protected] | 3c55d98 | 2010-05-06 14:25:44 | [diff] [blame] | 51 | nb_lines = len(file_content) |
| 52 | # We need to use / since patch on unix will fail otherwise. |
| 53 | data = cStringIO.StringIO() |
| 54 | data.write("Index: %s\n" % filename) |
| 55 | data.write('=' * 67 + '\n') |
| 56 | # Note: Should we use /dev/null instead? |
| 57 | data.write("--- %s\n" % filename) |
| 58 | data.write("+++ %s\n" % filename) |
| 59 | data.write("@@ -0,0 +1,%d @@\n" % nb_lines) |
| 60 | # Prepend '+' to every lines. |
| 61 | for line in file_content: |
| 62 | data.write('+') |
| 63 | data.write(line) |
| 64 | result = data.getvalue() |
| 65 | data.close() |
| 66 | return result |
| 67 | |
| 68 | |
[email protected] | 5c8c6de | 2011-03-18 16:20:18 | [diff] [blame] | 69 | def determine_scm(root): |
| 70 | """Similar to upload.py's version but much simpler. |
| 71 | |
Aaron Gable | 208db56 | 2016-12-21 22:46:36 | [diff] [blame] | 72 | Returns 'git' or None. |
[email protected] | 5c8c6de | 2011-03-18 16:20:18 | [diff] [blame] | 73 | """ |
Aaron Gable | 208db56 | 2016-12-21 22:46:36 | [diff] [blame] | 74 | if os.path.isdir(os.path.join(root, '.git')): |
[email protected] | 5c8c6de | 2011-03-18 16:20:18 | [diff] [blame] | 75 | return 'git' |
| 76 | else: |
[email protected] | c98c0c5 | 2011-04-06 13:39:43 | [diff] [blame] | 77 | try: |
[email protected] | 91def9b | 2011-09-14 16:28:07 | [diff] [blame] | 78 | subprocess2.check_call( |
[email protected] | 5c8c6de | 2011-03-18 16:20:18 | [diff] [blame] | 79 | ['git', 'rev-parse', '--show-cdup'], |
[email protected] | c98c0c5 | 2011-04-06 13:39:43 | [diff] [blame] | 80 | stdout=subprocess2.VOID, |
[email protected] | 87e6d33 | 2011-09-09 19:01:28 | [diff] [blame] | 81 | stderr=subprocess2.VOID, |
[email protected] | c98c0c5 | 2011-04-06 13:39:43 | [diff] [blame] | 82 | cwd=root) |
[email protected] | 5c8c6de | 2011-03-18 16:20:18 | [diff] [blame] | 83 | return 'git' |
[email protected] | c98c0c5 | 2011-04-06 13:39:43 | [diff] [blame] | 84 | except (OSError, subprocess2.CalledProcessError): |
[email protected] | 5c8c6de | 2011-03-18 16:20:18 | [diff] [blame] | 85 | return None |
| 86 | |
| 87 | |
[email protected] | 36ac239 | 2011-10-12 16:36:11 | [diff] [blame] | 88 | def only_int(val): |
| 89 | if val.isdigit(): |
| 90 | return int(val) |
| 91 | else: |
| 92 | return 0 |
| 93 | |
| 94 | |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 95 | class GIT(object): |
[email protected] | 36ac239 | 2011-10-12 16:36:11 | [diff] [blame] | 96 | current_version = None |
| 97 | |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 98 | @staticmethod |
[email protected] | 6d8115d | 2014-04-23 20:59:23 | [diff] [blame] | 99 | def ApplyEnvVars(kwargs): |
| 100 | env = kwargs.pop('env', None) or os.environ.copy() |
| 101 | # Don't prompt for passwords; just fail quickly and noisily. |
| 102 | # By default, git will use an interactive terminal prompt when a username/ |
| 103 | # password is needed. That shouldn't happen in the chromium workflow, |
| 104 | # and if it does, then gclient may hide the prompt in the midst of a flood |
| 105 | # of terminal spew. The only indication that something has gone wrong |
| 106 | # will be when gclient hangs unresponsively. Instead, we disable the |
| 107 | # password prompt and simply allow git to fail noisily. The error |
| 108 | # message produced by git will be copied to gclient's output. |
| 109 | env.setdefault('GIT_ASKPASS', 'true') |
| 110 | env.setdefault('SSH_ASKPASS', 'true') |
[email protected] | 82b91cd | 2013-07-09 06:33:41 | [diff] [blame] | 111 | # 'cat' is a magical git string that disables pagers on all platforms. |
[email protected] | 6d8115d | 2014-04-23 20:59:23 | [diff] [blame] | 112 | env.setdefault('GIT_PAGER', 'cat') |
| 113 | return env |
| 114 | |
| 115 | @staticmethod |
| 116 | def Capture(args, cwd, strip_out=True, **kwargs): |
| 117 | env = GIT.ApplyEnvVars(kwargs) |
[email protected] | 4380c80 | 2013-07-12 23:38:41 | [diff] [blame] | 118 | output = subprocess2.check_output( |
[email protected] | 82b91cd | 2013-07-09 06:33:41 | [diff] [blame] | 119 | ['git'] + args, |
[email protected] | 4380c80 | 2013-07-12 23:38:41 | [diff] [blame] | 120 | cwd=cwd, stderr=subprocess2.PIPE, env=env, **kwargs) |
| 121 | return output.strip() if strip_out else output |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 122 | |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 123 | @staticmethod |
[email protected] | 80a9ef1 | 2011-12-13 20:44:10 | [diff] [blame] | 124 | def CaptureStatus(files, cwd, upstream_branch): |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 125 | """Returns git status. |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 126 | |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 127 | @files can be a string (one file) or a list of files. |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 128 | |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 129 | Returns an array of (status, file) tuples.""" |
[email protected] | 786fb68 | 2010-06-02 15:16:23 | [diff] [blame] | 130 | if upstream_branch is None: |
[email protected] | 80a9ef1 | 2011-12-13 20:44:10 | [diff] [blame] | 131 | upstream_branch = GIT.GetUpstreamBranch(cwd) |
[email protected] | 786fb68 | 2010-06-02 15:16:23 | [diff] [blame] | 132 | if upstream_branch is None: |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 133 | raise gclient_utils.Error('Cannot determine upstream branch') |
Aaron Gable | 7817f02 | 2017-12-12 17:43:17 | [diff] [blame] | 134 | command = ['-c', 'core.quotePath=false', 'diff', |
| 135 | '--name-status', '--no-renames', '-r', '%s...' % upstream_branch] |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 136 | if not files: |
| 137 | pass |
| 138 | elif isinstance(files, basestring): |
| 139 | command.append(files) |
| 140 | else: |
| 141 | command.extend(files) |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 142 | status = GIT.Capture(command, cwd) |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 143 | results = [] |
| 144 | if status: |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 145 | for statusline in status.splitlines(): |
[email protected] | cc1614b | 2010-09-20 17:13:17 | [diff] [blame] | 146 | # 3-way merges can cause the status can be 'MMM' instead of 'M'. This |
| 147 | # can happen when the user has 2 local branches and he diffs between |
| 148 | # these 2 branches instead diffing to upstream. |
| 149 | m = re.match('^(\w)+\t(.+)$', statusline) |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 150 | if not m: |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 151 | raise gclient_utils.Error( |
| 152 | 'status currently unsupported: %s' % statusline) |
[email protected] | cc1614b | 2010-09-20 17:13:17 | [diff] [blame] | 153 | # Only grab the first letter. |
| 154 | results.append(('%s ' % m.group(1)[0], m.group(2))) |
[email protected] | 5aeb7dd | 2009-11-17 18:09:01 | [diff] [blame] | 155 | return results |
[email protected] | d5800f1 | 2009-11-12 20:03:43 | [diff] [blame] | 156 | |
[email protected] | c78f246 | 2009-11-21 01:20:57 | [diff] [blame] | 157 | @staticmethod |
[email protected] | ead4c7e | 2014-04-03 01:01:06 | [diff] [blame] | 158 | def IsWorkTreeDirty(cwd): |
| 159 | return GIT.Capture(['status', '-s'], cwd=cwd) != '' |
| 160 | |
| 161 | @staticmethod |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 162 | def GetEmail(cwd): |
[email protected] | c78f246 | 2009-11-21 01:20:57 | [diff] [blame] | 163 | """Retrieves the user email address if known.""" |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 164 | try: |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 165 | return GIT.Capture(['config', 'user.email'], cwd=cwd) |
[email protected] | da64d63 | 2011-09-08 17:41:15 | [diff] [blame] | 166 | except subprocess2.CalledProcessError: |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 167 | return '' |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 168 | |
| 169 | @staticmethod |
| 170 | def ShortBranchName(branch): |
| 171 | """Converts a name like 'refs/heads/foo' to just 'foo'.""" |
| 172 | return branch.replace('refs/heads/', '') |
| 173 | |
| 174 | @staticmethod |
| 175 | def GetBranchRef(cwd): |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 176 | """Returns the full branch reference, e.g. 'refs/heads/master'.""" |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 177 | return GIT.Capture(['symbolic-ref', 'HEAD'], cwd=cwd) |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 178 | |
| 179 | @staticmethod |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 180 | def GetBranch(cwd): |
| 181 | """Returns the short branch name, e.g. 'master'.""" |
[email protected] | c308a74 | 2009-12-22 18:29:33 | [diff] [blame] | 182 | return GIT.ShortBranchName(GIT.GetBranchRef(cwd)) |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 183 | |
| 184 | @staticmethod |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 185 | def FetchUpstreamTuple(cwd): |
| 186 | """Returns a tuple containg remote and remote ref, |
| 187 | e.g. 'origin', 'refs/heads/master' |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 188 | """ |
| 189 | remote = '.' |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 190 | branch = GIT.GetBranch(cwd) |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 191 | try: |
| 192 | upstream_branch = GIT.Capture( |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 193 | ['config', '--local', 'branch.%s.merge' % branch], cwd=cwd) |
[email protected] | da64d63 | 2011-09-08 17:41:15 | [diff] [blame] | 194 | except subprocess2.CalledProcessError: |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 195 | upstream_branch = None |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 196 | if upstream_branch: |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 197 | try: |
| 198 | remote = GIT.Capture( |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 199 | ['config', '--local', 'branch.%s.remote' % branch], cwd=cwd) |
[email protected] | da64d63 | 2011-09-08 17:41:15 | [diff] [blame] | 200 | except subprocess2.CalledProcessError: |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 201 | pass |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 202 | else: |
[email protected] | ade368c | 2011-03-01 08:57:50 | [diff] [blame] | 203 | try: |
| 204 | upstream_branch = GIT.Capture( |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 205 | ['config', '--local', 'rietveld.upstream-branch'], cwd=cwd) |
[email protected] | da64d63 | 2011-09-08 17:41:15 | [diff] [blame] | 206 | except subprocess2.CalledProcessError: |
[email protected] | ade368c | 2011-03-01 08:57:50 | [diff] [blame] | 207 | upstream_branch = None |
| 208 | if upstream_branch: |
| 209 | try: |
| 210 | remote = GIT.Capture( |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 211 | ['config', '--local', 'rietveld.upstream-remote'], cwd=cwd) |
[email protected] | da64d63 | 2011-09-08 17:41:15 | [diff] [blame] | 212 | except subprocess2.CalledProcessError: |
[email protected] | ade368c | 2011-03-01 08:57:50 | [diff] [blame] | 213 | pass |
[email protected] | 81e012c | 2010-04-29 16:07:24 | [diff] [blame] | 214 | else: |
Aaron Gable | 208db56 | 2016-12-21 22:46:36 | [diff] [blame] | 215 | # Else, try to guess the origin remote. |
| 216 | remote_branches = GIT.Capture(['branch', '-r'], cwd=cwd).split() |
| 217 | if 'origin/master' in remote_branches: |
| 218 | # Fall back on origin/master if it exits. |
| 219 | remote = 'origin' |
| 220 | upstream_branch = 'refs/heads/master' |
[email protected] | a630bd7 | 2010-04-29 23:32:34 | [diff] [blame] | 221 | else: |
Aaron Gable | 208db56 | 2016-12-21 22:46:36 | [diff] [blame] | 222 | # Give up. |
| 223 | remote = None |
| 224 | upstream_branch = None |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 225 | return remote, upstream_branch |
| 226 | |
| 227 | @staticmethod |
[email protected] | 6e7202b | 2014-09-09 18:23:39 | [diff] [blame] | 228 | def RefToRemoteRef(ref, remote=None): |
| 229 | """Convert a checkout ref to the equivalent remote ref. |
| 230 | |
| 231 | Returns: |
| 232 | A tuple of the remote ref's (common prefix, unique suffix), or None if it |
| 233 | doesn't appear to refer to a remote ref (e.g. it's a commit hash). |
| 234 | """ |
| 235 | # TODO(mmoss): This is just a brute-force mapping based of the expected git |
| 236 | # config. It's a bit better than the even more brute-force replace('heads', |
| 237 | # ...), but could still be smarter (like maybe actually using values gleaned |
| 238 | # from the git config). |
| 239 | m = re.match('^(refs/(remotes/)?)?branch-heads/', ref or '') |
| 240 | if m: |
| 241 | return ('refs/remotes/branch-heads/', ref.replace(m.group(0), '')) |
| 242 | if remote: |
| 243 | m = re.match('^((refs/)?remotes/)?%s/|(refs/)?heads/' % remote, ref or '') |
| 244 | if m: |
| 245 | return ('refs/remotes/%s/' % remote, ref.replace(m.group(0), '')) |
| 246 | return None |
| 247 | |
| 248 | @staticmethod |
[email protected] | 81e012c | 2010-04-29 16:07:24 | [diff] [blame] | 249 | def GetUpstreamBranch(cwd): |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 250 | """Gets the current branch's upstream branch.""" |
| 251 | remote, upstream_branch = GIT.FetchUpstreamTuple(cwd) |
[email protected] | a630bd7 | 2010-04-29 23:32:34 | [diff] [blame] | 252 | if remote != '.' and upstream_branch: |
[email protected] | 6e7202b | 2014-09-09 18:23:39 | [diff] [blame] | 253 | remote_ref = GIT.RefToRemoteRef(upstream_branch, remote) |
| 254 | if remote_ref: |
| 255 | upstream_branch = ''.join(remote_ref) |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 256 | return upstream_branch |
| 257 | |
| 258 | @staticmethod |
Daniel Cheng | 7a1f04d | 2017-03-22 02:12:31 | [diff] [blame] | 259 | def GetOldContents(cwd, filename, branch=None): |
| 260 | if not branch: |
| 261 | branch = GIT.GetUpstreamBranch(cwd) |
Pierre-Antoine Manzagol | fc1c6f4 | 2017-05-30 16:29:58 | [diff] [blame] | 262 | if platform.system() == 'Windows': |
| 263 | # git show <sha>:<path> wants a posix path. |
| 264 | filename = filename.replace('\\', '/') |
Daniel Cheng | 7a1f04d | 2017-03-22 02:12:31 | [diff] [blame] | 265 | command = ['show', '%s:%s' % (branch, filename)] |
Daniel Cheng | d67e715 | 2017-04-13 08:21:03 | [diff] [blame] | 266 | try: |
| 267 | return GIT.Capture(command, cwd=cwd, strip_out=False) |
| 268 | except subprocess2.CalledProcessError: |
| 269 | return '' |
Daniel Cheng | 7a1f04d | 2017-03-22 02:12:31 | [diff] [blame] | 270 | |
| 271 | @staticmethod |
[email protected] | 8ede00e | 2010-01-12 14:35:28 | [diff] [blame] | 272 | def GenerateDiff(cwd, branch=None, branch_head='HEAD', full_move=False, |
| 273 | files=None): |
[email protected] | a937176 | 2009-12-22 18:27:38 | [diff] [blame] | 274 | """Diffs against the upstream branch or optionally another branch. |
| 275 | |
| 276 | full_move means that move or copy operations should completely recreate the |
| 277 | files, usually in the prospect to apply the patch for a try job.""" |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 278 | if not branch: |
[email protected] | 81e012c | 2010-04-29 16:07:24 | [diff] [blame] | 279 | branch = GIT.GetUpstreamBranch(cwd) |
Aaron Gable | f4068aa | 2017-12-12 23:14:09 | [diff] [blame] | 280 | command = ['-c', 'core.quotePath=false', 'diff', |
| 281 | '-p', '--no-color', '--no-prefix', '--no-ext-diff', |
[email protected] | 400f3e7 | 2010-05-19 14:23:36 | [diff] [blame] | 282 | branch + "..." + branch_head] |
[email protected] | 9249f64 | 2013-06-03 21:36:18 | [diff] [blame] | 283 | if full_move: |
| 284 | command.append('--no-renames') |
| 285 | else: |
[email protected] | a937176 | 2009-12-22 18:27:38 | [diff] [blame] | 286 | command.append('-C') |
[email protected] | 8ede00e | 2010-01-12 14:35:28 | [diff] [blame] | 287 | # TODO(maruel): --binary support. |
| 288 | if files: |
| 289 | command.append('--') |
| 290 | command.extend(files) |
[email protected] | 4380c80 | 2013-07-12 23:38:41 | [diff] [blame] | 291 | diff = GIT.Capture(command, cwd=cwd, strip_out=False).splitlines(True) |
[email protected] | f2f9d55 | 2009-12-22 00:12:57 | [diff] [blame] | 292 | for i in range(len(diff)): |
| 293 | # In the case of added files, replace /dev/null with the path to the |
| 294 | # file being added. |
| 295 | if diff[i].startswith('--- /dev/null'): |
| 296 | diff[i] = '--- %s' % diff[i+1][4:] |
| 297 | return ''.join(diff) |
[email protected] | c78f246 | 2009-11-21 01:20:57 | [diff] [blame] | 298 | |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 299 | @staticmethod |
[email protected] | 8ede00e | 2010-01-12 14:35:28 | [diff] [blame] | 300 | def GetDifferentFiles(cwd, branch=None, branch_head='HEAD'): |
| 301 | """Returns the list of modified files between two branches.""" |
| 302 | if not branch: |
[email protected] | 81e012c | 2010-04-29 16:07:24 | [diff] [blame] | 303 | branch = GIT.GetUpstreamBranch(cwd) |
Aaron Gable | f4068aa | 2017-12-12 23:14:09 | [diff] [blame] | 304 | command = ['-c', 'core.quotePath=false', 'diff', |
| 305 | '--name-only', branch + "..." + branch_head] |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 306 | return GIT.Capture(command, cwd=cwd).splitlines(False) |
[email protected] | 8ede00e | 2010-01-12 14:35:28 | [diff] [blame] | 307 | |
| 308 | @staticmethod |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 309 | def GetPatchName(cwd): |
| 310 | """Constructs a name for this patch.""" |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 311 | short_sha = GIT.Capture(['rev-parse', '--short=4', 'HEAD'], cwd=cwd) |
[email protected] | 862ff8e | 2010-08-06 15:29:16 | [diff] [blame] | 312 | return "%s#%s" % (GIT.GetBranch(cwd), short_sha) |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 313 | |
| 314 | @staticmethod |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 315 | def GetCheckoutRoot(cwd): |
[email protected] | 01d8c1d | 2010-01-07 01:56:59 | [diff] [blame] | 316 | """Returns the top level directory of a git checkout as an absolute path. |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 317 | """ |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 318 | root = GIT.Capture(['rev-parse', '--show-cdup'], cwd=cwd) |
[email protected] | ad80e3b | 2010-09-09 14:18:28 | [diff] [blame] | 319 | return os.path.abspath(os.path.join(cwd, root)) |
[email protected] | b24a8e1 | 2009-12-22 13:45:48 | [diff] [blame] | 320 | |
[email protected] | e5d1e61 | 2011-12-19 19:49:19 | [diff] [blame] | 321 | @staticmethod |
[email protected] | ead4c7e | 2014-04-03 01:01:06 | [diff] [blame] | 322 | def GetGitDir(cwd): |
| 323 | return os.path.abspath(GIT.Capture(['rev-parse', '--git-dir'], cwd=cwd)) |
| 324 | |
| 325 | @staticmethod |
| 326 | def IsInsideWorkTree(cwd): |
| 327 | try: |
| 328 | return GIT.Capture(['rev-parse', '--is-inside-work-tree'], cwd=cwd) |
| 329 | except (OSError, subprocess2.CalledProcessError): |
| 330 | return False |
| 331 | |
| 332 | @staticmethod |
[email protected] | 1c12738 | 2015-02-17 11:15:40 | [diff] [blame] | 333 | def IsDirectoryVersioned(cwd, relative_dir): |
| 334 | """Checks whether the given |relative_dir| is part of cwd's repo.""" |
| 335 | return bool(GIT.Capture(['ls-tree', 'HEAD', relative_dir], cwd=cwd)) |
| 336 | |
| 337 | @staticmethod |
| 338 | def CleanupDir(cwd, relative_dir): |
| 339 | """Cleans up untracked file inside |relative_dir|.""" |
| 340 | return bool(GIT.Capture(['clean', '-df', relative_dir], cwd=cwd)) |
| 341 | |
| 342 | @staticmethod |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 343 | def IsValidRevision(cwd, rev, sha_only=False): |
| 344 | """Verifies the revision is a proper git revision. |
| 345 | |
| 346 | sha_only: Fail unless rev is a sha hash. |
| 347 | """ |
[email protected] | 8147386 | 2012-06-27 17:30:56 | [diff] [blame] | 348 | # 'git rev-parse foo' where foo is *any* 40 character hex string will return |
| 349 | # the string and return code 0. So strip one character to force 'git |
| 350 | # rev-parse' to do a hash table look-up and returns 128 if the hash is not |
| 351 | # present. |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 352 | lookup_rev = rev |
[email protected] | 8147386 | 2012-06-27 17:30:56 | [diff] [blame] | 353 | if re.match(r'^[0-9a-fA-F]{40}$', rev): |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 354 | lookup_rev = rev[:-1] |
[email protected] | e5d1e61 | 2011-12-19 19:49:19 | [diff] [blame] | 355 | try: |
[email protected] | 224ba24 | 2013-07-08 22:02:31 | [diff] [blame] | 356 | sha = GIT.Capture(['rev-parse', lookup_rev], cwd=cwd).lower() |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 357 | if lookup_rev != rev: |
| 358 | # Make sure we get the original 40 chars back. |
[email protected] | 6895317 | 2014-06-11 22:14:35 | [diff] [blame] | 359 | return rev.lower() == sha |
[email protected] | a41249c | 2013-07-03 00:09:12 | [diff] [blame] | 360 | if sha_only: |
[email protected] | 6895317 | 2014-06-11 22:14:35 | [diff] [blame] | 361 | return sha.startswith(rev.lower()) |
| 362 | return True |
[email protected] | e5d1e61 | 2011-12-19 19:49:19 | [diff] [blame] | 363 | except subprocess2.CalledProcessError: |
| 364 | return False |
| 365 | |
[email protected] | 36ac239 | 2011-10-12 16:36:11 | [diff] [blame] | 366 | @classmethod |
| 367 | def AssertVersion(cls, min_version): |
[email protected] | d0f854a | 2010-03-11 19:35:53 | [diff] [blame] | 368 | """Asserts git's version is at least min_version.""" |
[email protected] | 36ac239 | 2011-10-12 16:36:11 | [diff] [blame] | 369 | if cls.current_version is None: |
[email protected] | fcffd48 | 2012-02-24 01:47:00 | [diff] [blame] | 370 | current_version = cls.Capture(['--version'], '.') |
| 371 | matched = re.search(r'version ([0-9\.]+)', current_version) |
| 372 | cls.current_version = matched.group(1) |
[email protected] | 36ac239 | 2011-10-12 16:36:11 | [diff] [blame] | 373 | current_version_list = map(only_int, cls.current_version.split('.')) |
[email protected] | d0f854a | 2010-03-11 19:35:53 | [diff] [blame] | 374 | for min_ver in map(int, min_version.split('.')): |
| 375 | ver = current_version_list.pop(0) |
| 376 | if ver < min_ver: |
[email protected] | 36ac239 | 2011-10-12 16:36:11 | [diff] [blame] | 377 | return (False, cls.current_version) |
[email protected] | d0f854a | 2010-03-11 19:35:53 | [diff] [blame] | 378 | elif ver > min_ver: |
[email protected] | 36ac239 | 2011-10-12 16:36:11 | [diff] [blame] | 379 | return (True, cls.current_version) |
| 380 | return (True, cls.current_version) |