| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 """Wrapper for trychange.py for git checkout.""" | 5 """Wrapper for trychange.py for git checkout.""" |
| 6 | 6 |
| 7 import getpass | |
| 8 import optparse | |
| 9 import os | |
| 10 import re | |
| 11 import subprocess | |
| 12 import sys | 7 import sys |
| 13 import tempfile | |
| 14 import traceback | |
| 15 import urllib | |
| 16 | 8 |
| 17 import breakpad | 9 try: |
| 10 import breakpad |
| 11 except ImportError: |
| 12 pass |
| 18 | 13 |
| 14 from scm import GIT |
| 19 import trychange | 15 import trychange |
| 20 | 16 |
| 21 | 17 |
| 22 def Backquote(cmd, cwd=None): | |
| 23 """Like running `cmd` in a shell script.""" | |
| 24 return subprocess.Popen(cmd, | |
| 25 cwd=cwd, | |
| 26 stdout=subprocess.PIPE).communicate()[0].strip() | |
| 27 | |
| 28 | |
| 29 def GetTryServerConfig(): | |
| 30 """Returns the dictionary of try server options or None if they | |
| 31 cannot be found.""" | |
| 32 script_path = 'tools/tryserver/tryserver.py' | |
| 33 root_dir = Backquote(['git', 'rev-parse', '--show-cdup']) | |
| 34 try: | |
| 35 script_file = open(os.path.join(root_dir, script_path)) | |
| 36 except IOError: | |
| 37 return None | |
| 38 locals = {} | |
| 39 try: | |
| 40 exec(script_file, locals) | |
| 41 except Exception, e: | |
| 42 return None | |
| 43 return locals | |
| 44 | |
| 45 | |
| 46 def GetBranchName(working_dir=None): | |
| 47 """Return name of current git branch.""" | |
| 48 branch = Backquote(['git', 'symbolic-ref', 'HEAD'], working_dir) | |
| 49 if not branch.startswith('refs/heads/'): | |
| 50 raise "Couldn't figure out branch name" | |
| 51 branch = branch[len('refs/heads/'):] | |
| 52 return branch | |
| 53 | |
| 54 | |
| 55 def GetPatchName(working_dir=None): | |
| 56 """Construct a name for this patch.""" | |
| 57 short_sha = Backquote(['git', 'rev-parse', '--short=4', 'HEAD'], working_dir) | |
| 58 return GetBranchName() + '-' + short_sha | |
| 59 | |
| 60 | |
| 61 def GetRietveldIssueNumber(): | 18 def GetRietveldIssueNumber(): |
| 62 return Backquote(['git', 'config', | 19 return GIT.Capture( |
| 63 'branch.%s.rietveldissue' % GetBranchName()]) | 20 ['config', 'branch.%s.rietveldissue' % GIT.GetBranch(None)], |
| 21 error_ok=True) |
| 64 | 22 |
| 65 | 23 |
| 66 def GetRietveldPatchsetNumber(): | 24 def GetRietveldPatchsetNumber(): |
| 67 return Backquote(['git', 'config', | 25 return GIT.Capture( |
| 68 'branch.%s.rietveldpatchset' % GetBranchName()]) | 26 ['config', 'branch.%s.rietveldpatchset' % GIT.GetBranch(None)], |
| 69 | 27 error_ok=True) |
| 70 def GetSubRepWorkingDir(sub_rep_path): | |
| 71 """Computes the path to the sub repository""" | |
| 72 if sub_rep_path: | |
| 73 root_dir = os.path.abspath(Backquote(['git', 'rev-parse', '--show-cdup'])) | |
| 74 return os.path.join(root_dir, sub_rep_path) | |
| 75 return None | |
| 76 | |
| 77 def GetMungedDiff(branch, prefix, sub_rep_path): | |
| 78 """Get the diff we'll send to the try server. We munge paths to match svn. | |
| 79 We add the prefix that the try bot is expecting. If sub_rep_path is | |
| 80 provided, diff will be calculated in the sub repository. | |
| 81 We also return the list of files in this diff, without munged paths.""" | |
| 82 # Make the following changes: | |
| 83 # - Prepend "src/" (or some other prefix) to paths as svn is expecting | |
| 84 # - In the case of added files, replace /dev/null with the path to the file | |
| 85 # being added. | |
| 86 | |
| 87 cwd = GetSubRepWorkingDir(sub_rep_path) | |
| 88 | |
| 89 output = [] | |
| 90 if not branch: | |
| 91 # Try to guess the upstream branch. | |
| 92 branch = Backquote(['git', 'cl', 'upstream'], cwd) | |
| 93 command = ['git', 'diff-tree', '-p'] | |
| 94 | |
| 95 new_cwd = None | |
| 96 if not sub_rep_path: | |
| 97 command.extend(['--no-prefix']) | |
| 98 else: | |
| 99 # Append / | |
| 100 sub_rep_path = os.path.join(sub_rep_path, '') | |
| 101 # Add the right prefix | |
| 102 command.extend(['--src-prefix=' + sub_rep_path]) | |
| 103 command.extend(['--dst-prefix=' + sub_rep_path]) | |
| 104 | |
| 105 command.extend([branch, 'HEAD']) | |
| 106 | |
| 107 # Run diff tree | |
| 108 diff = subprocess.Popen(command, | |
| 109 stdout=subprocess.PIPE, | |
| 110 cwd=cwd).stdout.readlines() | |
| 111 # Replace --- /dev/null with --- <new file name> | |
| 112 for i in range(len(diff)): | |
| 113 line = diff[i] | |
| 114 if line.startswith('--- /dev/null'): | |
| 115 line = '--- %s' % diff[i+1][4:] | |
| 116 output.append(line) | |
| 117 diff = output | |
| 118 | |
| 119 # Add root prefix | |
| 120 output = [] | |
| 121 file_set = set() | |
| 122 for line in diff: | |
| 123 if line.startswith('--- ') or line.startswith('+++ '): | |
| 124 filename = line[4:] | |
| 125 line = line[0:4] + os.path.join(prefix, filename) | |
| 126 file_set.add(filename.rstrip('\r\n')) | |
| 127 output.append(line) | |
| 128 | |
| 129 munged_diff = ''.join(output) | |
| 130 if len(munged_diff.strip()) == 0: | |
| 131 raise Exception("Patch was empty, did you give the right remote branch?") | |
| 132 | |
| 133 return (munged_diff, list(file_set)) | |
| 134 | |
| 135 def OneRepositoryDiff(diff_file, patch_names, branch, prefix, sub_rep_path): | |
| 136 """Computes a diff for one git repository at a given path against a given | |
| 137 branch. Writes the diff into diff_file and appends a name to the | |
| 138 patch_names list. | |
| 139 | |
| 140 Returns the list of files in the diff.""" | |
| 141 | |
| 142 (diff, file_list) = GetMungedDiff(branch, prefix, sub_rep_path) | |
| 143 | |
| 144 # Write the diff out | |
| 145 diff_file.write(diff) | |
| 146 | |
| 147 # Add patch name to list of patches | |
| 148 patch_name = GetPatchName(GetSubRepWorkingDir(sub_rep_path)) | |
| 149 patch_names.extend([patch_name]) | |
| 150 return file_list | |
| 151 | |
| 152 | |
| 153 def ValidEmail(email): | |
| 154 return re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) | |
| 155 | |
| 156 | |
| 157 def GetEmail(): | |
| 158 email = Backquote(['git', 'config', 'user.email']) | |
| 159 runmsg = "Try: git config user.email <EMAIL>" | |
| 160 assert ValidEmail(email), "Email '%s' is not valid. %s" % (email, runmsg) | |
| 161 return email | |
| 162 | |
| 163 | |
| 164 def TryChange(args, file_list): | |
| 165 """Put a patch on the try server.""" | |
| 166 trychange.TryChange(args, file_list, False) | |
| 167 | 28 |
| 168 | 29 |
| 169 if __name__ == '__main__': | 30 if __name__ == '__main__': |
| 170 parser = optparse.OptionParser( | 31 args = sys.argv[:] |
| 171 usage='git try [options] [branch]', | 32 patchset = GetRietveldPatchsetNumber() |
| 172 description='Upload the current diff of branch...HEAD to the try server.') | 33 if patchset: |
| 173 parser.add_option("-b", "--bot", action="append", | |
| 174 help="Force the use of a specific build slave (eg mac, " | |
| 175 "win, or linux)") | |
| 176 parser.add_option("-c", "--clobber", action="store_true", | |
| 177 help="Make the try run use be a clobber build") | |
| 178 parser.add_option("-r", "--revision", | |
| 179 help="Specify the SVN base revision to use") | |
| 180 parser.add_option("--root", default="src", metavar="PATH", | |
| 181 help="Specify the root prefix that is prepended to paths " | |
| 182 "in the patch") | |
| 183 parser.add_option("--dry_run", action="store_true", | |
| 184 help="Print the diff but don't send it to the try bots") | |
| 185 parser.add_option("--sub_rep", nargs=2, action="append", default=[], | |
| 186 metavar="PATH BRANCH", | |
| 187 help="Specify a path to a git sub-repository and a branch " | |
| 188 "to diff with in order to simultanously try changes " | |
| 189 "in multiple git repositories. Option may be " | |
| 190 "specified multiple times.") | |
| 191 parser.add_option("--webkit", metavar="BRANCH", | |
| 192 help="Specify webkit branch. Syntactic sugar for " | |
| 193 "--sub_rep third_party/WebKit/ <branch>") | |
| 194 | |
| 195 (options, args) = parser.parse_args(sys.argv) | |
| 196 | |
| 197 if options.webkit: | |
| 198 options.sub_rep.extend([('third_party/WebKit/', options.webkit)]) | |
| 199 | |
| 200 branch = None | |
| 201 if len(args) > 1: | |
| 202 branch = args[1] | |
| 203 patch_names = [] | |
| 204 | |
| 205 # Dump all diffs into one diff file. | |
| 206 diff_file = tempfile.NamedTemporaryFile() | |
| 207 | |
| 208 # Calculate diff for main git repository. | |
| 209 file_list = OneRepositoryDiff(diff_file, patch_names, branch, options.root, | |
| 210 None) | |
| 211 | |
| 212 # Calculate diff for each extra git repository. | |
| 213 for path_and_branch in options.sub_rep: | |
| 214 file_list.extend(OneRepositoryDiff(diff_file, | |
| 215 patch_names, | |
| 216 path_and_branch[1], | |
| 217 options.root, | |
| 218 path_and_branch[0])) | |
| 219 # Make diff file ready for reading. | |
| 220 diff_file.flush() | |
| 221 | |
| 222 # Concatenate patch names | |
| 223 # Prepare args for TryChange | |
| 224 email = GetEmail() | |
| 225 user = email.partition('@')[0] | |
| 226 args = [ | |
| 227 '-u', user, | |
| 228 '-e', email, | |
| 229 '-n', '-'.join(patch_names), | |
| 230 '--diff', diff_file.name, | |
| 231 ] | |
| 232 | |
| 233 # Send to try server via HTTP if we can parse the config, otherwise | |
| 234 # upload via SVN. | |
| 235 config = GetTryServerConfig() | |
| 236 if config is not None: | |
| 237 sendmsg = "Sending %s using HTTP..." % '-'.join(patch_names) | |
| 238 args.extend(['--use_http']) | |
| 239 if config['try_server_http_host'] is not None: | |
| 240 args.extend(['--host', config['try_server_http_host']]) | |
| 241 if config['try_server_http_port'] is not None: | |
| 242 args.extend(['--port', config['try_server_http_port']]) | |
| 243 | |
| 244 else: | |
| 245 print "Could not get server config -- if you're within Google, " | |
| 246 print "do you have have src-internal checked out?" | |
| 247 sendmsg = "Sending %s using SVN..." % '-'.join(patch_names) | |
| 248 args.extend([ | |
| 249 '--use_svn', '--svn_repo', | |
| 250 'svn://svn.chromium.org/chrome-try/try', | |
| 251 ]) | |
| 252 | |
| 253 if options.bot: | |
| 254 for bot in options.bot: | |
| 255 args.extend(['--bot', bot]) | |
| 256 if options.clobber: | |
| 257 args.append('--clobber') | |
| 258 if options.revision: | |
| 259 args.extend(['-r', options.revision]) | |
| 260 if GetRietveldPatchsetNumber(): | |
| 261 args.extend([ | 34 args.extend([ |
| 262 '--issue', GetRietveldIssueNumber(), | 35 '--issue', GetRietveldIssueNumber(), |
| 263 '--patchset', GetRietveldPatchsetNumber(), | 36 '--patchset', patchset, |
| 264 ]) | 37 ]) |
| 265 | 38 sys.exit(trychange.TryChange(args, [], False, 'git-try')) |
| 266 if options.dry_run: | |
| 267 print open(diff_file.name, 'r').read() | |
| 268 exit(0) | |
| 269 | |
| 270 print sendmsg | |
| 271 TryChange(args, file_list) | |
| OLD | NEW |