| #!/usr/bin/python |
| # Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import getpass |
| import optparse |
| import os |
| import subprocess |
| import tempfile |
| import traceback |
| import urllib |
| import sys |
| import re |
| import trychange |
| |
| |
| def Backquote(cmd, cwd=None): |
| """Like running `cmd` in a shell script.""" |
| return subprocess.Popen(cmd, |
| cwd=cwd, |
| stdout=subprocess.PIPE).communicate()[0].strip() |
| |
| |
| def GetTryServerConfig(): |
| """Returns the dictionary of try server options or None if they |
| cannot be found.""" |
| script_path = 'tools/tryserver/tryserver.py' |
| root_dir = Backquote(['git', 'rev-parse', '--show-cdup']) |
| try: |
| script_file = open(os.path.join(root_dir, script_path)) |
| except IOError: |
| return None |
| locals = {} |
| try: |
| exec(script_file, locals) |
| except Exception, e: |
| return None |
| return locals |
| |
| |
| def GetBranchName(working_dir=None): |
| """Return name of current git branch.""" |
| branch = Backquote(['git', 'symbolic-ref', 'HEAD'], working_dir) |
| if not branch.startswith('refs/heads/'): |
| raise "Couldn't figure out branch name" |
| branch = branch[len('refs/heads/'):] |
| return branch |
| |
| |
| def GetPatchName(working_dir=None): |
| """Construct a name for this patch.""" |
| short_sha = Backquote(['git', 'rev-parse', '--short=4', 'HEAD'], working_dir) |
| return GetBranchName() + '-' + short_sha |
| |
| |
| def GetRietveldIssueNumber(): |
| return Backquote(['git', 'config', |
| 'branch.%s.rietveldissue' % GetBranchName()]) |
| |
| |
| def GetRietveldPatchsetNumber(): |
| return Backquote(['git', 'config', |
| 'branch.%s.rietveldpatchset' % GetBranchName()]) |
| |
| def GetSubRepWorkingDir(sub_rep_path): |
| """Computes the path to the sub repository""" |
| if sub_rep_path: |
| root_dir = os.path.abspath(Backquote(['git', 'rev-parse', '--show-cdup'])) |
| return os.path.join(root_dir, sub_rep_path) |
| return None |
| |
| def GetMungedDiff(branch, prefix, sub_rep_path): |
| """Get the diff we'll send to the try server. We munge paths to match svn. |
| We add the prefix that the try bot is expecting. If sub_rep_path is |
| provided, diff will be calculated in the sub repository.""" |
| # Make the following changes: |
| # - Prepend "src/" (or some other prefix) to paths as svn is expecting |
| # - In the case of added files, replace /dev/null with the path to the file |
| # being added. |
| |
| cwd = GetSubRepWorkingDir(sub_rep_path) |
| |
| output = [] |
| if not branch: |
| # Try to guess the upstream branch. |
| branch = Backquote(['git', 'cl', 'upstream'], cwd) |
| command = ['git', 'diff-tree', '-p'] |
| |
| new_cwd = None |
| if not sub_rep_path: |
| command.extend(['--no-prefix']) |
| else: |
| # Append / |
| sub_rep_path = os.path.join(sub_rep_path, '') |
| # Add the right prefix |
| command.extend(['--src-prefix=' + sub_rep_path]) |
| command.extend(['--dst-prefix=' + sub_rep_path]) |
| |
| command.extend([branch, 'HEAD']) |
| |
| # Run diff tree |
| diff = subprocess.Popen(command, |
| stdout=subprocess.PIPE, |
| cwd=cwd).stdout.readlines() |
| # Replace --- /dev/null with --- <new file name> |
| for i in range(len(diff)): |
| line = diff[i] |
| if line.startswith('--- /dev/null'): |
| line = '--- %s' % diff[i+1][4:] |
| output.append(line) |
| diff = output |
| |
| # Add root prefix |
| output = [] |
| for line in diff: |
| if line.startswith('--- ') or line.startswith('+++ '): |
| line = line[0:4] + os.path.join(prefix,line[4:]) |
| output.append(line) |
| |
| munged_diff = ''.join(output) |
| if len(munged_diff.strip()) == 0: |
| raise Exception("Patch was empty, did you give the right remote branch?") |
| |
| return munged_diff |
| |
| def OneRepositoryDiff(diff_file, patch_names, branch, prefix, sub_rep_path): |
| """Computes a diff for one git repository at a given path against a given |
| branch. Writes the diff into diff_file and appends a name to the |
| patch_names list.""" |
| |
| diff = GetMungedDiff(branch, prefix, sub_rep_path) |
| |
| # Write the diff out |
| diff_file.write(diff) |
| |
| # Add patch name to list of patches |
| patch_name = GetPatchName(GetSubRepWorkingDir(sub_rep_path)) |
| patch_names.extend([patch_name]) |
| |
| |
| def ValidEmail(email): |
| return re.match(r"^[a-zA-Z0-9._%-+]+@[a-zA-Z0-9._%-]+.[a-zA-Z]{2,6}$", email) |
| |
| |
| def GetEmail(): |
| email = Backquote(['git', 'config', 'user.email']) |
| runmsg = "Try: git config user.email <EMAIL>" |
| assert ValidEmail(email), "Email '%s' is not valid. %s" % (email, runmsg) |
| return email |
| |
| |
| def TryChange(args): |
| """Put a patch on the try server.""" |
| root_dir = Backquote(['git', 'rev-parse', '--show-cdup']) |
| trychange.checkout_root = os.path.abspath(root_dir) |
| trychange.TryChange(args, None, False) |
| |
| |
| if __name__ == '__main__': |
| parser = optparse.OptionParser( |
| usage='git try [options] [branch]', |
| description='Upload the current diff of branch...HEAD to the try server.') |
| parser.add_option("-b", "--bot", |
| help="Force the use of a specific build slave (eg mac, " |
| "win, or linux)") |
| parser.add_option("-c", "--clobber", action="store_true", |
| help="Make the try run use be a clobber build") |
| parser.add_option("-r", "--revision", |
| help="Specify the SVN base revision to use") |
| parser.add_option("--root", default="src", metavar="PATH", |
| help="Specify the root prefix that is appended to paths " |
| "in the patch") |
| parser.add_option("--dry_run", action="store_true", |
| help="Print the diff but don't send it to the try bots") |
| parser.add_option("--sub_rep", nargs=2, action="append", default=[], |
| metavar="PATH BRANCH", |
| help="Specify a path to a git sub-repository and a branch " |
| "to diff with in order to simultanously try changes " |
| "in multiple git repositories. Option may be " |
| "specified multiple times.") |
| parser.add_option("--webkit", metavar="BRANCH", |
| help="Specify webkit branch. Syntactic sugar for " |
| "--sub_rep third_party/WebKit/ <branch>") |
| |
| (options, args) = parser.parse_args(sys.argv) |
| |
| if options.webkit: |
| options.sub_rep.extend([('third_party/WebKit/', options.webkit)]) |
| |
| branch = None |
| if len(args) > 1: |
| branch = args[1] |
| patch_names = [] |
| |
| # Dump all diffs into one diff file. |
| diff_file = tempfile.NamedTemporaryFile() |
| |
| # Calculate diff for main git repository. |
| OneRepositoryDiff(diff_file, patch_names, branch, options.root, None) |
| |
| # Calculate diff for each extra git repository. |
| for path_and_branch in options.sub_rep: |
| OneRepositoryDiff(diff_file, |
| patch_names, |
| path_and_branch[1], |
| options.root, |
| path_and_branch[0]) |
| # Make diff file ready for reading. |
| diff_file.flush() |
| |
| # Concatenate patch names |
| # Prepare args for TryChange |
| email = GetEmail() |
| user = email.partition('@')[0] |
| args = [ |
| '-u', user, |
| '-e', email, |
| '-n', '-'.join(patch_names), |
| '--diff', diff_file.name, |
| ] |
| |
| # Send to try server via HTTP if we can parse the config, otherwise |
| # upload via SVN. |
| config = GetTryServerConfig() |
| if config is not None: |
| sendmsg = "Sending %s using HTTP..." % '-'.join(patch_names) |
| args.extend(['--use_http']) |
| if config['try_server_http_host'] is not None: |
| args.extend(['--host', config['try_server_http_host']]) |
| if config['try_server_http_port'] is not None: |
| args.extend(['--port', config['try_server_http_port']]) |
| |
| else: |
| print "Could not get server config -- if you're within Google, " |
| print "do you have have src-internal checked out?" |
| sendmsg = "Sending %s using SVN..." % '-'.join(patch_names) |
| args.extend([ |
| '--use_svn', '--svn_repo', |
| 'svn://svn.chromium.org/chrome-try/try', |
| ]) |
| |
| if options.bot: |
| args.extend(['--bot', options.bot]) |
| if options.clobber: |
| args.append('--clobber') |
| if options.revision: |
| args.extend(['-r', options.revision]) |
| if GetRietveldPatchsetNumber(): |
| args.extend([ |
| '--issue', GetRietveldIssueNumber(), |
| '--patchset', GetRietveldPatchsetNumber(), |
| ]) |
| |
| if options.dry_run: |
| print open(diff_file.name, 'r').read() |
| exit(0) |
| |
| print sendmsg |
| TryChange(args=args) |