278 lines
7.9 KiB
Python
Executable File
278 lines
7.9 KiB
Python
Executable File
#!/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 argparse
|
|
import datetime
|
|
import os
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
|
|
import pkg_resources
|
|
|
|
if sys.version < '3':
|
|
import ConfigParser
|
|
import urllib
|
|
import urlparse
|
|
urlencode = urllib.urlencode
|
|
urljoin = urlparse.urljoin
|
|
urlparse = urlparse.urlparse
|
|
do_input = raw_input
|
|
else:
|
|
import configparser as ConfigParser
|
|
|
|
import urllib.parse
|
|
import urllib.request
|
|
urlencode = urllib.parse.urlencode
|
|
urljoin = urllib.parse.urljoin
|
|
urlparse = urllib.parse.urlparse
|
|
do_input = input
|
|
|
|
VERBOSE = False
|
|
UPDATE = False
|
|
LOCAL_MODE = 'GITREVIEW_LOCAL_MODE' in os.environ
|
|
CONFIGDIR = os.path.expanduser("~/.config/git-review")
|
|
GLOBAL_CONFIG = "/etc/git-review/git-review.conf"
|
|
USER_CONFIG = os.path.join(CONFIGDIR, "git-review.conf")
|
|
DEFAULTS = dict(branch='master')
|
|
|
|
|
|
class GitRestackException(Exception):
|
|
pass
|
|
|
|
|
|
class CommandFailed(GitRestackException):
|
|
|
|
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 GitDirectoriesException(CommandFailed):
|
|
"Cannot determine where .git directory is."
|
|
EXIT_CODE = 70
|
|
|
|
|
|
class GitMergeBaseException(CommandFailed):
|
|
"Cannot determine merge base."
|
|
EXIT_CODE = 71
|
|
|
|
|
|
class GitConfigException(CommandFailed):
|
|
"""Git config value retrieval failed."""
|
|
EXIT_CODE = 128
|
|
|
|
|
|
def run_command_foreground(*argv, **kwargs):
|
|
if VERBOSE:
|
|
print(datetime.datetime.now(), "Running:", " ".join(argv))
|
|
if len(argv) == 1:
|
|
# for python2 compatibility with shlex
|
|
if sys.version_info < (3,) and isinstance(argv[0], unicode):
|
|
argv = shlex.split(argv[0].encode('utf-8'))
|
|
else:
|
|
argv = shlex.split(str(argv[0]))
|
|
subprocess.call(argv)
|
|
|
|
|
|
def run_command_status(*argv, **kwargs):
|
|
if VERBOSE:
|
|
print(datetime.datetime.now(), "Running:", " ".join(argv))
|
|
if len(argv) == 1:
|
|
# for python2 compatibility with shlex
|
|
if sys.version_info < (3,) and isinstance(argv[0], unicode):
|
|
argv = shlex.split(argv[0].encode('utf-8'))
|
|
else:
|
|
argv = shlex.split(str(argv[0]))
|
|
stdin = kwargs.pop('stdin', None)
|
|
newenv = os.environ.copy()
|
|
newenv['LANG'] = 'C'
|
|
newenv['LANGUAGE'] = 'C'
|
|
newenv.update(kwargs)
|
|
p = subprocess.Popen(argv,
|
|
stdin=subprocess.PIPE if stdin else None,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
env=newenv)
|
|
(out, nothing) = p.communicate(stdin)
|
|
out = out.decode('utf-8', 'replace')
|
|
return (p.returncode, out.strip())
|
|
|
|
|
|
def run_command(*argv, **kwargs):
|
|
(rc, output) = run_command_status(*argv, **kwargs)
|
|
return output
|
|
|
|
|
|
def run_command_exc(klazz, *argv, **env):
|
|
"""Run command *argv, on failure raise klazz
|
|
|
|
klazz should be derived from CommandFailed
|
|
"""
|
|
(rc, output) = run_command_status(*argv, **env)
|
|
if rc != 0:
|
|
raise klazz(rc, output, argv, env)
|
|
return output
|
|
|
|
|
|
def get_version():
|
|
requirement = pkg_resources.Requirement.parse('git-restack')
|
|
provider = pkg_resources.get_provider(requirement)
|
|
return provider.version
|
|
|
|
|
|
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.splitlines()
|
|
except ValueError:
|
|
raise GitDirectoriesException(0, out, cmd, {})
|
|
|
|
|
|
def git_config_get_value(section, option, default=None, as_bool=False):
|
|
"""Get config value for section/option."""
|
|
cmd = ["git", "config", "--get", "%s.%s" % (section, option)]
|
|
if as_bool:
|
|
cmd.insert(2, "--bool")
|
|
if LOCAL_MODE:
|
|
__, git_dir = git_directories()
|
|
cmd[2:2] = ['-f', os.path.join(git_dir, 'config')]
|
|
try:
|
|
return run_command_exc(GitConfigException, *cmd).strip()
|
|
except GitConfigException as exc:
|
|
if exc.rc == 1:
|
|
return default
|
|
raise
|
|
|
|
|
|
class Config(object):
|
|
"""Expose as dictionary configuration options."""
|
|
|
|
def __init__(self, config_file=None):
|
|
self.config = DEFAULTS.copy()
|
|
filenames = [] if LOCAL_MODE else [GLOBAL_CONFIG, USER_CONFIG]
|
|
if config_file:
|
|
filenames.append(config_file)
|
|
for filename in filenames:
|
|
if os.path.exists(filename):
|
|
if filename != config_file:
|
|
msg = ("Using global/system git-review config files (%s) "
|
|
"is deprecated")
|
|
print(msg % filename)
|
|
self.config.update(load_config_file(filename))
|
|
|
|
def __getitem__(self, key):
|
|
value = git_config_get_value('gitreview', key)
|
|
if value is None:
|
|
value = self.config[key]
|
|
return value
|
|
|
|
|
|
def load_config_file(config_file):
|
|
"""Load configuration options from a file."""
|
|
configParser = ConfigParser.ConfigParser()
|
|
configParser.read(config_file)
|
|
options = {
|
|
'scheme': 'scheme',
|
|
'hostname': 'host',
|
|
'port': 'port',
|
|
'project': 'project',
|
|
'branch': 'defaultbranch',
|
|
'remote': 'defaultremote',
|
|
'rebase': 'defaultrebase',
|
|
'track': 'track',
|
|
'usepushurl': 'usepushurl',
|
|
}
|
|
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 main():
|
|
usage = "git restack [BRANCH]"
|
|
|
|
parser = argparse.ArgumentParser(usage=usage, description=COPYRIGHT)
|
|
|
|
parser.add_argument("-v", "--verbose", dest="verbose", action="store_true",
|
|
help="Output more information about what's going on")
|
|
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], get_version()))
|
|
parser.add_argument("branch", nargs="?")
|
|
|
|
parser.set_defaults(verbose=False)
|
|
|
|
try:
|
|
(top_dir, git_dir) = git_directories()
|
|
except GitDirectoriesException as no_git_dir:
|
|
pass
|
|
else:
|
|
no_git_dir = False
|
|
config = Config(os.path.join(top_dir, ".gitreview"))
|
|
options = parser.parse_args()
|
|
if no_git_dir:
|
|
raise no_git_dir
|
|
|
|
if options.license:
|
|
print(COPYRIGHT)
|
|
sys.exit(0)
|
|
|
|
global VERBOSE
|
|
VERBOSE = options.verbose
|
|
|
|
if options.branch is None:
|
|
branch = config['branch']
|
|
else:
|
|
branch = options.branch
|
|
|
|
if branch is None:
|
|
branch = 'master'
|
|
|
|
status = 0
|
|
|
|
cmd = "git merge-base HEAD origin/%s" % branch
|
|
base = run_command_exc(GitMergeBaseException, cmd)
|
|
|
|
run_command_foreground("git rebase -i %s" % base, stdin=sys.stdin)
|
|
|
|
sys.exit(status)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|