Prefer git-config over git-review config files

git-review gets its configuration from multiple sources (by priority):

 * command line options
 * git-config
  * options: username, rebase
  * with local/global/system config
 * git-review config files
  * options: scheme, host, port, project, defaultbranch/remote/rebase
  * locally using .gitreview
  * globally using ~/.config/gitreview/git-review.conf
  * system using /etc/git-review/git-review.conf

.gitreview file is commonly provided versioned in the git repo, where
other git-review config files and git-config is done by developers. It
implies for developers multiple places to define local/global/system
configuration options but some can only be changed by updating
.gitreview (scheme/port...) which is under versioning.

This change proposes to use as configuration sources (by priority):

 * command line options
 * git-config for developer local/global/system config
  * options: username, rebase and all .gitreview options
 * .gitreview local file for repo provided specific config

and deprecates git-review global/system files in order to reduce
configuration sources and allow to overload .gitreview configuration
without updating it.

Change-Id: I24aba0fa089de57d51a79049d9c192f76d576f69
This commit is contained in:
Cedric Brandily 2014-08-18 01:38:22 +02:00
parent abe76bfe28
commit 1bc87f26d6
5 changed files with 89 additions and 33 deletions

View File

@ -181,6 +181,24 @@ file:
[gitreview] [gitreview]
username=\fImygerrituser\fP username=\fImygerrituser\fP
.Ed .Ed
.It gitreview.scheme
This setting determines the default scheme (ssh/http/https) of gerrit remote
.Ed
.It gitreview.host
This setting determines the default hostname of gerrit remote
.Ed
.It gitreview.port
This setting determines the default port of gerrit remote
.Ed
.It gitreview.project
This setting determines the default name of gerrit git repo
.Ed
.It gitreview.remote
This setting determines the default name to use for gerrit remote
.Ed
.It gitreview.branch
This setting determines the default branch
.Ed
.It gitreview.rebase .It gitreview.rebase
This setting determines whether changes submitted will This setting determines whether changes submitted will
be rebased to the newest state of the branch. be rebased to the newest state of the branch.
@ -232,6 +250,7 @@ not to rebase changes by default (same as the
command line option) command line option)
.Bd -literal -offset indent .Bd -literal -offset indent
[gerrit] [gerrit]
scheme=ssh
host=review.example.com host=review.example.com
port=29418 port=29418
project=department/project.git project=department/project.git
@ -240,6 +259,9 @@ defaultremote=review
defaultrebase=0 defaultrebase=0
.Ed .Ed
.Pp .Pp
When the same option is provided through FILES and CONFIGURATION, the
CONFIGURATION value wins.
.Pp
.Sh DIAGNOSTICS .Sh DIAGNOSTICS
.Pp .Pp
Normally, exit status is 0 if executed successfully. Normally, exit status is 0 if executed successfully.

View File

@ -56,8 +56,7 @@ CONFIGDIR = os.path.expanduser("~/.config/git-review")
GLOBAL_CONFIG = "/etc/git-review/git-review.conf" GLOBAL_CONFIG = "/etc/git-review/git-review.conf"
USER_CONFIG = os.path.join(CONFIGDIR, "git-review.conf") USER_CONFIG = os.path.join(CONFIGDIR, "git-review.conf")
DEFAULTS = dict(scheme='ssh', hostname=False, port=None, project=False, DEFAULTS = dict(scheme='ssh', hostname=False, port=None, project=False,
defaultbranch='master', defaultremote="gerrit", branch='master', remote="gerrit", rebase="1")
defaultrebase="1")
_branch_name = None _branch_name = None
_has_color = None _has_color = None
@ -240,6 +239,29 @@ def git_config_get_value(section, option, default=None, as_bool=False):
raise 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
class GitConfigException(CommandFailed): class GitConfigException(CommandFailed):
"""Git config value retrieval failed.""" """Git config value retrieval failed."""
EXIT_CODE = 128 EXIT_CODE = 128
@ -519,22 +541,6 @@ def check_color_support():
return _has_color 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()
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):
config.update(load_config_file(filename))
return config
def load_config_file(config_file): def load_config_file(config_file):
"""Load configuration options from a file.""" """Load configuration options from a file."""
configParser = ConfigParser.ConfigParser() configParser = ConfigParser.ConfigParser()
@ -544,9 +550,9 @@ def load_config_file(config_file):
'hostname': 'host', 'hostname': 'host',
'port': 'port', 'port': 'port',
'project': 'project', 'project': 'project',
'defaultbranch': 'defaultbranch', 'branch': 'defaultbranch',
'defaultremote': 'defaultremote', 'remote': 'defaultremote',
'defaultrebase': 'defaultrebase', 'rebase': 'defaultrebase',
} }
config = {} config = {}
for config_key, option_name in options.items(): for config_key, option_name in options.items():
@ -1047,7 +1053,7 @@ def finish_branch(target_branch):
def convert_bool(one_or_zero): def convert_bool(one_or_zero):
"Return a bool on a one or zero string." "Return a bool on a one or zero string."
return one_or_zero in ["1", "true", "True"] return str(one_or_zero) in ["1", "true", "True"]
def _main(): def _main():
@ -1167,13 +1173,10 @@ def _main():
pass pass
else: else:
no_git_dir = False no_git_dir = False
config = get_config(os.path.join(top_dir, ".gitreview")) config = Config(os.path.join(top_dir, ".gitreview"))
defaultrebase = convert_bool( parser.set_defaults(branch=config['branch'],
git_config_get_value("gitreview", "rebase", rebase=convert_bool(config['rebase']),
default=str(config['defaultrebase']))) remote=config['remote'])
parser.set_defaults(rebase=defaultrebase,
branch=config['defaultbranch'],
remote=config['defaultremote'])
options = parser.parse_args() options = parser.parse_args()
if no_git_dir: if no_git_dir:
raise no_git_dir raise no_git_dir

View File

@ -274,16 +274,18 @@ class BaseGitReviewTestCase(testtools.TestCase, GerritHelpers):
host = '127.%s.%s.%s' % (self._test_counter, pid >> 8, pid & 255) host = '127.%s.%s.%s' % (self._test_counter, pid >> 8, pid & 255)
return host, 29418, host, 8080, self._dir('gerrit', 'site-' + host) return host, 29418, host, 8080, self._dir('gerrit', 'site-' + host)
def _create_gitreview_file(self): def _create_gitreview_file(self, **kwargs):
cfg = ('[gerrit]\n' cfg = ('[gerrit]\n'
'scheme=%s\n' 'scheme=%s\n'
'host=%s\n' 'host=%s\n'
'port=%s\n' 'port=%s\n'
'project=test/test_project.git') 'project=test/test_project.git\n'
'%s')
parsed = urlparse(self.project_uri) parsed = urlparse(self.project_uri)
host_port = parsed.netloc.rpartition('@')[-1] host_port = parsed.netloc.rpartition('@')[-1]
host, __, port = host_port.partition(':') host, __, port = host_port.partition(':')
cfg %= parsed.scheme, host, port extra = '\n'.join('%s=%s' % kv for kv in kwargs.items())
cfg %= parsed.scheme, host, port, extra
utils.write_to_file(self._dir('test', '.gitreview'), cfg.encode()) utils.write_to_file(self._dir('test', '.gitreview'), cfg.encode())

View File

@ -302,6 +302,35 @@ class GitReviewTestCase(tests.BaseGitReviewTestCase):
def test_git_review_F_R(self): def test_git_review_F_R(self):
self.assertRaises(Exception, self._run_git_review, '-F', '-R') self.assertRaises(Exception, self._run_git_review, '-F', '-R')
def test_get_config_from_cli(self):
self._run_git('remote', 'rm', 'origin')
self._run_git('remote', 'rm', 'gerrit')
self._create_gitreview_file(defaultremote='remote-file')
self._run_git('config', 'gitreview.remote', 'remote-gitconfig')
self._run_git_review('-s', '-r', 'remote-cli')
remote = self._run_git('remote').strip()
self.assertEqual('remote-cli', remote)
def test_get_config_from_gitconfig(self):
self._run_git('remote', 'rm', 'origin')
self._run_git('remote', 'rm', 'gerrit')
self._create_gitreview_file(defaultremote='remote-file')
self._run_git('config', 'gitreview.remote', 'remote-gitconfig')
self._run_git_review('-s')
remote = self._run_git('remote').strip()
self.assertEqual('remote-gitconfig', remote)
def test_get_config_from_file(self):
self._run_git('remote', 'rm', 'origin')
self._run_git('remote', 'rm', 'gerrit')
self._create_gitreview_file(defaultremote='remote-file')
self._run_git_review('-s')
remote = self._run_git('remote').strip()
self.assertEqual('remote-file', remote)
class HttpGitReviewTestCase(tests.HttpMixin, GitReviewTestCase): class HttpGitReviewTestCase(tests.HttpMixin, GitReviewTestCase):
"""Class for the git-review tests over HTTP(S).""" """Class for the git-review tests over HTTP(S)."""

View File

@ -46,7 +46,7 @@ class ConfigTestCase(testtools.TestCase):
mock.PropertyMock(return_value=True)) mock.PropertyMock(return_value=True))
@mock.patch('os.path.exists', return_value=False) @mock.patch('os.path.exists', return_value=False)
def test_gitreview_local_mode(self, exists_mock): def test_gitreview_local_mode(self, exists_mock):
cmd.get_config() cmd.Config()
self.assertFalse(exists_mock.called) self.assertFalse(exists_mock.called)