+++ /dev/null
-#!/usr/bin/env python
-from __future__ import print_function
-
-COPYRIGHT = """\
-Copyright (C) 2011-2012 OpenStack LLC.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-implied.
-
-See the License for the specific language governing permissions and
-limitations under the License."""
-
-import datetime
-import json
-import os
-import re
-import shlex
-import subprocess
-import sys
-import time
-
-if sys.version < '3':
- import ConfigParser
- import urllib
- import urlparse
- urlopen = urllib.urlopen
- urlparse = urlparse.urlparse
- do_input = raw_input
-else:
- import configparser as ConfigParser
- import urllib.parse
- import urllib.request
- urlopen = urllib.request.urlopen
- urlparse = urllib.parse.urlparse
- do_input = input
-
-from distutils import version as du_version
-
-version = "1.22"
-
-VERBOSE = False
-UPDATE = False
-CONFIGDIR = os.path.expanduser("~/.config/git-review")
-GLOBAL_CONFIG = "/etc/git-review/git-review.conf"
-USER_CONFIG = os.path.join(CONFIGDIR, "git-review.conf")
-PYPI_URL = "http://pypi.python.org/pypi/git-review/json"
-PYPI_CACHE_TIME = 60 * 60 * 24 # 24 hours
-DEFAULTS = dict(hostname='gerrit.lud.stericsson.com', port='29418', project=False,
- defaultbranch='master', defaultremote="gerrit",
- defaultrebase="0")
-
-_branch_name = None
-_has_color = None
-_no_color_support = False
-
-
-class colors:
- yellow = '\033[33m'
- green = '\033[92m'
- reset = '\033[0m'
-
-
-class GitReviewException(Exception):
- pass
-
-
-class CommandFailed(GitReviewException):
-
- def __init__(self, *args):
- Exception.__init__(self, *args)
- (self.rc, self.output, self.argv, self.envp) = args
- self.quickmsg = dict([
- ("argv", " ".join(self.argv)),
- ("rc", self.rc),
- ("output", self.output)])
-
- def __str__(self):
- return self.__doc__ + """
-The following command failed with exit code %(rc)d
- "%(argv)s"
------------------------
-%(output)s
------------------------""" % self.quickmsg
-
-
-class ChangeSetException(GitReviewException):
-
- def __init__(self, e):
- GitReviewException.__init__(self)
- self.e = str(e)
-
- def __str__(self):
- return self.__doc__ % self.e
-
-
-def parse_review_number(review):
- parts = review.split(',')
- if len(parts) < 2:
- parts.append(None)
- return parts
-
-
-def build_review_number(review, patchset):
- if patchset is not None:
- return '%s,%s' % (review, patchset)
- return review
-
-
-def run_command_status(*argv, **env):
- if VERBOSE:
- print(datetime.datetime.now(), "Running:", " ".join(argv))
- if len(argv) == 1:
- argv = shlex.split(str(argv[0]))
- newenv = os.environ
- newenv.update(env)
- p = subprocess.Popen(argv, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT, env=newenv)
- (out, nothing) = p.communicate()
- out = out.decode('utf-8')
- return (p.returncode, out.strip())
-
-
-def run_command(*argv, **env):
- (rc, output) = run_command_status(*argv, **env)
- return output
-
-
-def run_command_exc(klazz, *argv, **env):
- """Run command *argv, on failure raise 'klazz
-
- klass should be derived from CommandFailed
- """
- (rc, output) = run_command_status(*argv, **env)
- if rc != 0:
- raise klazz(rc, output, argv, env)
- return output
-
-
-def update_latest_version(version_file_path):
- """Cache the latest version of git-review for the upgrade check."""
-
- if not os.path.exists(CONFIGDIR):
- os.makedirs(CONFIGDIR)
-
- if os.path.exists(version_file_path) and not UPDATE:
- if (time.time() - os.path.getmtime(version_file_path)) < 28800:
- return
-
- latest_version = version
- try:
- latest_version = json.load(urlopen(PYPI_URL))['info']['version']
- except Exception:
- pass
-
- with open(version_file_path, "w") as version_file:
- version_file.write(latest_version)
-
-
-def latest_is_newer():
- """Check if there is a new version of git-review."""
-
- # Skip version check if distro package turns it off
- if os.path.exists(GLOBAL_CONFIG):
- config = dict(check=False)
- configParser = ConfigParser.ConfigParser(config)
- configParser.read(GLOBAL_CONFIG)
- if not configParser.getboolean("updates", "check"):
- return False
-
- version_file_path = os.path.join(CONFIGDIR, "latest-version")
- update_latest_version(version_file_path)
-
- latest_version = None
- with open(version_file_path, "r") as version_file:
- latest_version = du_version.StrictVersion(version_file.read())
- if latest_version > du_version.StrictVersion(version):
- return True
- return False
-
-
-def git_directories():
- """Determine (absolute git work directory path, .git subdirectory path)."""
- cmd = ("git", "rev-parse", "--show-toplevel", "--git-dir")
- out = run_command_exc(GitDirectoriesException, *cmd)
- try:
- return out.split()
- except ValueError:
- raise GitDirectoriesException(0, out, cmd, {})
-
-
-class GitDirectoriesException(CommandFailed):
- "Cannot determine where .git directory is."
- EXIT_CODE = 70
-
-
-class CustomScriptException(CommandFailed):
- """Custom script execution failed."""
- EXIT_CODE = 71
-
-
-def run_custom_script(action):
- """Get status and output of .git/hooks/$action-review or/and
- ~/.config/hooks/$action-review if existing.
- """
- returns = []
- script_file = "%s-review" % (action)
- (top_dir, git_dir) = git_directories()
- paths = [os.path.join(CONFIGDIR, "hooks", script_file),
- os.path.join(git_dir, "hooks", script_file)]
- for fpath in paths:
- if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
- status, output = run_command_status(fpath)
- returns.append((status, output, fpath))
-
- for (status, output, path) in returns:
- if status is not None and status != 0:
- raise CustomScriptException(status, output, [path], {})
- elif output and VERBOSE:
- print("script %s output is:" % (path))
- print(output)
-
-
-def git_config_get_value(section, option, default=None):
- try:
- return run_command_exc(GitConfigException,
- "git", "config",
- "--get",
- "%s.%s" % (section, option)).strip()
- except GitConfigException as exc:
- if exc.rc == 1:
- return default
- raise
-
-
-class GitConfigException(CommandFailed):
- """Git config value retrieval failed."""
- EXIT_CODE = 128
-
-
-class CannotInstallHook(CommandFailed):
- "Problems encountered installing commit-msg hook"
- EXIT_CODE = 2
-
-
-def set_hooks_commit_msg(remote, target_file):
- """Install the commit message hook if needed."""
-
- # Create the hooks directory if it's not there already
- hooks_dir = os.path.dirname(target_file)
- if not os.path.isdir(hooks_dir):
- os.mkdir(hooks_dir)
-
- (hostname, username, port, project_name) = \
- parse_git_show(remote, "Push")
-
- if not os.path.exists(target_file) or UPDATE:
- if VERBOSE:
- print("Fetching commit hook from: scp://%s:%s" % (hostname, port))
- if port is not None:
- port = "-P %s" % port
- else:
- port = ""
- if username is None:
- userhost = hostname
- else:
- userhost = "%s@%s" % (username, hostname)
- run_command_exc(
- CannotInstallHook,
- "scp", port,
- userhost + ":hooks/commit-msg",
- target_file)
-
- if not os.access(target_file, os.X_OK):
- os.chmod(target_file, os.path.stat.S_IREAD | os.path.stat.S_IEXEC)
-
-
-def test_remote(username, hostname, port, project):
- """Tests that a possible gerrit remote works."""
-
- if port is not None:
- port = "-p %s" % port
- else:
- port = ""
- if username is None:
- userhost = hostname
- else:
- userhost = "%s@%s" % (username, hostname)
-
- (status, ssh_output) = run_command_status(
- "ssh", "-x", port, userhost,
- "gerrit", "ls-projects")
-
- if status == 0:
- if VERBOSE:
- print("%s@%s:%s worked." % (username, hostname, port))
- return True
- else:
- if VERBOSE:
- print("%s@%s:%s did not work." % (username, hostname, port))
- return False
-
-
-def make_remote_url(username, hostname, port, project):
- """Builds a gerrit remote URL."""
- if username is None:
- return "ssh://%s:%s/%s" % (hostname, port, project)
- else:
- return "ssh://%s@%s:%s/%s" % (username, hostname, port, project)
-
-
-def add_remote(hostname, port, project, remote):
- """Adds a gerrit remote."""
- asked_for_username = False
-
- username = os.getenv("USERNAME")
- if not username:
- username = git_config_get_value("gitreview", "username")
- if not username:
- username = os.getenv("USER")
- if port is None:
- port = 29418
-
- remote_url = make_remote_url(username, hostname, port, project)
- if VERBOSE:
- print("No remote set, testing %s" % remote_url)
- if not test_remote(username, hostname, port, project):
- print("Could not connect to gerrit.")
- username = do_input("Enter your gerrit username: ")
- remote_url = make_remote_url(username, hostname, port, project)
- print("Trying again with %s" % remote_url)
- if not test_remote(username, hostname, port, project):
- raise Exception("Could not connect to gerrit at %s" % remote_url)
- asked_for_username = True
-
- print("Creating a git remote called \"%s\" that maps to:" % remote)
- print("\t%s" % remote_url)
- cmd = "git remote add -f %s %s" % (remote, remote_url)
- (status, remote_output) = run_command_status(cmd)
-
- if status != 0:
- raise Exception("Error running %s" % cmd)
-
- if asked_for_username:
- print()
- print("This repository is now set up for use with git-review.")
- print("You can set the default username for future repositories with:")
- print(' git config --global --add gitreview.username "%s"' % username)
- print()
-
-
-def parse_git_show(remote, verb):
- fetch_url = ""
- for line in run_command("git remote show -n %s" % remote).split("\n"):
- if line.strip().startswith("%s" % verb):
- fetch_url = line.split()[2]
-
- parsed_url = urlparse(fetch_url)
- project_name = parsed_url.path.lstrip("/")
- if project_name.endswith(".git"):
- project_name = project_name[:-4]
-
- hostname = parsed_url.netloc
- username = None
- port = parsed_url.port
-
- if VERBOSE:
- print("Found origin %s URL:" % verb, fetch_url)
-
- # Workaround bug in urlparse on OSX
- if parsed_url.scheme == "ssh" and parsed_url.path[:2] == "//":
- hostname = parsed_url.path[2:].split("/")[0]
-
- if "@" in hostname:
- (username, hostname) = hostname.split("@")
- if ":" in hostname:
- (hostname, port) = hostname.split(":")
-
- # Is origin an ssh location? Let's pull more info
- if parsed_url.scheme == "ssh" and port is None:
- port = 22
-
- return (hostname, username, str(port), project_name)
-
-
-def check_color_support():
- global _has_color
- global _no_color_support
- if _has_color is None:
- test_command = "git log --color=never --oneline HEAD^1..HEAD"
- (status, output) = run_command_status(test_command)
- if status == 0:
- _has_color = True
- else:
- _has_color = False
-
- if _no_color_support:
- _has_color = False
-
- return _has_color
-
-
-def get_config(config_file=None):
- """Generate the configuration map by starting with some built-in defaults
- and then loading GLOBAL_CONFIG, USER_CONFIG, and a repository-specific
- .gitreview file, if they exist. In case of conflict, the configuration file
- with the narrowest scope wins.
- """
- config = DEFAULTS.copy()
- for filename in (GLOBAL_CONFIG, USER_CONFIG, config_file):
- if filename is not None and os.path.exists(filename):
- config.update(load_config_file(filename))
- return config
-
-
-def load_config_file(config_file):
- """Load configuration options from a file."""
- configParser = ConfigParser.ConfigParser()
- configParser.read(config_file)
- options = {
- 'hostname': 'host',
- 'port': 'port',
- 'project': 'project',
- 'defaultbranch': 'defaultbranch',
- 'defaultremote': 'defaultremote',
- 'defaultrebase': 'defaultrebase',
- }
- config = {}
- for config_key, option_name in options.items():
- if configParser.has_option('gerrit', option_name):
- config[config_key] = configParser.get('gerrit', option_name)
- return config
-
-
-def update_remote(remote):
- cmd = "git remote update %s" % remote
- (status, output) = run_command_status(cmd)
- if VERBOSE:
- print(output)
- if status != 0:
- print("Problem running '%s'" % cmd)
- if not VERBOSE:
- print(output)
- return False
- return True
-
-
-def check_remote(branch, remote, hostname, port, project):
- """Check that a Gerrit Git remote repo exists, if not, set one."""
-
- has_color = check_color_support()
- if has_color:
- color_never = "--color=never"
- else:
- color_never = ""
-
- if remote in run_command("git remote").split("\n"):
-
- remotes = run_command("git branch -a %s" % color_never).split("\n")
- for current_remote in remotes:
- if (current_remote.strip() == "remotes/%s/%s" % (remote, branch)
- and not UPDATE):
- return
- # We have the remote, but aren't set up to fetch. Fix it
- if VERBOSE:
- print("Setting up gerrit branch tracking for better rebasing")
- update_remote(remote)
- return
-
- if hostname is False or port is False or project is False:
- # This means there was no .gitreview file
- print("No '.gitreview' file found in this repository.")
- print("We don't know where your gerrit is. Please manually create ")
- print("a remote named \"%s\" and try again." % remote)
- sys.exit(1)
-
- # Gerrit remote not present, try to add it
- try:
- add_remote(hostname, port, project, remote)
- except Exception:
- print(sys.exc_info()[2])
- print("We don't know where your gerrit is. Please manually create ")
- print("a remote named \"%s\" and try again." % remote)
- raise
-
-
-def rebase_changes(branch, remote, interactive=True):
-
- remote_branch = "remotes/%s/%s" % (remote, branch)
-
- if not update_remote(remote):
- return False
-
- if interactive:
- cmd = "git rebase -i %s" % remote_branch
- else:
- cmd = "git rebase %s" % remote_branch
-
- (status, output) = run_command_status(cmd, GIT_EDITOR='true')
- if status != 0:
- print("Errors running %s" % cmd)
- if interactive:
- print(output)
- return False
- return True
-
-
-def undo_rebase():
- cmd = "git reset --hard ORIG_HEAD"
- (status, output) = run_command_status(cmd)
- if status != 0:
- print("Errors running %s" % cmd)
- print(output)
- return False
- return True
-
-
-def get_branch_name(target_branch):
- global _branch_name
- if _branch_name is not None:
- return _branch_name
- _branch_name = None
- has_color = check_color_support()
- if has_color:
- color_never = "--color=never"
- else:
- color_never = ""
- for branch in run_command("git branch %s" % color_never).split("\n"):
- if branch.startswith('*'):
- _branch_name = branch.split()[1].strip()
- if _branch_name == "(no":
- _branch_name = target_branch
- return _branch_name
-
-
-def assert_one_change(remote, branch, yes, have_hook):
- has_color = check_color_support()
- if has_color:
- color = git_config_get_value("color", "ui")
- if color is None:
- color = "auto"
- else:
- color = color.lower()
- if (color == "" or color == "true"):
- color = "auto"
- elif color == "false":
- color = "never"
- elif color == "auto":
- # Python is not a tty, we have to force colors
- color = "always"
- use_color = "--color=%s" % color
- else:
- use_color = ""
- cmd = "git log %s --decorate --oneline HEAD --not remotes/%s/%s --" % (
- use_color, remote, branch)
- (status, output) = run_command_status(cmd)
- if status != 0:
- print("Had trouble running %s" % cmd)
- print(output)
- sys.exit(1)
- output_lines = len(output.split("\n"))
- if output_lines == 1 and not have_hook:
- print("Your change was committed before the commit hook was installed")
- print("Amending the commit to add a gerrit change id")
- run_command("git commit --amend", GIT_EDITOR='true')
- elif output_lines == 0:
- print("No changes between HEAD and %s/%s." % (remote, branch))
- print("Submitting for review would be pointless.")
- sys.exit(1)
- elif output_lines > 1:
- if not yes:
- print("You have more than one commit"
- " that you are about to submit.")
- print("The outstanding commits are:\n\n%s\n" % output)
- print("Is this really what you meant to do?")
- yes_no = do_input("Type 'yes' to confirm: ")
- if yes_no.lower().strip() != "yes":
- print("Aborting.")
- print("Please rebase/squash your changes and try again")
- sys.exit(1)
-
-
-def use_topic(why, topic):
- """Inform the user about why a particular topic has been selected."""
- if VERBOSE:
- print(why % ('"%s"' % topic,))
- return topic
-
-
-def get_topic(target_branch):
-
- branch_name = get_branch_name(target_branch)
-
- branch_parts = branch_name.split("/")
- if len(branch_parts) >= 3 and branch_parts[0] == "review":
- return use_topic("Using change number %s "
- "for the topic of the change submitted",
- "/".join(branch_parts[2:]))
-
- log_output = run_command("git log HEAD^1..HEAD")
- bug_re = r'\b([Bb]ug|[Ll][Pp])\s*[#:]?\s*(\d+)'
-
- match = re.search(bug_re, log_output)
- if match is not None:
- return use_topic("Using bug number %s "
- "for the topic of the change submitted",
- "bug/%s" % match.group(2))
-
- bp_re = r'\b([Bb]lue[Pp]rint|[Bb][Pp])\s*[#:]?\s*([0-9a-zA-Z-_]+)'
- match = re.search(bp_re, log_output)
- if match is not None:
- return use_topic("Using blueprint number %s "
- "for the topic of the change submitted",
- "bp/%s" % match.group(2))
-
- return use_topic("Using local branch name %s "
- "for the topic of the change submitted",
- branch_name)
-
-
-class CannotQueryOpenChangesets(CommandFailed):
- "Cannot fetch review information from gerrit"
- EXIT_CODE = 32
-
-
-class CannotParseOpenChangesets(ChangeSetException):
- "Cannot parse JSON review information from gerrit"
- EXIT_CODE = 33
-
-
-def list_reviews(remote):
-
- (hostname, username, port, project_name) = \
- parse_git_show(remote, "Push")
-
- if port is not None:
- port = "-p %s" % port
- else:
- port = ""
- if username is None:
- userhost = hostname
- else:
- userhost = "%s@%s" % (username, hostname)
-
- review_info = None
- output = run_command_exc(
- CannotQueryOpenChangesets,
- "ssh", "-x", port, userhost,
- "gerrit", "query",
- "--current-patch-set --format=JSON project:%s status:open reviewer:self" % project_name)
-
- review_list = []
- review_field_width = {}
- REVIEW_FIELDS = ('number', 'currentPatchSet', 'branch', 'subject')
- FIELDS = range(0, len(REVIEW_FIELDS))
- if check_color_support():
- review_field_color = (colors.yellow, colors.yellow, colors.green, "")
- color_reset = colors.reset
- else:
- review_field_color = ("", "", "", "")
- color_reset = ""
- review_field_width = [0, 0, 0, 0]
- review_field_format = ["%*s", "%*s", "%*s", "%*s"]
- review_field_justify = [+1, +1, +1, -1] # -1 is justify to right
-
- for line in output.split("\n"):
- # Warnings from ssh wind up in this output
- if line[0] != "{":
- print(line)
- continue
- try:
- review_info = json.loads(line)
- except Exception:
- if VERBOSE:
- print(output)
- raise(CannotParseOpenChangesets, sys.exc_info()[1])
-
- if 'type' in review_info:
- break
-
- tempPS = review_info['currentPatchSet']
- appPS = tempPS['approvals']
- appValue = '-';
- for appLine in appPS:
- appBy = appLine['by']
- appUser = appBy['username']
- if appUser == username:
- appValue = appLine['value']
- review_info['currentPatchSet'] = tempPS['number'] + ' ' + appValue
-
- review_list.append([review_info[f] for f in REVIEW_FIELDS])
- for i in FIELDS:
- review_field_width[i] = max(
- review_field_width[i],
- len(review_info[REVIEW_FIELDS[i]])
- )
-
- review_field_format = " ".join([
- review_field_color[i] +
- review_field_format[i] +
- color_reset
- for i in FIELDS])
-
- review_field_width = [
- review_field_width[i] * review_field_justify[i]
- for i in FIELDS]
- for review_value in review_list:
- # At this point we have review_field_format
- # like "%*s %*s %*s" and we need to supply
- # (width1, value1, width2, value2, ...) tuple to print
- # It's easy to zip() widths with actual values,
- # but we need to flatten the resulting
- # ((width1, value1), (width2, value2), ...) map.
- formatted_fields = []
- for (width, value) in zip(review_field_width, review_value):
- formatted_fields.extend([width, value])
- print(review_field_format % tuple(formatted_fields))
- print("Found %d items for review" % review_info['rowCount'])
-
- return 0
-
-
-class CannotQueryPatchSet(CommandFailed):
- "Cannot query patchset information"
- EXIT_CODE = 34
-
-
-class ReviewInformationNotFound(ChangeSetException):
- "Could not fetch review information for change %s"
- EXIT_CODE = 35
-
-
-class ReviewNotFound(ChangeSetException):
- "Gerrit review %s not found"
- EXIT_CODE = 36
-
-
-class PatchSetGitFetchFailed(CommandFailed):
- """Cannot fetch patchset contents
-
-Does specified change number belong to this project?
-"""
- EXIT_CODE = 37
-
-
-class PatchSetNotFound(ChangeSetException):
- "Review patchset %s not found"
- EXIT_CODE = 38
-
-
-class CheckoutNewBranchFailed(CommandFailed):
- "Cannot checkout to new branch"
- EXIT_CODE = 64
-
-
-class CheckoutExistingBranchFailed(CommandFailed):
- "Cannot checkout existing branch"
- EXIT_CODE = 65
-
-
-class ResetHardFailed(CommandFailed):
- "Failed to hard reset downloaded branch"
- EXIT_CODE = 66
-
-
-def fetch_review(review, masterbranch, remote):
-
- (hostname, username, port, project_name) = \
- parse_git_show(remote, "Push")
-
- if port is not None:
- port = "-p %s" % port
- else:
- port = ""
- if username is None:
- userhost = hostname
- else:
- userhost = "%s@%s" % (username, hostname)
-
- review_arg = review
- patchset_opt = '--current-patch-set'
-
- review, patchset_number = parse_review_number(review)
- if patchset_number is not None:
- patchset_opt = '--patch-sets'
-
- review_info = None
- output = run_command_exc(
- CannotQueryPatchSet,
- "ssh", "-x", port, userhost,
- "gerrit", "query",
- "--format=JSON %s change:%s" % (patchset_opt, review))
-
- review_jsons = output.split("\n")
- found_review = False
- for review_json in review_jsons:
- try:
- review_info = json.loads(review_json)
- found_review = True
- except Exception:
- pass
- if found_review:
- break
- if not found_review:
- if VERBOSE:
- print(output)
- raise ReviewInformationNotFound(review)
-
- try:
- if patchset_number is None:
- refspec = review_info['currentPatchSet']['ref']
- else:
- refspec = [ps for ps
- in review_info['patchSets']
- if ps['number'] == patchset_number][0]['ref']
- except IndexError:
- raise PatchSetNotFound(review_arg)
- except KeyError:
- raise ReviewNotFound(review)
-
- try:
- topic = review_info['topic']
- if topic == masterbranch:
- topic = review
- except KeyError:
- topic = review
- try:
- author = re.sub('\W+', '_', review_info['owner']['name']).lower()
- except KeyError:
- author = 'unknown'
-
- if patchset_number is None:
- branch_name = "review/%s/%s" % (author, topic)
- else:
- branch_name = "review/%s/%s-patch%s" % (author, topic, patchset_number)
-
- print("Downloading %s from gerrit" % refspec)
- run_command_exc(PatchSetGitFetchFailed,
- "git", "fetch", remote, refspec)
- return branch_name
-
-
-def checkout_review(branch_name):
- """Checkout a newly fetched (FETCH_HEAD) change
- into a branch
- """
-
- try:
- run_command_exc(CheckoutNewBranchFailed,
- "git", "checkout", "-b",
- branch_name, "FETCH_HEAD")
-
- except CheckoutNewBranchFailed as e:
- if re.search("already exists\.?", e.output):
- print("Branch already exists - reusing")
- run_command_exc(CheckoutExistingBranchFailed,
- "git", "checkout", branch_name)
- run_command_exc(ResetHardFailed,
- "git", "reset", "--hard", "FETCH_HEAD")
- else:
- raise
-
- print("Switched to branch \"%s\"" % branch_name)
-
-
-class PatchSetGitCherrypickFailed(CommandFailed):
- "There was a problem applying changeset contents to the current branch."
- EXIT_CODE = 69
-
-
-def cherrypick_review(option=None):
- cmd = ["git", "cherry-pick"]
- if option:
- cmd.append(option)
- cmd.append("FETCH_HEAD")
- print(run_command_exc(PatchSetGitCherrypickFailed, *cmd))
-
-
-class CheckoutBackExistingBranchFailed(CommandFailed):
- "Cannot switch back to existing branch"
- EXIT_CODE = 67
-
-
-class DeleteBranchFailed(CommandFailed):
- "Failed to delete branch"
- EXIT_CODE = 68
-
-
-class InvalidPatchsetsToCompare(GitReviewException):
- def __init__(self, patchsetA, patchsetB):
- Exception.__init__(
- self,
- "Invalid patchsets for comparison specified (old=%s,new=%s)" % (
- patchsetA,
- patchsetB))
- EXIT_CODE = 39
-
-
-def compare_review(review_spec, branch, remote, rebase=False):
- new_ps = None # none means latest
-
- if '-' in review_spec:
- review_spec, new_ps = review_spec.split('-')
- review, old_ps = parse_review_number(review_spec)
-
- if old_ps is None or old_ps == new_ps:
- raise InvalidPatchsetsToCompare(old_ps, new_ps)
-
- old_review = build_review_number(review, old_ps)
- new_review = build_review_number(review, new_ps)
-
- old_branch = fetch_review(old_review, branch, remote)
- checkout_review(old_branch)
-
- if rebase:
- print('Rebasing %s' % old_branch)
- rebase = rebase_changes(branch, remote, False)
- if not rebase:
- print('Skipping rebase because of conflicts')
- run_command_exc(CommandFailed, 'git', 'rebase', '--abort')
-
- new_branch = fetch_review(new_review, branch, remote)
- checkout_review(new_branch)
-
- if rebase:
- print('Rebasing also %s' % new_branch)
- if not rebase_changes(branch, remote, False):
- print("Rebasing of the new branch failed, "
- "diff can be messed up (use -R to not rebase at all)!")
- run_command_exc(CommandFailed, 'git', 'rebase', '--abort')
-
- subprocess.check_call(['git', 'difftool', old_branch])
-
-
-def finish_branch(target_branch):
- local_branch = get_branch_name(target_branch)
- if VERBOSE:
- print("Switching back to '%s' and deleting '%s'" % (target_branch,
- local_branch))
- run_command_exc(CheckoutBackExistingBranchFailed,
- "git", "checkout", target_branch)
- print("Switched to branch '%s'" % target_branch)
-
- run_command_exc(DeleteBranchFailed,
- "git", "branch", "-D", local_branch)
- print("Deleted branch '%s'" % local_branch)
-
-
-def convert_bool(one_or_zero):
- "Return a bool on a one or zero string."
- return one_or_zero in ["1", "true", "True"]
-
-
-def print_exit_message(status, needs_update):
-
- if needs_update:
- print("""
-***********************************************************
-A new version of git-review is available on PyPI. Please
-update your copy with:
-
- pip install -U git-review
-
-to ensure proper behavior with gerrit. Thanks!
-***********************************************************
-""")
- sys.exit(status)
-
-
-def main():
- global _no_color_support
- usage = "git review [OPTIONS] ... [BRANCH]"
-
- import argparse
-
- class DownloadFlag(argparse.Action):
- """Additional option parsing: store value in 'dest', but
- at the same time set one of the flag options to True
- """
- def __call__(self, parser, namespace, values, option_string=None):
- setattr(namespace, self.dest, values)
- setattr(namespace, self.const, True)
-
- parser = argparse.ArgumentParser(usage=usage, description=COPYRIGHT)
-
- parser.add_argument("-t", "--topic", dest="topic",
- help="Topic to submit branch to")
- parser.add_argument("-D", "--draft", dest="draft", action="store_true",
- help="Submit review as a draft")
- parser.add_argument("-c", "--compatible", dest="compatible",
- action="store_true",
- help="Push change to refs/for/* for compatibility "
- "with Gerrit versions < 2.3. Ignored if "
- "-D/--draft is used.")
- parser.add_argument("-n", "--dry-run", dest="dry", action="store_true",
- help="Don't actually submit the branch for review")
- parser.add_argument("-i", "--new-changeid", dest="regenerate",
- action="store_true",
- help="Regenerate Change-id before submitting")
- parser.add_argument("-r", "--remote", dest="remote",
- help="git remote to use for gerrit")
- parser.add_argument("-R", "--no-rebase", dest="rebase",
- action="store_false",
- help="Don't rebase changes before submitting.")
- parser.add_argument("-F", "--force-rebase", dest="force_rebase",
- action="store_true",
- help="Force rebase even when not needed.")
- parser.add_argument("-B", "--no-color", dest="no_color_support",
- action="store_true",
- help="No color support.")
-
-
- fetch = parser.add_mutually_exclusive_group()
- fetch.set_defaults(download=False, compare=False, cherrypickcommit=False,
- cherrypickindicate=False, cherrypickonly=False)
- fetch.add_argument("-d", "--download", dest="changeidentifier",
- action=DownloadFlag, metavar="CHANGE",
- const="download",
- help="Download the contents of an existing gerrit "
- "review into a branch")
- fetch.add_argument("-x", "--cherrypick", dest="changeidentifier",
- action=DownloadFlag, metavar="CHANGE",
- const="cherrypickcommit",
- help="Apply the contents of an existing gerrit "
- "review onto the current branch and commit "
- "(cherry pick; not recommended in most "
- "situations)")
- fetch.add_argument("-X", "--cherrypickindicate", dest="changeidentifier",
- action=DownloadFlag, metavar="CHANGE",
- const="cherrypickindicate",
- help="Apply the contents of an existing gerrit "
- "review onto the current branch and commit, "
- "indicating its origin")
- fetch.add_argument("-N", "--cherrypickonly", dest="changeidentifier",
- action=DownloadFlag, metavar="CHANGE",
- const="cherrypickonly",
- help="Apply the contents of an existing gerrit "
- "review to the working directory and prepare "
- "for commit")
- fetch.add_argument("-m", "--compare", dest="changeidentifier",
- action=DownloadFlag, metavar="CHANGE,PS[-NEW_PS]",
- const="compare",
- help="Download specified and latest (or NEW_PS) "
- "patchsets of an existing gerrit review into "
- "a branches, rebase on master "
- "(skipped on conflicts or when -R is specified) "
- "and show their differences")
-
- parser.add_argument("-u", "--update", dest="update", action="store_true",
- help="Force updates from remote locations")
- parser.add_argument("-s", "--setup", dest="setup", action="store_true",
- help="Just run the repo setup commands but don't "
- "submit anything")
- parser.add_argument("-f", "--finish", dest="finish", action="store_true",
- help="Close down this branch and switch back to "
- "master on successful submission")
- parser.add_argument("-l", "--list", dest="list", action="store_true",
- help="List available reviews for the current project")
- parser.add_argument("-y", "--yes", dest="yes", action="store_true",
- help="Indicate that you do, in fact, understand if "
- "you are submitting more than one patch")
- parser.add_argument("-v", "--verbose", dest="verbose", action="store_true",
- help="Output more information about what's going on")
- parser.add_argument("--no-custom-script", dest="custom_script",
- action="store_false", default=True,
- help="Do not run custom scripts.")
- parser.add_argument("--license", dest="license", action="store_true",
- help="Print the license and exit")
- parser.add_argument("--version", action="version",
- version='%s version %s' %
- (os.path.split(sys.argv[0])[-1], version))
- parser.add_argument("branch", nargs="?")
-
- try:
- (top_dir, git_dir) = git_directories()
- except GitDirectoriesException:
- if sys.argv[1:] in ([], ['-h'], ['--help']):
- parser.print_help()
- sys.exit(1)
- raise
-
-# config = get_config(os.path.join(top_dir, ".gitreview"))
- config = DEFAULTS.copy()
- cur_proj = run_command("git remote -v")
- import re
- m = re.search('(?<=29418\/)(\S+)', cur_proj)
- config['project'] = m.group(0)
-
-
- defaultrebase = convert_bool(
- git_config_get_value("gitreview", "rebase",
- default=str(config['defaultrebase'])))
- parser.set_defaults(dry=False,
- draft=False,
- rebase=defaultrebase,
- verbose=False,
- update=False,
- setup=False,
- list=False,
- yes=False,
- branch=config['defaultbranch'],
- remote=config['defaultremote'])
- options = parser.parse_args()
-
- if options.license:
- print(COPYRIGHT)
- sys.exit(0)
-
- branch = options.branch
- global VERBOSE
- global UPDATE
- VERBOSE = options.verbose
- UPDATE = options.update
- remote = options.remote
- yes = options.yes
- status = 0
-
- needs_update = latest_is_newer()
- check_remote(branch, remote,
- config['hostname'], config['port'], config['project'])
-
- if options.no_color_support:
- _no_color_support = True
-
- if options.changeidentifier:
- if options.compare:
- compare_review(options.changeidentifier,
- branch, remote, options.rebase)
- return
- local_branch = fetch_review(options.changeidentifier, branch, remote)
- if options.download:
- checkout_review(local_branch)
- else:
- if options.cherrypickcommit:
- cherrypick_review()
- elif options.cherrypickonly:
- cherrypick_review("-n")
- if options.cherrypickindicate:
- cherrypick_review("-x")
- return
- elif options.list:
- list_reviews(remote)
- return
-
- if options.custom_script:
- run_custom_script("pre")
-
- hook_file = os.path.join(git_dir, "hooks", "commit-msg")
- have_hook = os.path.exists(hook_file) and os.access(hook_file, os.X_OK)
-
- if not have_hook:
- set_hooks_commit_msg(remote, hook_file)
-
- if options.setup:
- if options.finish and not options.dry:
- finish_branch(branch)
- return
-
- if options.rebase:
- if not rebase_changes(branch, remote):
- print_exit_message(1, needs_update)
- if not options.force_rebase and not undo_rebase():
- print_exit_message(1, needs_update)
- assert_one_change(remote, branch, yes, have_hook)
-
- ref = "publish"
-
- if options.draft:
- ref = "drafts"
- if options.custom_script:
- run_custom_script("draft")
- elif options.compatible:
- ref = "for"
-
- cmd = "git push %s HEAD:refs/%s/%s" % (remote, ref, branch)
- topic = options.topic or get_topic(branch)
- if topic != branch:
- cmd += "/%s" % topic
- if options.regenerate:
- print("Amending the commit to regenerate the change id\n")
- regenerate_cmd = "git commit --amend"
- if options.dry:
- print("\tGIT_EDITOR=\"sed -i -e '/^Change-Id:/d'\" %s\n" %
- regenerate_cmd)
- else:
- run_command(regenerate_cmd,
- GIT_EDITOR="sed -i -e "
- "'/^Change-Id:/d'")
-
- if options.dry:
- print("Please use the following command "
- "to send your commits to review:\n")
- print("\t%s\n" % cmd)
- else:
- (status, output) = run_command_status(cmd)
- print(output)
-
- if options.finish and not options.dry and status == 0:
- finish_branch(branch)
- return
-
- if options.custom_script:
- run_custom_script("post")
- print_exit_message(status, needs_update)
-
-
-if __name__ == "__main__":
- try:
- main()
- except GitReviewException as e:
- print(e)
- sys.exit(e.EXIT_CODE)