git-review-1.26.0/0000775000175100017510000000000013203667433013715 5ustar zuulzuul00000000000000git-review-1.26.0/.testr.conf0000666000175100017510000000052113203667220015775 0ustar zuulzuul00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ ./git_review/tests $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list git-review-1.26.0/HACKING.rst0000666000175100017510000002302713203667220015513 0ustar zuulzuul00000000000000Hacking git-review ================== Development of git-review is managed by OpenStack's Gerrit, which can be found at https://review.openstack.org/ Instructions on submitting patches can be found at http://docs.openstack.org/infra/manual/developers.html#development-workflow git-review should, in general, not depend on a huge number of external libraries, so that installing it is a lightweight operation. OpenStack Style Commandments ============================ - Step 1: Read http://www.python.org/dev/peps/pep-0008/ - Step 2: Read http://www.python.org/dev/peps/pep-0008/ again - Step 3: Read on General ------- - Put two newlines between top-level code (funcs, classes, etc) - Use only UNIX style newlines ("\n"), not Windows style ("\r\n") - Put one newline between methods in classes and anywhere else - Long lines should be wrapped in parentheses in preference to using a backslash for line continuation. - Do not write "except:", use "except Exception:" at the very least - Include your name with TODOs as in "#TODO(termie)" - Do not shadow a built-in or reserved word. Example:: def list(): return [1, 2, 3] mylist = list() # BAD, shadows `list` built-in class Foo(object): def list(self): return [1, 2, 3] mylist = Foo().list() # OKAY, does not shadow built-in - Use the "is not" operator when testing for unequal identities. Example:: if not X is Y: # BAD, intended behavior is ambiguous pass if X is not Y: # OKAY, intuitive pass - Use the "not in" operator for evaluating membership in a collection. Example:: if not X in Y: # BAD, intended behavior is ambiguous pass if X not in Y: # OKAY, intuitive pass if not (X in Y or X in Z): # OKAY, still better than all those 'not's pass Imports ------- - Do not import objects, only modules (*) - Do not import more than one module per line (*) - Do not use wildcard ``*`` import (*) - Do not make relative imports - Do not make new nova.db imports in nova/virt/* - Order your imports by the full module path - Organize your imports according to the following template (*) exceptions are: - imports from ``migrate`` package - imports from ``sqlalchemy`` package - imports from ``nova.db.sqlalchemy.session`` module - imports from ``nova.db.sqlalchemy.migration.versioning_api`` package Example:: # vim: tabstop=4 shiftwidth=4 softtabstop=4 {{stdlib imports in human alphabetical order}} \n {{third-party lib imports in human alphabetical order}} \n {{nova imports in human alphabetical order}} \n \n {{begin your code}} Human Alphabetical Order Examples --------------------------------- Example:: import httplib import logging import random import StringIO import time import unittest import eventlet import webob.exc import nova.api.ec2 from nova.api import openstack from nova.auth import users from nova.endpoint import cloud import nova.flags from nova import test Docstrings ---------- Example:: """A one line docstring looks like this and ends in a period.""" """A multi line docstring has a one-line summary, less than 80 characters. Then a new paragraph after a newline that explains in more detail any general information about the function, class or method. Example usages are also great to have here if it is a complex class or function. When writing the docstring for a class, an extra line should be placed after the closing quotations. For more in-depth explanations for these decisions see http://www.python.org/dev/peps/pep-0257/ If you are going to describe parameters and return values, use Sphinx, the appropriate syntax is as follows. :param foo: the foo parameter :param bar: the bar parameter :returns: return_type -- description of the return value :returns: description of the return value :raises: AttributeError, KeyError """ Dictionaries/Lists ------------------ If a dictionary (dict) or list object is longer than 80 characters, its items should be split with newlines. Embedded iterables should have their items indented. Additionally, the last item in the dictionary should have a trailing comma. This increases readability and simplifies future diffs. Example:: my_dictionary = { "image": { "name": "Just a Snapshot", "size": 2749573, "properties": { "user_id": 12, "arch": "x86_64", }, "things": [ "thing_one", "thing_two", ], "status": "ACTIVE", }, } Calling Methods --------------- Calls to methods 80 characters or longer should format each argument with newlines. This is not a requirement, but a guideline:: unnecessarily_long_function_name('string one', 'string two', kwarg1=constants.ACTIVE, kwarg2=['a', 'b', 'c']) Rather than constructing parameters inline, it is better to break things up:: list_of_strings = [ 'what_a_long_string', 'not as long', ] dict_of_numbers = { 'one': 1, 'two': 2, 'twenty four': 24, } object_one.call_a_method('string three', 'string four', kwarg1=list_of_strings, kwarg2=dict_of_numbers) Internationalization (i18n) Strings ----------------------------------- In order to support multiple languages, we have a mechanism to support automatic translations of exception and log strings. Example:: msg = _("An error occurred") raise HTTPBadRequest(explanation=msg) If you have a variable to place within the string, first internationalize the template string then do the replacement. Example:: msg = _("Missing parameter: %s") % ("flavor",) LOG.error(msg) If you have multiple variables to place in the string, use keyword parameters. This helps our translators reorder parameters when needed. Example:: msg = _("The server with id %(s_id)s has no key %(m_key)s") LOG.error(msg % {"s_id": "1234", "m_key": "imageId"}) Creating Unit Tests ------------------- For every new feature, unit tests should be created that both test and (implicitly) document the usage of said feature. If submitting a patch for a bug that had no unit test, a new passing unit test should be added. If a submitted bug fix does have a unit test, be sure to add a new one that fails without the patch and passes with the patch. For more information on creating unit tests and utilizing the testing infrastructure in OpenStack Nova, please read nova/tests/README.rst. Running Tests ------------- The testing system is based on a combination of tox and testr. The canonical approach to running tests is to simply run the command `tox`. This will create virtual environments, populate them with dependencies and run all of the tests that OpenStack CI systems run. Behind the scenes, tox is running `testr run --parallel`, but is set up such that you can supply any additional testr arguments that are needed to tox. For example, you can run: `tox -- --analyze-isolation` to cause tox to tell testr to add --analyze-isolation to its argument list. It is also possible to run the tests inside of a virtual environment you have created, or it is possible that you have all of the dependencies installed locally already. In this case, you can interact with the testr command directly. Running `testr run` will run the entire test suite. `testr run --parallel` will run it in parallel (this is the default incantation tox uses.) More information about testr can be found at: http://wiki.openstack.org/testr openstack-common ---------------- A number of modules from openstack-common are imported into the project. These modules are "incubating" in openstack-common and are kept in sync with the help of openstack-common's update.py script. See: http://wiki.openstack.org/CommonLibrary#Incubation The copy of the code should never be directly modified here. Please always update openstack-common first and then run the script to copy the changes across. OpenStack Trademark ------------------- OpenStack is a registered trademark of the OpenStack Foundation, and uses the following capitalization: OpenStack Commit Messages --------------- Using a common format for commit messages will help keep our git history readable. Follow these guidelines: First, provide a brief summary of 50 characters or less. Summaries of greater then 72 characters will be rejected by the gate. The first line of the commit message should provide an accurate description of the change, not just a reference to a bug or blueprint. It must be followed by a single blank line. If the change relates to a specific driver (libvirt, xenapi, qpid, etc...), begin the first line of the commit message with the driver name, lowercased, followed by a colon. Following your brief summary, provide a more detailed description of the patch, manually wrapping the text at 72 characters. This description should provide enough detail that one does not have to refer to external resources to determine its high-level functionality. Once you use 'git review', two lines will be appended to the commit message: a blank line followed by a 'Change-Id'. This is important to correlate this commit with a specific review in Gerrit, and it should not be modified. For further information on constructing high quality commit messages, and how to split up commits into a series of changes, consult the project wiki: http://wiki.openstack.org/GitCommitMessages git-review-1.26.0/MANIFEST.in0000666000175100017510000000017613203667220015453 0ustar zuulzuul00000000000000include README.rst include LICENSE include AUTHORS include ChangeLog include HACKING.rst include git-review.1 include tox.ini git-review-1.26.0/setup.py0000777000175100017510000000125313203667220015427 0ustar zuulzuul00000000000000#!/usr/bin/env python # Copyright (c) 2010-2011 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 setuptools setuptools.setup(setup_requires=['pbr'], pbr=True) git-review-1.26.0/.mailmap0000666000175100017510000000063013203667220015331 0ustar zuulzuul00000000000000# Format is: # # David Ostrovsky David Ostrovsky James E. Blair James E. Blair James E. Blair git-review-1.26.0/git_review/0000775000175100017510000000000013203667433016061 5ustar zuulzuul00000000000000git-review-1.26.0/git_review/tests/0000775000175100017510000000000013203667433017223 5ustar zuulzuul00000000000000git-review-1.26.0/git_review/tests/test_git_review.py0000666000175100017510000005566413203667220023014 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2013 Mirantis Inc. # # 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 json import os import shutil from git_review import tests from git_review.tests import utils class ConfigTestCase(tests.BaseGitReviewTestCase): """Class for config tests.""" def test_get_config_from_cli(self): self.reset_remote() self._run_git('remote', 'rm', 'origin') 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.reset_remote() self._run_git('remote', 'rm', 'origin') 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.reset_remote() self._run_git('remote', 'rm', 'origin') self._create_gitreview_file(defaultremote='remote-file') self._run_git_review('-s') remote = self._run_git('remote').strip() self.assertEqual('remote-file', remote) class GitReviewTestCase(tests.BaseGitReviewTestCase): """Class for the git-review tests.""" def test_cloned_repo(self): """Test git-review on the just cloned repository.""" self._simple_change('test file modified', 'test commit message') self.assertNotIn('Change-Id:', self._run_git('log', '-1')) self.assertIn('remote: New Changes:', self._run_git_review()) self.assertIn('Change-Id:', self._run_git('log', '-1')) def test_git_review_s(self): """Test git-review -s.""" self.reset_remote() self._run_git_review('-s') self._simple_change('test file modified', 'test commit message') self.assertIn('Change-Id:', self._run_git('log', '-1')) def test_git_review_s_in_detached_head(self): """Test git-review -s in detached HEAD state.""" self.reset_remote() master_sha1 = self._run_git('rev-parse', 'master') self._run_git('checkout', master_sha1) self._run_git_review('-s') self._simple_change('test file modified', 'test commit message') self.assertIn('Change-Id:', self._run_git('log', '-1')) def test_git_review_s_with_outdated_repo(self): """Test git-review -s with a outdated repo.""" self._simple_change('test file to outdate', 'test commit message 1') self._run_git('push', 'origin', 'master') self._run_git('reset', '--hard', 'HEAD^') # Review setup with an outdated repo self.reset_remote() self._run_git_review('-s') self._simple_change('test file modified', 'test commit message 2') self.assertIn('Change-Id:', self._run_git('log', '-1')) def test_git_review_s_from_subdirectory(self): """Test git-review -s from subdirectory.""" self.reset_remote() utils.run_cmd('mkdir', 'subdirectory', chdir=self.test_dir) self._run_git_review( '-s', chdir=os.path.join(self.test_dir, 'subdirectory')) def test_git_review_d(self): """Test git-review -d.""" self._run_git_review('-s') # create new review to be downloaded self._simple_change('test file modified', 'test commit message') self._run_git_review() change_id = self._run_git('log', '-1').split()[-1] shutil.rmtree(self.test_dir) # download clean Git repository and fresh change from Gerrit to it self._run_git('clone', self.project_uri) self.configure_gerrit_remote() self._run_git_review('-d', change_id) self.assertIn('test commit message', self._run_git('log', '-1')) # test backport branch self._run_git('checkout', '-b', 'mybackport', self._remote + '/' + 'testbranch') self._simple_change('test file modified in branch', 'test branch commit message\n\nChange-Id: %s' % change_id) self._run_git_review('testbranch') self._run_git('checkout', 'master') self._run_git_review('-d', change_id, 'testbranch') self.assertIn('test branch commit message', self._run_git('log', '-1')) # second download should also work correctly self._run_git('checkout', 'master') self._run_git_review('-d', change_id) self.assertIn('test commit message', self._run_git('show', 'HEAD')) self.assertNotIn('test commit message', self._run_git('show', 'HEAD^1')) # and branch is tracking head = self._run_git('symbolic-ref', '-q', 'HEAD') self.assertIn( 'refs/remotes/%s/master' % self._remote, self._run_git("for-each-ref", "--format='%(upstream)'", head)) def test_multiple_changes(self): """Test git-review asks about multiple changes. Should register user's wish to send two change requests by interactive 'yes' message and by the -y option. """ self._run_git_review('-s') # 'yes' message self._simple_change('test file modified 1st time', 'test commit message 1') self._simple_change('test file modified 2nd time', 'test commit message 2') review_res = self._run_git_review(confirm=True) self.assertIn("Type 'yes' to confirm", review_res) self.assertIn("Processing changes: new: 2", review_res) # abandon changes sent to the Gerrit head = self._run_git('rev-parse', 'HEAD') head_1 = self._run_git('rev-parse', 'HEAD^1') self._run_gerrit_cli('review', '--abandon', head) self._run_gerrit_cli('review', '--abandon', head_1) # -y option self._simple_change('test file modified 3rd time', 'test commit message 3') self._simple_change('test file modified 4th time', 'test commit message 4') review_res = self._run_git_review('-y') self.assertIn("Processing changes: new: 2", review_res) def test_git_review_re(self): """Test git-review adding reviewers to changes.""" self._run_git_review('-s') # Create users to add as reviewers self._run_gerrit_cli('create-account', '--email', 'reviewer1@example.com', 'reviewer1') self._run_gerrit_cli('create-account', '--email', 'reviewer2@example.com', 'reviewer2') self._simple_change('test file', 'test commit message') review_res = self._run_git_review('--reviewers', 'reviewer1', 'reviewer2') self.assertIn("Processing changes: new: 1", review_res) # verify both reviewers are on patch set head = self._run_git('rev-parse', 'HEAD') change = self._run_gerrit_cli('query', '--format=JSON', '--all-reviewers', head) # The first result should be the one we want change = json.loads(change.split('\n')[0]) self.assertEqual(2, len(change['allReviewers'])) reviewers = set() for reviewer in change['allReviewers']: reviewers.add(reviewer['username']) self.assertEqual(set(['reviewer1', 'reviewer2']), reviewers) def test_rebase_no_remote_branch_msg(self): """Test message displayed where no remote branch exists.""" self._run_git_review('-s') self._run_git('checkout', '-b', 'new_branch') self._simple_change('simple message', 'need to avoid noop message') exc = self.assertRaises(Exception, self._run_git_review, 'new_branch') self.assertIn("The branch 'new_branch' does not exist on the given " "remote '%s'" % self._remote, exc.args[0]) def test_need_rebase_no_upload(self): """Test change needing a rebase does not upload.""" self._run_git_review('-s') head_1 = self._run_git('rev-parse', 'HEAD^1') self._run_git('checkout', '-b', 'test_branch', head_1) self._simple_change('some other message', 'create conflict with master') exc = self.assertRaises(Exception, self._run_git_review) self.assertIn( "Errors running git rebase -p -i remotes/%s/master" % self._remote, exc.args[0]) self.assertIn("It is likely that your change has a merge conflict.", exc.args[0]) def test_upload_without_rebase(self): """Test change not needing a rebase can upload without rebasing.""" self._run_git_review('-s') head_1 = self._run_git('rev-parse', 'HEAD^1') self._run_git('checkout', '-b', 'test_branch', head_1) self._simple_change('some new message', 'just another file (no conflict)', self._dir('test', 'new_test_file.txt')) review_res = self._run_git_review('-v') self.assertIn( "Running: git rebase -p -i remotes/%s/master" % self._remote, review_res) self.assertEqual(self._run_git('rev-parse', 'HEAD^1'), head_1) def test_uploads_with_nondefault_rebase(self): """Test changes rebase against correct branches.""" # prepare maintenance branch that is behind master self._create_gitreview_file(track='true', defaultremote='origin') self._run_git('add', '.gitreview') self._run_git('commit', '-m', 'track=true.') self._simple_change('diverge master from maint', 'no conflict', self._dir('test', 'test_file_to_diverge.txt')) self._run_git('push', 'origin', 'master') self._run_git('push', 'origin', 'master', 'master:other') self._run_git_review('-s') head_1 = self._run_git('rev-parse', 'HEAD^1') self._run_gerrit_cli('create-branch', 'test/test_project', 'maint', head_1) self._run_git('fetch') br_out = self._run_git('checkout', '-b', 'test_branch', 'origin/maint') expected_track = 'Branch test_branch set up to track remote' + \ ' branch maint from origin.' self.assertIn(expected_track, br_out) branches = self._run_git('branch', '-a') expected_branch = '* test_branch' observed = branches.split('\n') self.assertIn(expected_branch, observed) self._simple_change('some new message', 'just another file (no conflict)', self._dir('test', 'new_tracked_test_file.txt')) change_id = self._run_git('log', '-1').split()[-1] review_res = self._run_git_review('-v') # no rebase needed; if it breaks it would try to rebase to master self.assertNotIn("Running: git rebase -p -i remotes/origin/master", review_res) # Don't need to query gerrit for the branch as the second half # of this test will work only if the branch was correctly # stored in gerrit # delete branch locally self._run_git('checkout', 'master') self._run_git('branch', '-D', 'test_branch') # download, amend, submit self._run_git_review('-d', change_id) self._simple_amend('just another file (no conflict)', self._dir('test', 'new_tracked_test_file_2.txt')) new_change_id = self._run_git('log', '-1').split()[-1] self.assertEqual(change_id, new_change_id) review_res = self._run_git_review('-v') # caused the right thing to happen self.assertIn("Running: git rebase -p -i remotes/origin/maint", review_res) # track different branch than expected in changeset branch = self._run_git('rev-parse', '--abbrev-ref', 'HEAD') self._run_git('branch', '--set-upstream-to', 'remotes/origin/other', branch) self.assertRaises( Exception, # cmd.BranchTrackingMismatch inside self._run_git_review, '-d', change_id) def test_no_rebase_check(self): """Test -R causes a change to be uploaded without rebase checking.""" self._run_git_review('-s') head_1 = self._run_git('rev-parse', 'HEAD^1') self._run_git('checkout', '-b', 'test_branch', head_1) self._simple_change('some new message', 'just another file', self._dir('test', 'new_test_file.txt')) review_res = self._run_git_review('-v', '-R') self.assertNotIn('rebase', review_res) self.assertEqual(self._run_git('rev-parse', 'HEAD^1'), head_1) def test_rebase_anyway(self): """Test -F causes a change to be rebased regardless.""" self._run_git_review('-s') head = self._run_git('rev-parse', 'HEAD') head_1 = self._run_git('rev-parse', 'HEAD^1') self._run_git('checkout', '-b', 'test_branch', head_1) self._simple_change('some new message', 'just another file', self._dir('test', 'new_test_file.txt')) review_res = self._run_git_review('-v', '-F') self.assertIn('rebase', review_res) self.assertEqual(self._run_git('rev-parse', 'HEAD^1'), head) def _assert_branch_would_be(self, branch, extra_args=None): extra_args = extra_args or [] output = self._run_git_review('-n', *extra_args) # last non-empty line should be: # git push gerrit HEAD:refs/publish/master last_line = output.strip().split('\n')[-1] branch_was = last_line.rsplit(' ', 1)[-1].split('/', 2)[-1] self.assertEqual(branch, branch_was) def test_detached_head(self): """Test on a detached state: we shouldn't have '(detached' as topic.""" self._run_git_review('-s') curr_branch = self._run_git('rev-parse', '--abbrev-ref', 'HEAD') # Note: git checkout --detach has been introduced in git 1.7.5 (2011) self._run_git('checkout', curr_branch + '^0') self._simple_change('some new message', 'just another file', self._dir('test', 'new_test_file.txt')) # switch to French, 'git branch' should return '(détaché du HEAD)' lang_env = os.getenv('LANG', 'C') os.environ.update(LANG='fr_FR.UTF-8') try: self._assert_branch_would_be(curr_branch) finally: os.environ.update(LANG=lang_env) def test_git_review_t(self): self._run_git_review('-s') self._simple_change('test file modified', 'commit message for bug 654') self._assert_branch_would_be('master/zat', extra_args=['-t', 'zat']) def test_bug_topic(self): self._run_git_review('-s') self._simple_change('a change', 'new change for bug 123') self._assert_branch_would_be('master/bug/123') def test_bug_topic_newline(self): self._run_git_review('-s') self._simple_change('a change', 'new change not for bug\n\n123') self._assert_branch_would_be('master') def test_bp_topic(self): self._run_git_review('-s') self._simple_change('a change', 'new change for blueprint asdf') self._assert_branch_would_be('master/bp/asdf') def test_bp_topic_newline(self): self._run_git_review('-s') self._simple_change('a change', 'new change not for blueprint\n\nasdf') self._assert_branch_would_be('master') def test_author_name_topic_bp(self): old_author = None if 'GIT_AUTHOR_NAME' in os.environ: old_author = os.environ['GIT_AUTHOR_NAME'] try: os.environ['GIT_AUTHOR_NAME'] = 'BPNAME' self._run_git_review('-s') self._simple_change('a change', 'new change 1 with name but no topic') self._assert_branch_would_be('master') finally: if old_author: os.environ['GIT_AUTHOR_NAME'] = old_author else: del os.environ['GIT_AUTHOR_NAME'] def test_author_email_topic_bp(self): old_author = None if 'GIT_AUTHOR_EMAIL' in os.environ: old_author = os.environ['GIT_AUTHOR_EMAIL'] try: os.environ['GIT_AUTHOR_EMAIL'] = 'bpemail@example.com' self._run_git_review('-s') self._simple_change('a change', 'new change 1 with email but no topic') self._assert_branch_would_be('master') finally: if old_author: os.environ['GIT_AUTHOR_EMAIL'] = old_author else: del os.environ['GIT_AUTHOR_EMAIL'] def test_author_name_topic_bug(self): old_author = None if 'GIT_AUTHOR_NAME' in os.environ: old_author = os.environ['GIT_AUTHOR_NAME'] try: os.environ['GIT_AUTHOR_NAME'] = 'Bug: #1234' self._run_git_review('-s') self._simple_change('a change', 'new change 2 with name but no topic') self._assert_branch_would_be('master') finally: if old_author: os.environ['GIT_AUTHOR_NAME'] = old_author else: del os.environ['GIT_AUTHOR_NAME'] def test_author_email_topic_bug(self): old_author = None if 'GIT_AUTHOR_EMAIL' in os.environ: old_author = os.environ['GIT_AUTHOR_EMAIL'] try: os.environ['GIT_AUTHOR_EMAIL'] = 'bug5678@example.com' self._run_git_review('-s') self._simple_change('a change', 'new change 2 with email but no topic') self._assert_branch_would_be('master') finally: if old_author: os.environ['GIT_AUTHOR_EMAIL'] = old_author else: del os.environ['GIT_AUTHOR_EMAIL'] def test_git_review_T(self): self._run_git_review('-s') self._simple_change('test file modified', 'commit message for bug 456') self._assert_branch_would_be('master/bug/456') self._assert_branch_would_be('master', extra_args=['-T']) def test_git_review_T_t(self): self.assertRaises(Exception, self._run_git_review, '-T', '-t', 'taz') def test_git_review_l(self): self._run_git_review('-s') # Populate "project" repo self._simple_change('project: test1', 'project: change1, merged') self._simple_change('project: test2', 'project: change2, open') self._simple_change('project: test3', 'project: change3, abandoned') self._run_git_review('-y') head = self._run_git('rev-parse', 'HEAD') head_2 = self._run_git('rev-parse', 'HEAD^^') self._run_gerrit_cli('review', head_2, '--code-review=+2', '--submit') self._run_gerrit_cli('review', head, '--abandon') # Populate "project2" repo self._run_gerrit_cli('create-project', '--empty-commit', '--name', 'test/test_project2') project2_uri = self.project_uri.replace('test/test_project', 'test/test_project2') self._run_git('fetch', project2_uri, 'HEAD') self._run_git('checkout', 'FETCH_HEAD') # We have to rewrite the .gitreview file after this checkout. self._create_gitreview_file() self._simple_change('project2: test1', 'project2: change1, open') self._run_git('push', project2_uri, 'HEAD:refs/for/master') # Only project1 open changes result = self._run_git_review('-l') self.assertNotIn('project: change1, merged', result) self.assertIn('project: change2, open', result) self.assertNotIn('project: change3, abandoned', result) self.assertNotIn('project2:', result) def _test_git_review_F(self, rebase): self._run_git_review('-s') # Populate repo self._simple_change('create file', 'test commit message') change1 = self._run_git('rev-parse', 'HEAD') self._run_git_review() self._run_gerrit_cli('review', change1, '--code-review=+2', '--submit') self._run_git('reset', '--hard', 'HEAD^') # Review with force_rebase self._run_git('config', 'gitreview.rebase', rebase) self._simple_change('create file2', 'test commit message 2', self._dir('test', 'test_file2.txt')) self._run_git_review('-F') head_1 = self._run_git('rev-parse', 'HEAD^') self.assertEqual(change1, head_1) def test_git_review_F(self): self._test_git_review_F('1') def test_git_review_F_norebase(self): self._test_git_review_F('0') def test_git_review_F_R(self): self.assertRaises(Exception, self._run_git_review, '-F', '-R') def test_config_instead_of_honored(self): self.set_remote('test_project_url') self.assertRaises(Exception, self._run_git_review, '-l') self._run_git('config', '--add', 'url.%s.insteadof' % self.project_uri, 'test_project_url') self._run_git_review('-l') def test_config_pushinsteadof_honored(self): self.set_remote('test_project_url') self.assertRaises(Exception, self._run_git_review, '-l') self._run_git('config', '--add', 'url.%s.pushinsteadof' % self.project_uri, 'test_project_url') self._run_git_review('-l') class PushUrlTestCase(GitReviewTestCase): """Class for the git-review tests using origin push-url.""" _remote = 'origin' def set_remote(self, uri): self._run_git('remote', 'set-url', '--push', self._remote, uri) def reset_remote(self): self._run_git('config', '--unset', 'remote.%s.pushurl' % self._remote) def configure_gerrit_remote(self): self.set_remote(self.project_uri) self._run_git('config', 'gitreview.usepushurl', '1') def test_config_pushinsteadof_honored(self): self.skipTest("pushinsteadof doesn't rewrite pushurls") class HttpGitReviewTestCase(tests.HttpMixin, GitReviewTestCase): """Class for the git-review tests over HTTP(S).""" pass git-review-1.26.0/git_review/tests/utils.py0000666000175100017510000000456213203667220020740 0ustar zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # 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 os import subprocess import traceback def run_cmd(*args, **kwargs): """Run command and check the return code.""" preexec_fn = None if 'chdir' in kwargs: def preexec_fn(): return os.chdir(kwargs['chdir']) try: proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=os.environ, preexec_fn=preexec_fn) if 'confirm' in kwargs and kwargs['confirm']: proc.stdin.write('yes'.encode()) proc.stdin.flush() out, err = proc.communicate() out = out.decode('utf-8') except Exception: raise Exception( "Exception while processing the command:\n%s.\n%s" % (' '.join(args), traceback.format_exc()) ) if proc.returncode != 0: raise Exception( "Error occurred while processing the command:\n%s.\n" "Stdout: %s\nStderr: %s" % (' '.join(args), out.strip(), err) ) return out.strip() def run_git(command, *args, **kwargs): """Run git command with the specified args.""" return run_cmd("git", command, *args, **kwargs) def write_to_file(path, content): """Create (if does not exist) and write to the file.""" with open(path, 'wb') as file_: file_.write(content) GERRIT_CONF_TMPL = """ [gerrit] basePath = git canonicalWebUrl = http://nonexistent/ [database] type = h2 database = db/ReviewDB [auth] type = DEVELOPMENT_BECOME_ANY_ACCOUNT [sshd] listenAddress = %s:%s [httpd] listenUrl = http://%s:%s/ """ def get_gerrit_conf(ssh_addr, ssh_port, http_addr, http_port): return GERRIT_CONF_TMPL % (ssh_addr, ssh_port, http_addr, http_port) git-review-1.26.0/git_review/tests/prepare.py0000666000175100017510000000141713203667220021232 0ustar zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # 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. from git_review import tests def main(): helpers = tests.GerritHelpers() helpers.init_dirs() helpers.ensure_gerrit_war() helpers.init_gerrit() if __name__ == "__main__": main() git-review-1.26.0/git_review/tests/test_unit.py0000666000175100017510000003253713203667220021621 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # 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 functools import os import textwrap import fixtures import mock import testtools from git_review import cmd from git_review.tests import IsoEnvDir from git_review.tests import utils # Use of io.StringIO in python =< 2.7 requires all strings handled to be # unicode. See if StringIO.StringIO is available first try: import StringIO as io except ImportError: import io class ConfigTestCase(testtools.TestCase): """Class testing config behavior.""" @mock.patch('git_review.cmd.LOCAL_MODE', mock.PropertyMock(return_value=True)) @mock.patch('git_review.cmd.git_directories', return_value=['', 'fake']) @mock.patch('git_review.cmd.run_command_exc') def test_git_local_mode(self, run_mock, dir_mock): cmd.git_config_get_value('abc', 'def') run_mock.assert_called_once_with( cmd.GitConfigException, 'git', 'config', '-f', 'fake/config', '--get', 'abc.def') @mock.patch('git_review.cmd.LOCAL_MODE', mock.PropertyMock(return_value=True)) @mock.patch('os.path.exists', return_value=False) def test_gitreview_local_mode(self, exists_mock): cmd.Config() self.assertFalse(exists_mock.called) class GitReviewConsole(testtools.TestCase, fixtures.TestWithFixtures): """Class for testing the console output of git-review.""" reviews = [ { 'number': '1010101', 'branch': 'master', 'subject': 'A simple short subject', 'topic': 'simple-topic' }, { 'number': 9877, # Starting with 2.14, numbers are sent as int 'branch': 'stable/codeword', 'subject': 'A longer and slightly more wordy subject' }, { 'number': '12345', 'branch': 'master', 'subject': 'A ridiculously long subject that can exceed the ' 'normal console width, just need to ensure the ' 'max width is short enough' }] def setUp(self): # will set up isolated env dir super(GitReviewConsole, self).setUp() # ensure all tests get a separate git dir to work in to avoid # local git config from interfering iso_env = self.useFixture(IsoEnvDir()) self._run_git = functools.partial(utils.run_git, chdir=iso_env.work_dir) self.run_cmd_patcher = mock.patch('git_review.cmd.run_command_status') run_cmd_partial = functools.partial( cmd.run_command_status, GIT_WORK_TREE=iso_env.work_dir, GIT_DIR=os.path.join(iso_env.work_dir, '.git')) self.run_cmd_mock = self.run_cmd_patcher.start() self.run_cmd_mock.side_effect = run_cmd_partial self._run_git('init') self._run_git('commit', '--allow-empty', '-m "initial commit"') self._run_git('commit', '--allow-empty', '-m "2nd commit"') def tearDown(self): self.run_cmd_patcher.stop() super(GitReviewConsole, self).tearDown() @mock.patch('git_review.cmd.query_reviews') @mock.patch('git_review.cmd.get_remote_url', mock.MagicMock) @mock.patch('git_review.cmd._has_color', False) def test_list_reviews_output(self, mock_query): mock_query.return_value = self.reviews with mock.patch('sys.stdout', new_callable=io.StringIO) as output: cmd.list_reviews(None, None) console_output = output.getvalue().split('\n') self.assertEqual( ['1010101 master A simple short subject', ' 9877 stable/codeword A longer and slightly more wordy ' 'subject'], console_output[:2]) @mock.patch('git_review.cmd.query_reviews') @mock.patch('git_review.cmd.get_remote_url', mock.MagicMock) @mock.patch('git_review.cmd._has_color', False) def test_list_reviews_output_with_topic(self, mock_query): mock_query.return_value = self.reviews with mock.patch('sys.stdout', new_callable=io.StringIO) as output: cmd.list_reviews(None, None, with_topic=True) console_output = output.getvalue().split('\n') self.assertEqual( ['1010101 master simple-topic A simple short subject', ' 9877 stable/codeword - A longer and slightly ' 'more wordy subject'], console_output[:2]) @mock.patch('git_review.cmd.query_reviews') @mock.patch('git_review.cmd.get_remote_url', mock.MagicMock) @mock.patch('git_review.cmd._has_color', False) def test_list_reviews_no_blanks(self, mock_query): mock_query.return_value = self.reviews with mock.patch('sys.stdout', new_callable=io.StringIO) as output: cmd.list_reviews(None, None) console_output = output.getvalue().split('\n') wrapper = textwrap.TextWrapper(replace_whitespace=False, drop_whitespace=False) for text in console_output: for line in wrapper.wrap(text): self.assertEqual(line.isspace(), False, "Extra blank lines appearing between reviews" "in console output") @mock.patch('git_review.cmd._use_color', None) def test_color_output_disabled(self): """Test disabling of colour output color.ui defaults to enabled """ # git versions < 1.8.4 default to 'color.ui' being false # so must be set to auto to correctly test self._run_git("config", "color.ui", "auto") self._run_git("config", "color.review", "never") self.assertFalse(cmd.check_use_color_output(), "Failed to detect color output disabled") @mock.patch('git_review.cmd._use_color', None) def test_color_output_forced(self): """Test force enable of colour output when color.ui is defaulted to false """ self._run_git("config", "color.ui", "never") self._run_git("config", "color.review", "always") self.assertTrue(cmd.check_use_color_output(), "Failed to detect color output forcefully " "enabled") @mock.patch('git_review.cmd._use_color', None) def test_color_output_fallback(self): """Test fallback to using color.ui when color.review is not set """ self._run_git("config", "color.ui", "always") self.assertTrue(cmd.check_use_color_output(), "Failed to use fallback to color.ui when " "color.review not present") class FakeResponse(object): def __init__(self, code, text=""): self.status_code = code self.text = text class FakeException(Exception): def __init__(self, code, *args, **kwargs): super(FakeException, self).__init__(*args, **kwargs) self.code = code FAKE_GIT_CREDENTIAL_FILL = """\ protocol=http host=gerrit.example.com username=user password=pass """ class ResolveTrackingUnitTest(testtools.TestCase): """Class for testing resolve_tracking.""" def setUp(self): testtools.TestCase.setUp(self) patcher = mock.patch('git_review.cmd.run_command_exc') self.addCleanup(patcher.stop) self.run_command_exc = patcher.start() def test_track_local_branch(self): 'Test that local tracked branch is not followed.' self.run_command_exc.side_effect = [ '', 'refs/heads/other/branch', ] self.assertEqual(cmd.resolve_tracking(u'remote', u'rbranch'), (u'remote', u'rbranch')) def test_track_untracked_branch(self): 'Test that local untracked branch is not followed.' self.run_command_exc.side_effect = [ '', '', ] self.assertEqual(cmd.resolve_tracking(u'remote', u'rbranch'), (u'remote', u'rbranch')) def test_track_remote_branch(self): 'Test that remote tracked branch is followed.' self.run_command_exc.side_effect = [ '', 'refs/remotes/other/branch', ] self.assertEqual(cmd.resolve_tracking(u'remote', u'rbranch'), (u'other', u'branch')) def test_track_git_error(self): 'Test that local tracked branch is not followed.' self.run_command_exc.side_effect = [cmd.CommandFailed(1, '', [], {})] self.assertRaises(cmd.CommandFailed, cmd.resolve_tracking, u'remote', u'rbranch') class GitReviewUnitTest(testtools.TestCase): """Class for misc unit tests.""" @mock.patch('requests.get', return_value=FakeResponse(404)) def test_run_http_exc_raise_http_error(self, mock_get): url = 'http://gerrit.example.com' try: cmd.run_http_exc(FakeException, url) self.fails('Exception expected') except FakeException as err: self.assertEqual(cmd.http_code_2_return_code(404), err.code) mock_get.assert_called_once_with(url) @mock.patch('requests.get', side_effect=Exception()) def test_run_http_exc_raise_unknown_error(self, mock_get): url = 'http://gerrit.example.com' try: cmd.run_http_exc(FakeException, url) self.fails('Exception expected') except FakeException as err: self.assertEqual(255, err.code) mock_get.assert_called_once_with(url) @mock.patch('git_review.cmd.run_command_status') @mock.patch('requests.get', return_value=FakeResponse(200)) def test_run_http_exc_without_auth(self, mock_get, mock_run): url = 'http://user@gerrit.example.com' cmd.run_http_exc(FakeException, url) self.assertFalse(mock_run.called) mock_get.assert_called_once_with(url) @mock.patch('git_review.cmd.run_command_status', return_value=(0, FAKE_GIT_CREDENTIAL_FILL)) @mock.patch('requests.get', side_effect=[FakeResponse(401), FakeResponse(200)]) def test_run_http_exc_with_auth(self, mock_get, mock_run): url = 'http://user@gerrit.example.com' cmd.run_http_exc(FakeException, url) # This gets encoded to utf8 which means the type passed down # is bytes. mock_run.assert_called_once_with('git', 'credential', 'fill', stdin=b'url=%s' % url.encode('utf-8')) calls = [mock.call(url), mock.call(url, auth=('user', 'pass'))] mock_get.assert_has_calls(calls) @mock.patch('git_review.cmd.run_command_status', return_value=(0, FAKE_GIT_CREDENTIAL_FILL)) @mock.patch('requests.get', return_value=FakeResponse(401)) def test_run_http_exc_with_failing_auth(self, mock_get, mock_run): url = 'http://user@gerrit.example.com' try: cmd.run_http_exc(FakeException, url) self.fails('Exception expected') except FakeException as err: self.assertEqual(cmd.http_code_2_return_code(401), err.code) # This gets encoded to utf8 which means the type passed down # is bytes. mock_run.assert_called_once_with('git', 'credential', 'fill', stdin=b'url=%s' % url.encode('utf-8')) calls = [mock.call(url), mock.call(url, auth=('user', 'pass'))] mock_get.assert_has_calls(calls) @mock.patch('git_review.cmd.run_command_status', return_value=(1, '')) @mock.patch('requests.get', return_value=FakeResponse(401)) def test_run_http_exc_with_failing_git_creds(self, mock_get, mock_run): url = 'http://user@gerrit.example.com' try: cmd.run_http_exc(FakeException, url) self.fails('Exception expected') except FakeException as err: self.assertEqual(cmd.http_code_2_return_code(401), err.code) # This gets encoded to utf8 which means the type passed down # is bytes. mock_run.assert_called_once_with('git', 'credential', 'fill', stdin=b'url=%s' % url.encode('utf-8')) mock_get.assert_called_once_with(url) @mock.patch('sys.argv', ['argv0', '--track', 'branch']) @mock.patch('git_review.cmd.check_remote') @mock.patch('git_review.cmd.resolve_tracking') def test_command_line_no_track(self, resolve_tracking, check_remote): check_remote.side_effect = Exception() self.assertRaises(Exception, cmd._main) self.assertFalse(resolve_tracking.called) @mock.patch('sys.argv', ['argv0', '--track']) @mock.patch('git_review.cmd.check_remote') @mock.patch('git_review.cmd.resolve_tracking') def test_track(self, resolve_tracking, check_remote): check_remote.side_effect = Exception() self.assertRaises(Exception, cmd._main) self.assertTrue(resolve_tracking.called) git-review-1.26.0/git_review/tests/__init__.py0000666000175100017510000003771713203667220021347 0ustar zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # 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 hashlib import os import shutil import stat import struct import sys if sys.version < '3': import urllib import urlparse urlparse = urlparse.urlparse else: import urllib.parse import urllib.request urlparse = urllib.parse.urlparse import fixtures import requests import testtools from testtools import content from git_review.tests import utils WAR_URL = 'http://tarballs.openstack.org/' \ 'ci/gerrit/gerrit-v2.11.4.13.cb9800e.war' # Update GOLDEN_SITE_VER for every change altering golden site, including # WAR_URL changes. Set new value to something unique (just +1 it for example) GOLDEN_SITE_VER = '4' # NOTE(yorik-sar): This function needs to be a perfect hash function for # existing test IDs. This is verified by running check_test_id_hashes script # prior to running tests. Note that this doesn't imply any cryptographic # requirements for underlying algorithm, so we can use weak hash here. # Range of results for this function is limited by port numbers selection # in _pick_gerrit_port_and_dir method (it can't be greater than 10000 now). def _hash_test_id(test_id): if not isinstance(test_id, bytes): test_id = test_id.encode('utf-8') hash_ = hashlib.md5(test_id).digest() num = struct.unpack("=I", hash_[:4])[0] return num % 10000 class DirHelpers(object): def _dir(self, base, *args): """Creates directory name from base name and other parameters.""" return os.path.join(getattr(self, base + '_dir'), *args) class IsoEnvDir(DirHelpers, fixtures.Fixture): """Created isolated env and associated directories""" def __init__(self, base_dir=None): super(IsoEnvDir, self).setUp() # set up directories for isolation if base_dir: self.base_dir = base_dir self.temp_dir = self._dir('base', 'tmp') if not os.path.exists(self.temp_dir): os.mkdir(self.temp_dir) self.addCleanup(shutil.rmtree, self.temp_dir) else: self.temp_dir = self.useFixture(fixtures.TempDir()).path self.work_dir = self._dir('temp', 'work') self.home_dir = self._dir('temp', 'home') self.xdg_config_dir = self._dir('home', '.xdgconfig') for path in [self.home_dir, self.xdg_config_dir, self.work_dir]: if not os.path.exists(path): os.mkdir(path) # ensure user proxy conf doesn't interfere with tests self.useFixture(fixtures.EnvironmentVariable('no_proxy', '*')) self.useFixture(fixtures.EnvironmentVariable('NO_PROXY', '*')) # isolate tests from user and system git configuration self.useFixture(fixtures.EnvironmentVariable('HOME', self.home_dir)) self.useFixture( fixtures.EnvironmentVariable('XDG_CONFIG_HOME', self.xdg_config_dir)) self.useFixture( fixtures.EnvironmentVariable('GIT_CONFIG_NOSYSTEM', "1")) self.useFixture( fixtures.EnvironmentVariable('EMAIL', "you@example.com")) self.useFixture( fixtures.EnvironmentVariable('GIT_AUTHOR_NAME', "gitreview tester")) self.useFixture( fixtures.EnvironmentVariable('GIT_COMMITTER_NAME', "gitreview tester")) class GerritHelpers(DirHelpers): def init_dirs(self): self.primary_dir = os.path.abspath(os.path.curdir) self.gerrit_dir = self._dir('primary', '.gerrit') self.gsite_dir = self._dir('gerrit', 'golden_site') self.gerrit_war = self._dir('gerrit', WAR_URL.split('/')[-1]) def ensure_gerrit_war(self): # check if gerrit.war file exists in .gerrit directory if not os.path.exists(self.gerrit_dir): os.mkdir(self.gerrit_dir) if not os.path.exists(self.gerrit_war): print("Downloading Gerrit binary from %s..." % WAR_URL) resp = requests.get(WAR_URL) if resp.status_code != 200: raise RuntimeError("Problem requesting Gerrit war") utils.write_to_file(self.gerrit_war, resp.content) print("Saved to %s" % self.gerrit_war) def init_gerrit(self): """Run Gerrit from the war file and configure it.""" golden_ver_file = self._dir('gsite', 'golden_ver') if os.path.exists(self.gsite_dir): if not os.path.exists(golden_ver_file): golden_ver = '0' else: with open(golden_ver_file) as f: golden_ver = f.read().strip() if GOLDEN_SITE_VER != golden_ver: print("Existing golden site has version %s, removing..." % golden_ver) shutil.rmtree(self.gsite_dir) else: print("Golden site of version %s already exists" % GOLDEN_SITE_VER) return # We write out the ssh host key for gerrit's ssh server which # for undocumented reasons forces gerrit init to download the # bouncy castle libs which we need for ssh that works on # newer distros like ubuntu xenial. os.makedirs(self._dir('gsite', 'etc')) # create SSH host key host_key_file = self._dir('gsite', 'etc', 'ssh_host_rsa_key') utils.run_cmd('ssh-keygen', '-t', 'rsa', '-b', '4096', '-f', host_key_file, '-N', '') print("Creating a new golden site of version " + GOLDEN_SITE_VER) # initialize Gerrit utils.run_cmd('java', '-jar', self.gerrit_war, 'init', '-d', self.gsite_dir, '--batch', '--no-auto-start', '--install-plugin', 'download-commands') utils.run_cmd('java', '-jar', self.gerrit_war, 'reindex', '-d', self.gsite_dir) with open(golden_ver_file, 'w') as f: f.write(GOLDEN_SITE_VER) # create SSH public key key_file = self._dir('gsite', 'test_ssh_key') utils.run_cmd('ssh-keygen', '-t', 'rsa', '-b', '4096', '-f', key_file, '-N', '') with open(key_file + '.pub', 'rb') as pub_key_file: pub_key = pub_key_file.read() # create admin user in Gerrit database sql_query = """INSERT INTO ACCOUNTS (REGISTERED_ON) VALUES (NOW()); INSERT INTO ACCOUNT_GROUP_MEMBERS (ACCOUNT_ID, GROUP_ID) \ VALUES (0, 1); INSERT INTO ACCOUNT_EXTERNAL_IDS (ACCOUNT_ID, EXTERNAL_ID, PASSWORD) \ VALUES (0, 'username:test_user', 'test_pass'); INSERT INTO ACCOUNT_SSH_KEYS (SSH_PUBLIC_KEY, VALID) \ VALUES ('%s', 'Y')""" % pub_key.decode() utils.run_cmd('java', '-jar', self._dir('gsite', 'bin', 'gerrit.war'), 'gsql', '-d', self.gsite_dir, '-c', sql_query) def _run_gerrit_cli(self, command, *args): """SSH to gerrit Gerrit server and run command there.""" return utils.run_cmd('ssh', '-p', str(self.gerrit_port), 'test_user@' + self.gerrit_host, 'gerrit', command, *args) def _run_git_review(self, *args, **kwargs): """Run git-review utility from source.""" git_review = utils.run_cmd('which', 'git-review') kwargs.setdefault('chdir', self.test_dir) return utils.run_cmd(git_review, *args, **kwargs) class BaseGitReviewTestCase(testtools.TestCase, GerritHelpers): """Base class for the git-review tests.""" _remote = 'gerrit' @property def project_uri(self): return self.project_ssh_uri def setUp(self): """Configure testing environment. Prepare directory for the testing and clone test Git repository. Require Gerrit war file in the .gerrit directory to run Gerrit local. """ super(BaseGitReviewTestCase, self).setUp() self.useFixture(fixtures.Timeout(2 * 60, True)) # ensures git-review command runs in local mode (for functional tests) self.useFixture( fixtures.EnvironmentVariable('GITREVIEW_LOCAL_MODE', '')) self.init_dirs() ssh_addr, ssh_port, http_addr, http_port, self.site_dir = \ self._pick_gerrit_port_and_dir() self.gerrit_host, self.gerrit_port = ssh_addr, ssh_port self.test_dir = self._dir('site', 'tmp', 'test_project') self.ssh_dir = self._dir('site', 'tmp', 'ssh') self.project_ssh_uri = ( 'ssh://test_user@%s:%s/test/test_project.git' % ( ssh_addr, ssh_port)) self.project_http_uri = ( 'http://test_user:test_pass@%s:%s/test/test_project.git' % ( http_addr, http_port)) self._run_gerrit(ssh_addr, ssh_port, http_addr, http_port) self._configure_ssh(ssh_addr, ssh_port) # create Gerrit empty project self._run_gerrit_cli('create-project', '--empty-commit', '--name', 'test/test_project') # setup isolated area to work under self.useFixture(IsoEnvDir(self.site_dir)) # prepare repository for the testing self._run_git('clone', self.project_uri) utils.write_to_file(self._dir('test', 'test_file.txt'), 'test file created'.encode()) self._create_gitreview_file() # push changes to the Gerrit self._run_git('add', '--all') self._run_git('commit', '-m', 'Test file and .gitreview added.') self._run_git('push', 'origin', 'master') # push a branch to gerrit self._run_git('checkout', '-b', 'testbranch') utils.write_to_file(self._dir('test', 'test_file.txt'), 'test file branched'.encode()) self._create_gitreview_file(defaultbranch='testbranch') self._run_git('add', '--all') self._run_git('commit', '-m', 'Branched.') self._run_git('push', 'origin', 'testbranch') # cleanup shutil.rmtree(self.test_dir) # go to the just cloned test Git repository self._run_git('clone', self.project_uri) self.configure_gerrit_remote() self.addCleanup(shutil.rmtree, self.test_dir) # ensure user is configured for all tests self._configure_gitreview_username() def set_remote(self, uri): self._run_git('remote', 'set-url', self._remote, uri) def reset_remote(self): self._run_git('remote', 'rm', self._remote) def attach_on_exception(self, filename): @self.addOnException def attach_file(exc_info): if os.path.exists(filename): content.attach_file(self, filename) else: self.addDetail(os.path.basename(filename), content.text_content('Not found')) def _run_git(self, command, *args): """Run git command using test git directory.""" if command == 'clone': return utils.run_git(command, args[0], self._dir('test')) return utils.run_git('--git-dir=' + self._dir('test', '.git'), '--work-tree=' + self._dir('test'), command, *args) def _run_gerrit(self, ssh_addr, ssh_port, http_addr, http_port): # create a copy of site dir shutil.copytree(self.gsite_dir, self.site_dir) self.addCleanup(shutil.rmtree, self.site_dir) # write config with open(self._dir('site', 'etc', 'gerrit.config'), 'w') as _conf: new_conf = utils.get_gerrit_conf( ssh_addr, ssh_port, http_addr, http_port) _conf.write(new_conf) # If test fails, attach Gerrit config and logs to the result self.attach_on_exception(self._dir('site', 'etc', 'gerrit.config')) for name in ['error_log', 'sshd_log', 'httpd_log']: self.attach_on_exception(self._dir('site', 'logs', name)) # start Gerrit gerrit_sh = self._dir('site', 'bin', 'gerrit.sh') utils.run_cmd(gerrit_sh, 'start') self.addCleanup(utils.run_cmd, gerrit_sh, 'stop') def _simple_change(self, change_text, commit_message, file_=None): """Helper method to create small changes and commit them.""" if file_ is None: file_ = self._dir('test', 'test_file.txt') utils.write_to_file(file_, change_text.encode()) self._run_git('add', file_) self._run_git('commit', '-m', commit_message) def _simple_amend(self, change_text, file_=None): """Helper method to amend existing commit with change.""" if file_ is None: file_ = self._dir('test', 'test_file_new.txt') utils.write_to_file(file_, change_text.encode()) self._run_git('add', file_) # cannot use --no-edit because it does not exist in older git message = self._run_git('log', '-1', '--format=%s\n\n%b') self._run_git('commit', '--amend', '-m', message) def _configure_ssh(self, ssh_addr, ssh_port): """Setup ssh and scp to run with special options.""" os.mkdir(self.ssh_dir) ssh_key = utils.run_cmd('ssh-keyscan', '-p', str(ssh_port), ssh_addr) utils.write_to_file(self._dir('ssh', 'known_hosts'), ssh_key.encode()) self.addCleanup(os.remove, self._dir('ssh', 'known_hosts')) # Attach known_hosts to test results if anything fails self.attach_on_exception(self._dir('ssh', 'known_hosts')) for cmd in ('ssh', 'scp'): cmd_file = self._dir('ssh', cmd) s = '#!/bin/sh\n' \ '/usr/bin/%s -i %s -o UserKnownHostsFile=%s ' \ '-o IdentitiesOnly=yes ' \ '-o PasswordAuthentication=no $@' % \ (cmd, self._dir('gsite', 'test_ssh_key'), self._dir('ssh', 'known_hosts')) utils.write_to_file(cmd_file, s.encode()) os.chmod(cmd_file, os.stat(cmd_file).st_mode | stat.S_IEXEC) os.environ['PATH'] = self.ssh_dir + os.pathsep + os.environ['PATH'] os.environ['GIT_SSH'] = self._dir('ssh', 'ssh') def configure_gerrit_remote(self): self._run_git('remote', 'add', self._remote, self.project_uri) def _configure_gitreview_username(self): self._run_git('config', 'gitreview.username', 'test_user') def _pick_gerrit_port_and_dir(self): hash_ = _hash_test_id(self.id()) host = '127.0.0.1' return ( host, 12000 + hash_, # avoid 11211 that is memcached port on CI host, 22000 + hash_, # avoid ephemeral ports at 32678+ self._dir('gerrit', 'site-' + str(hash_)), ) def _create_gitreview_file(self, **kwargs): cfg = ('[gerrit]\n' 'scheme=%s\n' 'host=%s\n' 'port=%s\n' 'project=test/test_project.git\n' '%s') parsed = urlparse(self.project_uri) host_port = parsed.netloc.rpartition('@')[-1] host, __, port = host_port.partition(':') 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()) class HttpMixin(object): """HTTP remote_url mixin.""" @property def project_uri(self): return self.project_http_uri def _configure_gitreview_username(self): # trick to set http password self._run_git('config', 'gitreview.username', 'test_user:test_pass') git-review-1.26.0/git_review/tests/check_test_id_hashes.py0000666000175100017510000000316613203667220023722 0ustar zuulzuul00000000000000# Copyright (c) 2013 Mirantis Inc. # # 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. from __future__ import print_function import sys from git_review import tests from git_review.tests import utils def list_test_ids(argv): res = utils.run_cmd(sys.executable, '-m', 'testtools.run', *argv[1:]) return res.split('\n') def find_collisions(test_ids): hashes = {} for test_id in test_ids: hash_ = tests._hash_test_id(test_id) if hash_ in hashes: return (hashes[hash_], test_id) hashes[hash_] = test_id return None def main(argv): test_ids = list_test_ids(argv) if not test_ids: print("No tests found, check command line arguments", file=sys.stderr) return 1 collision = find_collisions(test_ids) if collision is None: return 0 print( "Found a collision for test ids hash function: %s and %s\n" "You should change _hash_test_id function in" " git_review/tests/__init__.py module to fit new set of test ids." % collision, file=sys.stderr, ) return 2 if __name__ == "__main__": sys.exit(main(sys.argv)) git-review-1.26.0/git_review/cmd.py0000777000175100017510000016144113203667234017211 0ustar zuulzuul00000000000000#!/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 getpass import json import os import re import shlex import subprocess import sys import textwrap import pkg_resources import requests 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(scheme='ssh', hostname=False, port=None, project=False, branch='master', remote="gerrit", rebase="1", track="0", usepushurl="0") _branch_name = None _has_color = None _use_color = None _orig_head = None _rewrites = None _rewrites_push = None class colors(object): yellow = '\033[33m' green = '\033[92m' reset = '\033[0m' blue = '\033[36m' class GitReviewException(Exception): EXIT_CODE = 127 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 printwrap(unwrapped): print('\n'.join(textwrap.wrap(unwrapped))) def warn(warning): printwrap("WARNING: %s" % warning) 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, **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, universal_newlines=True) (out, nothing) = p.communicate(stdin) 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 git_credentials(url): """Return credentials using git credential or None.""" cmd = 'git', 'credential', 'fill' stdin = 'url=%s' % url rc, out = run_command_status(*cmd, stdin=stdin.encode('utf-8')) if rc: return None data = dict(l.split('=', 1) for l in out.splitlines()) return data['username'], data['password'] def http_code_2_return_code(code): """Tranform http status code to system return code.""" return (code - 301) % 255 + 1 def run_http_exc(klazz, url, **env): """Run http GET request url, on failure raise klazz klazz should be derived from CommandFailed """ if url.startswith("https://") and "verify" not in env: if "GIT_SSL_NO_VERIFY" in os.environ: env["verify"] = False else: verify = git_config_get_value("http", "sslVerify", as_bool=True) env["verify"] = verify != 'false' try: res = requests.get(url, **env) if res.status_code == 401: creds = git_credentials(url) if creds: env['auth'] = creds res = requests.get(url, **env) except klazz: raise except Exception as err: raise klazz(255, str(err), ('GET', url), env) if not 200 <= res.status_code < 300: raise klazz(http_code_2_return_code(res.status_code), res.text, ('GET', url), env) return res def get_version(): requirement = pkg_resources.Requirement.parse('git-review') 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, {}) 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, 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: result = run_command_exc(GitConfigException, *cmd).strip() if VERBOSE: print(datetime.datetime.now(), "result:", result) return result except GitConfigException as exc: if exc.rc == 1: if VERBOSE: print(datetime.datetime.now(), "using default:", default) 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 and will be removed in a future " "release") warn(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): """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) if not os.path.exists(target_file) or UPDATE: remote_url = get_remote_url(remote) if (remote_url.startswith('http://') or remote_url.startswith('https://')): hook_url = urljoin(remote_url, '/tools/hooks/commit-msg') if VERBOSE: print("Fetching commit hook from: %s" % hook_url) res = run_http_exc(CannotInstallHook, hook_url, stream=True) with open(target_file, 'wb') as f: for x in res.iter_content(1024): f.write(x) else: (hostname, username, port, project_name) = \ parse_gerrit_ssh_params_from_git_url(remote_url) if username is None: userhost = hostname else: userhost = "%s@%s" % (username, hostname) # OS independent target file scp_target_file = target_file.replace(os.sep, "/") cmd = ["scp", userhost + ":hooks/commit-msg", scp_target_file] if port is not None: cmd.insert(1, "-P%s" % port) if VERBOSE: hook_url = 'scp://%s%s/hooks/commit-msg' \ % (userhost, (":%s" % port) if port else "") print("Fetching commit hook from: %s" % hook_url) run_command_exc(CannotInstallHook, *cmd) 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_url(remote_url): """Tests that a possible gerrit remote url works.""" status, description = run_command_status("git", "push", "--dry-run", remote_url, "--all") if status != 128: if VERBOSE: print("%s worked. Description: %s" % (remote_url, description)) return True else: if VERBOSE: print("%s did not work. Description: %s" % ( remote_url, description)) return False def make_remote_url(scheme, username, hostname, port, project): """Builds a gerrit remote URL.""" if port is None and scheme == 'ssh': port = 29418 hostport = '%s:%s' % (hostname, port) if port else hostname if username is None: return "%s://%s/%s" % (scheme, hostport, project) else: return "%s://%s@%s/%s" % (scheme, username, hostport, project) def add_remote(scheme, hostname, port, project, remote, usepushurl): """Adds a gerrit remote.""" asked_for_username = False username = git_config_get_value("gitreview", "username") if not username: username = getpass.getuser() remote_url = make_remote_url(scheme, username, hostname, port, project) if VERBOSE: print("No remote set, testing %s" % remote_url) if not test_remote_url(remote_url): print("Could not connect to gerrit.") username = do_input("Enter your gerrit username: ") remote_url = make_remote_url(scheme, username, hostname, port, project) print("Trying again with %s" % remote_url) if not test_remote_url(remote_url): raise GerritConnectionException( "Could not connect to gerrit at %s" % remote_url ) asked_for_username = True if usepushurl: cmd = "git remote set-url --push %s %s" % (remote, remote_url) print("Adding a git push url to '%s' that maps to:" % remote) else: cmd = "git remote add -f %s %s" % (remote, remote_url) print("Creating a git remote called '%s' that maps to:" % remote) print("\t%s" % remote_url) (status, remote_output) = run_command_status(cmd) if status != 0: raise CommandFailed(status, remote_output, cmd, {}) if asked_for_username: print() printwrap("This repository is now set up for use with git-review. " "You can set the default username for future repositories " "with:") print(' git config --global --add gitreview.username "%s"' % username) print() def populate_rewrites(): """Populate the global _rewrites and _rewrites_push maps based on the output of "git-config". """ cmd = ['git', 'config', '--list'] out = run_command_exc(CommandFailed, *cmd).strip() global _rewrites, _rewrites_push _rewrites = {} _rewrites_push = {} for entry in out.splitlines(): key, _, value = entry.partition('=') key = key.lower() if key.startswith('url.') and key.endswith('.insteadof'): rewrite = key[len('url.'):-len('.insteadof')] if rewrite: _rewrites[value] = rewrite elif key.startswith('url.') and key.endswith('.pushinsteadof'): rewrite = key[len('url.'):-len('.pushinsteadof')] if rewrite: _rewrites_push[value] = rewrite def alias_url(url, rewrite_push): """Expand a remote URL. Use the global map _rewrites to replace the longest match with its equivalent. If rewrite_push is True, try _rewrites_push before _rewrites. """ if _rewrites is None: populate_rewrites() if rewrite_push: maps = [_rewrites_push, _rewrites] else: maps = [_rewrites] for rewrites in maps: # If Git finds a pushInsteadOf alias, it uses that even if # there is a longer insteadOf alias. longest = None for alias in rewrites: if (url.startswith(alias) and (longest is None or len(longest) < len(alias))): longest = alias if longest: return url.replace(longest, rewrites[longest]) return url def get_remote_url(remote): """Retrieve the remote URL. Read the configuration to expand the URL of a remote repository taking into account any "url..insteadOf" or "url..pushInsteadOf" config setting. TODO: Replace current code with something like "git ls-remote --get-url" after Git grows a version of it that returns the push URL rather than the fetch URL. """ push_url = git_config_get_value('remote.%s' % remote, 'pushurl') if push_url is not None: # Git rewrites pushurl using insteadOf but not pushInsteadOf. push_url = alias_url(push_url, False) else: url = git_config_get_value('remote.%s' % remote, 'url') # Git rewrites url using pushInsteadOf or insteadOf. push_url = alias_url(url, True) if VERBOSE: print("Found origin Push URL:", push_url) return push_url def parse_gerrit_ssh_params_from_git_url(git_url): """Parse a given Git "URL" into Gerrit parameters. Git "URLs" are either real URLs or SCP-style addresses. """ # The exact code for this in Git itself is a bit obtuse, so just do # something sensible and pythonic here instead of copying the exact # minutiae from Git. # Handle real(ish) URLs if "://" in git_url: parsed_url = urlparse(git_url) path = parsed_url.path hostname = parsed_url.netloc username = None port = parsed_url.port # 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(":") if port is not None: port = str(port) # Handle SCP-style addresses else: username = None port = None (hostname, path) = git_url.split(":", 1) if "@" in hostname: (username, hostname) = hostname.split("@", 1) # Strip leading slash and trailing .git from the path to form the project # name. project_name = re.sub(r"^/|(\.git$)", "", path) return (hostname, username, port, project_name) def query_reviews(remote_url, project=None, change=None, current_patch_set=True, exception=CommandFailed, parse_exc=Exception): if remote_url.startswith('http://') or remote_url.startswith('https://'): query = query_reviews_over_http else: query = query_reviews_over_ssh return query(remote_url, project=project, change=change, current_patch_set=current_patch_set, exception=exception, parse_exc=parse_exc) def query_reviews_over_http(remote_url, project=None, change=None, current_patch_set=True, exception=CommandFailed, parse_exc=Exception): if project: # Remove any trailing .git suffixes for project to url comparison clean_url = os.path.splitext(remote_url)[0] clean_project = os.path.splitext(project)[0] if clean_url.endswith(clean_project): # Get the "root" url for gerrit by removing the project from the # url. For example: # https://example.com/foo/project.git gets truncated to # https://example.com/foo/ regardless of whether or not none, # either, or both of the remote_url or project strings end # with .git. remote_url = clean_url[:-len(clean_project)] url = urljoin(remote_url, 'changes/') if change: if current_patch_set: url += '?q=%s&o=CURRENT_REVISION' % change else: url += '?q=%s&o=ALL_REVISIONS' % change else: if project: project_name = re.sub(r"^/|(\.git$)", "", project) else: project_name = re.sub(r"^/|(\.git$)", "", urlparse(remote_url).path) params = urlencode({'q': 'project:%s status:open' % project_name}) url += '?' + params if VERBOSE: print("Query gerrit %s" % url) request = run_http_exc(exception, url) if VERBOSE: print(request.text) reviews = json.loads(request.text[4:]) # Reformat output to match ssh output try: for review in reviews: review["number"] = str(review.pop("_number")) if "revisions" not in review: continue patchsets = {} for key, revision in review["revisions"].items(): fetch_value = list(revision["fetch"].values())[0] patchset = {"number": str(revision["_number"]), "ref": fetch_value["ref"]} patchsets[key] = patchset review["patchSets"] = patchsets.values() review["currentPatchSet"] = patchsets[review["current_revision"]] except Exception as err: raise parse_exc(err) return reviews def query_reviews_over_ssh(remote_url, project=None, change=None, current_patch_set=True, exception=CommandFailed, parse_exc=Exception): (hostname, username, port, project_name) = \ parse_gerrit_ssh_params_from_git_url(remote_url) if change: if current_patch_set: query = "--current-patch-set change:%s" % change else: query = "--patch-sets change:%s" % change else: query = "project:%s status:open" % project_name port_data = "p%s" % port if port is not None else "" if username is None: userhost = hostname else: userhost = "%s@%s" % (username, hostname) if VERBOSE: print("Query gerrit %s %s" % (remote_url, query)) output = run_command_exc( exception, "ssh", "-x" + port_data, userhost, "gerrit", "query", "--format=JSON %s" % query) if VERBOSE: print(output) changes = [] try: for line in output.split("\n"): if line[0] == "{": try: data = json.loads(line) if "type" not in data: changes.append(data) except Exception: if VERBOSE: print(output) except Exception as err: raise parse_exc(err) return changes def set_color_output(color="auto"): global _use_color if check_color_support(): if color == "auto": check_use_color_output() else: _use_color = color == "always" def check_use_color_output(): global _use_color if _use_color is None: if check_color_support(): # we can support color, now check if we should use it stdout = "true" if sys.stdout.isatty() else "false" test_command = "git config --get-colorbool color.review " + stdout color = run_command(test_command) _use_color = color == "true" else: _use_color = False return _use_color def check_color_support(): global _has_color 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 return _has_color 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 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 parse_tracking(ref=None): """Return tracked (remote, branch) of current HEAD or other named branch if tracking remote. """ if ref is None: ref = run_command_exc( SymbolicRefFailed, "git", "symbolic-ref", "-q", "HEAD") tracked = run_command_exc( ForEachRefFailed, "git", "for-each-ref", "--format=%(upstream)", ref) # Only on explicitly tracked remote branch do we diverge from default if tracked and tracked.startswith('refs/remotes/'): return tracked[13:].partition('/')[::2] return None, None def resolve_tracking(remote, branch): """Resolve tracked upstream remote/branch if current branch is tracked.""" tracked_remote, tracked_branch = parse_tracking() # tracked_branch will be empty when tracking a local branch if tracked_branch: if VERBOSE: print('Following tracked %s/%s rather than default %s/%s' % ( tracked_remote, tracked_branch, remote, branch)) return tracked_remote, tracked_branch return remote, branch def check_remote(branch, remote, scheme, hostname, port, project, usepushurl=False): """Check that a Gerrit Git remote repo exists, if not, set one.""" if usepushurl: push_url = git_config_get_value('remote.%s' % remote, 'pushurl', None) if push_url: return else: 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: remote_string = "remotes/%s/%s" % (remote, branch) if (current_remote.strip() == remote_string 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 project is False: # This means there was no .gitreview file printwrap("No '.gitreview' file found in this repository. We don't " "know where your gerrit is.") if usepushurl: printwrap("Please set the push-url on your origin remote to the " "location of your gerrit server and try again") else: printwrap("Please manually create a remote " "named \"%s\" and try again." % remote) sys.exit(1) # Gerrit remote not present, try to add it try: add_remote(scheme, hostname, port, project, remote, usepushurl) except Exception: if usepushurl: printwrap("We don't know where your gerrit is. Please manually" " add a push-url to the '%s' remote and try again." % remote) else: printwrap("We don't know where your gerrit is. Please manually" " create a remote named '%s' and try again." % remote) raise def rebase_changes(branch, remote, interactive=True): global _orig_head remote_branch = "remotes/%s/%s" % (remote, branch) if not update_remote(remote): return False # since the value of ORIG_HEAD may not be set by rebase as expected # for use in undo_rebase, make sure to save it explicitly cmd = "git rev-parse HEAD" (status, output) = run_command_status(cmd) if status != 0: print("Errors running %s" % cmd) if interactive: print(output) return False _orig_head = output cmd = "git show-ref --quiet --verify refs/%s" % remote_branch (status, output) = run_command_status(cmd) if status != 0: printwrap("The branch '%s' does not exist on the given remote '%s'. " "If these changes are intended to start a new branch, " "re-run with the '-R' option enabled." % (branch, remote)) sys.exit(1) if interactive: cmd = "git rebase -p -i %s" % remote_branch else: cmd = "git rebase -p %s" % remote_branch (status, output) = run_command_status(cmd, GIT_EDITOR='true') if status != 0: print("Errors running %s" % cmd) if interactive: print(output) printwrap("It is likely that your change has a merge conflict. " "You may resolve it in the working tree now as " "described above and then run 'git review' again, or " "if you do not want to resolve it yet (note that the " "change can not merge until the conflict is resolved) " "you may run 'git rebase --abort' then 'git review -R' " "to upload the change without rebasing.") return False return True def undo_rebase(): global _orig_head if not _orig_head: return True cmd = "git reset --hard %s" % _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 cmd = "git rev-parse --symbolic-full-name --abbrev-ref HEAD" _branch_name = run_command(cmd) if _branch_name == "HEAD": # detached head or no branch found _branch_name = target_branch return _branch_name def assert_one_change(remote, branch, yes, have_hook): if check_use_color_output(): use_color = "--color=always" else: use_color = "--color=never" cmd = ("git log %s --decorate --oneline HEAD --not --remotes=%s" % ( use_color, remote)) (status, output) = run_command_status(cmd) if status != 0: print("Had trouble running %s" % cmd) print(output) sys.exit(1) filtered = filter(None, output.split("\n")) output_lines = sum(1 for s in filtered) if output_lines == 1 and not have_hook: printwrap("Your change was committed before the commit hook was " "installed. Amending the commit to add a gerrit change id.") run_command("git commit --amend", GIT_EDITOR='true') elif output_lines == 0: printwrap("No changes between HEAD and %s/%s. Submitting for review " "would be pointless." % (remote, branch)) sys.exit(1) elif output_lines > 1: if not yes: printwrap("You are about to submit multiple commits. This is " "expected if you are submitting a commit that is " "dependent on one or more in-review commits, or if you " "are submitting multiple self-contained but dependent " "changes. Otherwise you should consider squashing your " "changes into one commit before submitting (for " "indivisible changes) or submitting from separate " "branches (for independent changes).") print("\nThe outstanding commits are:\n\n%s\n\n" "Do you really want to submit the above commits?" % output) yes_no = do_input("Type 'yes' to confirm, other to cancel: ") if yes_no.lower().strip() != "yes": print("Aborting.") 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:])) preferred_log_format = "%B" log_output = run_command("git log --pretty='" + preferred_log_format + "' HEAD^1..HEAD") if log_output == preferred_log_format: # The %B format specifier is supported starting at Git v1.7.2. If it's # not supported, we'll just get back '%B', so we try something else. # The downside of %s is that it removes newlines in the subject. log_output = run_command("git log --pretty='%s%n%b' HEAD^1..HEAD") bug_re = r'''(?x) # verbose regexp \b([Bb]ug|[Ll][Pp]) # bug or lp [ \t\f\v]* # don't want to match newline [:]? # separator if needed [ \t\f\v]* # don't want to match newline [#]? # if needed [ \t\f\v]* # don't want to match newline (\d+) # bug number''' 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'''(?x) # verbose regexp \b([Bb]lue[Pp]rint|[Bb][Pp]) # a blueprint or bp [ \t\f\v]* # don't want to match newline [#:]? # separator if needed [ \t\f\v]* # don't want to match newline ([0-9a-zA-Z-_]+) # any identifier or number''' 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 class Review(dict): _default_fields = ('branch', 'topic', 'subject') def __init__(self, data): if 'number' not in data: raise TypeError(" requires 'number' key in data") super(Review, self).__init__(data) # provide default values for some fields for field in self._default_fields: self[field] = self.get(field, '-') class ReviewsPrinter(object): def __init__(self, with_topic=False): if with_topic: self.fields = ('number', 'branch', 'topic', 'subject') # > is right justify, < is left, field indices for py26 self.fields_format = [ u"{0:>{1}}", u"{2:>{3}}", u"{4:>{5}}", u"{6:<{7}}"] else: self.fields = ('number', 'branch', 'subject') # > is right justify, < is left, field indices for py26 self.fields_format = [u"{0:>{1}}", u"{2:>{3}}", u"{4:<{5}}"] self.fields_colors = ("", "", "", "") self.color_reset = "" if check_use_color_output(): self.fields_colors = ( colors.yellow, colors.green, colors.blue, "") self.color_reset = colors.reset self.reviews = [] @property def fields_width(self): return [ max(len(str(review[field])) for review in self.reviews) for field in self.fields[:-1] ] + [1] def _get_field_format_str(self, field): index = self.fields.index(field) return ( self.fields_colors[index] + self.fields_format[index] + self.color_reset ) def add_review(self, review): self.reviews.append(review) def _get_fields_format_str(self): return " ".join([ self._get_field_format_str(field) for field in self.fields]) def print_review(self, review): fields_format_str = self._get_fields_format_str() formatted_fields = [] for field, width in zip(self.fields, self.fields_width): formatted_fields.extend(( review[field], width )) print(fields_format_str.format(*formatted_fields)) def do_print(self, reviews): total_reviews = len(reviews) for review in reviews: self.print_review(review) print("Found %d items for review" % total_reviews) def list_reviews(remote, project, with_topic=False): remote_url = get_remote_url(remote) reviews = [] for r in query_reviews(remote_url, project=project, exception=CannotQueryOpenChangesets, parse_exc=CannotParseOpenChangesets): reviews.append(Review(r)) if not reviews: print("No pending reviews") return printer = ReviewsPrinter(with_topic=with_topic) for review in reviews: printer.add_review(review) printer.do_print(reviews) 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 GerritConnectionException(GitReviewException): """Problem to establish connection to gerrit.""" EXIT_CODE = 40 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 class SetUpstreamBranchFailed(CommandFailed): "Cannot set upstream to remote branch" EXIT_CODE = 67 class SymbolicRefFailed(CommandFailed): "Cannot find symbolic reference" EXIT_CODE = 68 class ForEachRefFailed(CommandFailed): "Cannot process symbolic reference" EXIT_CODE = 69 class BranchTrackingMismatch(GitReviewException): "Branch exists but is tracking unexpected branch" EXIT_CODE = 70 def fetch_review(review, masterbranch, remote, project): remote_url = get_remote_url(remote) review_arg = review review, patchset_number = parse_review_number(review) current_patch_set = patchset_number is None review_infos = query_reviews(remote_url, project=project, change=review, current_patch_set=current_patch_set, exception=CannotQueryPatchSet, parse_exc=ReviewInformationNotFound) if not len(review_infos): raise ReviewInformationNotFound(review) for info in review_infos: if 'branch' in info and info['branch'] == masterbranch: if VERBOSE: print('Using review info from branch %s' % info['branch']) review_info = info break else: review_info = review_infos[0] if VERBOSE and 'branch' in review_info: print('Using default branch %s' % review_info['branch']) 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' remote_branch = review_info['branch'] 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, remote_branch def checkout_review(branch_name, remote, remote_branch): """Checkout a newly fetched (FETCH_HEAD) change into a branch """ try: run_command_exc(CheckoutNewBranchFailed, "git", "checkout", "-b", branch_name, "FETCH_HEAD") # --set-upstream-to is supported starting in git 1.8 run_command_exc(SetUpstreamBranchFailed, "git", "branch", "--set-upstream-to", '%s/%s' % (remote, remote_branch), branch_name) except CheckoutNewBranchFailed as e: if re.search("already exists\.?", e.output): print("Branch %s already exists - reusing" % branch_name) track_remote, track_branch = parse_tracking( ref='refs/heads/' + branch_name) if track_remote and not (track_remote == remote and track_branch == remote_branch): print("Branch %s incorrectly tracking %s/%s instead of %s/%s" % (branch_name, track_remote, track_branch, remote, remote_branch)) raise BranchTrackingMismatch 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, None, None) 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, remote_branch = fetch_review(new_review, branch, remote) checkout_review(new_branch, remote, remote_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', 'diff', 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 str(one_or_zero) in ["1", "true", "True"] class MalformedInput(GitReviewException): EXIT_CODE = 3 def assert_valid_reviewers(reviewers): """Ensure no whitespace is found in reviewer names, as it will result in an invalid refspec. """ for reviewer in reviewers: if re.search(r'\s', reviewer): raise MalformedInput( "Whitespace not allowed in reviewer: '%s'" % reviewer) def _main(): usage = "git review [OPTIONS] ... [BRANCH]" 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) topic_arg_group = parser.add_mutually_exclusive_group() topic_arg_group.add_argument("-t", "--topic", dest="topic", help="Topic to submit branch to") topic_arg_group.add_argument("-T", "--no-topic", dest="notopic", action="store_true", help="No topic except if explicitly provided") parser.add_argument("--reviewers", nargs="+", help="Add reviewers to uploaded patch sets.") 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("--use-pushurl", dest="usepushurl", action="store_true", help="Use remote push-url logic instead of separate" " remotes") rebase_group = parser.add_mutually_exclusive_group() rebase_group.add_argument("-R", "--no-rebase", dest="rebase", action="store_false", help="Don't rebase changes before submitting.") rebase_group.add_argument("-F", "--force-rebase", dest="force_rebase", action="store_true", help="Force rebase even when not needed.") track_group = parser.add_mutually_exclusive_group() track_group.add_argument("--track", dest="track", action="store_true", help="Use tracked branch as default.") track_group.add_argument("--no-track", dest="track", action="store_false", help="Ignore tracked branch.") 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="count", help="List available reviews for the current project, " "if passed more than once, will show more information") 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("--color", dest="color", metavar="", nargs="?", choices=["always", "never", "auto"], help="Show color output. --color (without []) " "is the same as --color=always. can be " "one of %(choices)s. Behaviour can also be " "controlled by the color.ui and color.review " "configuration settings.") parser.add_argument("--no-color", dest="color", action="store_const", const="never", help="Turn off colored output. Can be used to " "override configuration options. Same as " "setting --color=never.") 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(dry=False, draft=False, verbose=False, update=False, setup=False, list=False, yes=False) try: (top_dir, git_dir) = git_directories() except GitDirectoriesException as _no_git_dir: no_git_dir = _no_git_dir else: no_git_dir = False config = Config(os.path.join(top_dir, ".gitreview")) parser.set_defaults(rebase=convert_bool(config['rebase']), track=convert_bool(config['track']), remote=None, usepushurl=convert_bool(config['usepushurl'])) options = parser.parse_args() if options.license: print(COPYRIGHT) sys.exit(0) if no_git_dir: raise no_git_dir if options.branch is None: branch = config['branch'] else: # explicitly-specified branch on command line overrides options.track branch = options.branch options.track = False global VERBOSE global UPDATE VERBOSE = options.verbose UPDATE = options.update remote = options.remote if not remote: if options.usepushurl: remote = 'origin' else: remote = config['remote'] yes = options.yes status = 0 if options.track: remote, branch = resolve_tracking(remote, branch) check_remote(branch, remote, config['scheme'], config['hostname'], config['port'], config['project'], usepushurl=options.usepushurl) if options.color: set_color_output(options.color) if options.changeidentifier: if options.compare: compare_review(options.changeidentifier, branch, remote, options.rebase) return local_branch, remote_branch = fetch_review(options.changeidentifier, branch, remote, config['project']) if options.download: checkout_review(local_branch, remote, remote_branch) else: if options.cherrypickcommit: cherrypick_review() elif options.cherrypickonly: cherrypick_review("-n") if options.cherrypickindicate: cherrypick_review("-x") return elif options.list: with_topic = options.list > 1 list_reviews(remote, config['project'], with_topic=with_topic) 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 or options.force_rebase: if not rebase_changes(branch, remote): sys.exit(1) if not options.force_rebase and not undo_rebase(): sys.exit(1) 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) if options.topic is not None: topic = options.topic else: topic = None if options.notopic else get_topic(branch) if topic and topic != branch: cmd += "/%s" % topic if options.reviewers: assert_valid_reviewers(options.reviewers) cmd += "%" + ",".join("r=%s" % r for r in options.reviewers) 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") sys.exit(status) def main(): try: _main() except GitReviewException as e: print(e) sys.exit(getattr(e, 'EXIT_CODE', -1)) if __name__ == "__main__": main() git-review-1.26.0/git_review/__init__.py0000666000175100017510000000000013203667220020154 0ustar zuulzuul00000000000000git-review-1.26.0/README.rst0000666000175100017510000000056013203667220015401 0ustar zuulzuul00000000000000git-review ========== A git command for submitting branches to Gerrit git-review is a tool that helps submitting git branches to gerrit for review. * Free software: Apache license * Documentation: http://docs.openstack.org/infra/git-review/ * Source: https://git.openstack.org/cgit/openstack-infra/git-review * Bugs: https://storyboard.openstack.org/#!/project/719 git-review-1.26.0/tox.ini0000666000175100017510000000134213203667220015224 0ustar zuulzuul00000000000000[tox] envlist = py26,py27,py32,py33,py34,pep8 [testenv] install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} commands = python -m git_review.tests.check_test_id_hashes discover --list python -m git_review.tests.prepare python setup.py testr --slowest --testr-args='--concurrency=2 {posargs}' deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt [testenv:pep8] commands = flake8 [testenv:sdist] commands = python setup.py sdist {posargs} [testenv:docs] commands = python setup.py build_sphinx [testenv:venv] commands = {posargs} [flake8] ignore = E125,H202,H405,H904 show-source = True exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build git-review-1.26.0/git-review.10000666000175100017510000003517013203667220016063 0ustar zuulzuul00000000000000.\" Uses mdoc(7). See `man 7 mdoc` for details about the syntax used here .\" .Dd June 12th, 2015 .Dt GIT\-REVIEW 1 .Sh NAME .Nm git\-review .Nd Submit changes to Gerrit for review .Sh SYNOPSIS .Nm .Op Fl r Ar remote .Op Fl uv .Fl d Ar change .Op Ar branch .Nm .Op Fl r Ar remote .Op Fl uv .Fl x Ar change .Op Ar branch .Nm .Op Fl r Ar remote .Op Fl uv .Fl N Ar change .Op Ar branch .Nm .Op Fl r Ar remote .Op Fl uv .Fl X Ar change .Op Ar branch .Nm .Op Fl r Ar remote .Op Fl uv .Fl m .Ar change\-ps\-range .Op Ar branch .Nm .Op Fl r Ar remote .Op Fl fnuv .Fl s .Op Ar branch .Nm .Op Fl fnuvDRT .Op Fl r Ar remote .Op Fl t Ar topic .Op Fl \-reviewers Ar reviewer ... .Op Ar branch .Nm .Fl l .Nm .Fl \-version .Sh DESCRIPTION .Nm automates and streamlines some of the tasks involved with submitting local changes to a Gerrit server for review. It is designed to make it easier to comprehend Gerrit, especially for users that have recently switched to Git from another version control system. .Pp .Ar change can be .Ar changeNumber as obtained using .Fl \-list option, or it can be .Ar changeNumber,patchsetNumber for fetching exact patchset from the change. In that case local branch name will have a \-patch[patchsetNumber] suffix. .Pp The following options are available: .Bl -tag -width indent .It Fl c , Fl \-compatible Push changes to refs compatible with Gerrit of versions before 2.3. .It Fl d Ar change , Fl \-download= Ns Ar change Download .Ar change from Gerrit into a local branch. The branch will be named after the patch author and the name of a topic. If the local branch already exists, it will attempt to update with the latest patchset for this change. .It Fl x Ar change , Fl \-cherrypick= Ns Ar change Apply .Ar change from Gerrit and commit into the current local branch ("cherry pick"). No additional branch is created. .Pp This makes it possible to review a change without creating a local branch for it. On the other hand, be aware: if you are not careful, this can easily result in additional patch sets for dependent changes. Also, if the current branch is different enough, the change may not apply at all or produce merge conflicts that need to be resolved by hand. .It Fl N Ar change , Fl \-cherrypickonly= Ns Ar change Apply .Ar change from Gerrit into the current working directory, add it to the staging area ("git index"), but do not commit it. .Pp This makes it possible to review a change without creating a local commit for it. Useful if you want to merge several commits into one that will be submitted for review. .Pp If the current branch is different enough, the change may not apply at all or produce merge conflicts that need to be resolved by hand. .It Fl X Ar change , Fl \-cherrypickindicate= Ns Ar change Apply .Ar change from Gerrit and commit into the current local branch ("cherry pick"), indicating which commit this change was cherry\-picked from. .Pp This makes it possible to re\-review a change for a different branch without creating a local branch for it. .Pp If the current branch is different enough, the change may not apply at all or produce merge conflicts that need to be resolved by hand. .It Fl i , Fl \-new\-changeid Force the git-review to generate a new Change-Id, even if one already exists in the changelog. .It Fl m Ar change\-ps\-range , Fl \-compare= Ns Ar change\-ps\-range Download the specified patchsets for .Ar change from Gerrit, rebase both on master and display differences (git\-diff). .Pp .Ar change\-ps\-range can be specified as .Ar changeNumber, Ns Ar oldPatchSetNumber Ns Op Ns Ar \-newPatchSetNumber .Pp .Ar oldPatchSetNumber is mandatory, and if .Ar newPatchSetNumber is not specified, the latest patchset will be used. .Pp This makes it possible to easily compare what has changed from last time you reviewed the proposed change. .Pp If the master branch is different enough, the rebase can produce merge conflicts. If that happens rebasing will be aborted and diff displayed for not\-rebased branches. You can also use .Ar \-\-no\-rebase ( Ar \-R ) to always skip rebasing. .It Fl f , Fl \-finish Close down the local branch and switch back to the target branch on successful submission. .It Fl F , Fl \-force\-rebase Force a rebase before doing anything else, even if not otherwise needed. .It Fl n , Fl \-dry\-run Don\(aqt actually perform any commands that have direct effects. Print them instead. .It Fl r Ar remote , Fl \-remote= Ns Ar remote Git remote to use for Gerrit. .It Fl s , Fl \-setup Just run the repo setup commands but don\(aqt submit anything. .It Fl t Ar topic , Fl \-topic= Ns Ar topic Sets the target topic for this change on the Gerrit server. If not specified, a bug number from the commit summary will be used. Alternatively, the local branch name will be used if different from remote branch. .It Fl T , Fl \-no\-topic Submit review without topic. .It Fl \-reviewers Ar reviewer ... Subscribe one or more reviewers to the uploaded patch sets. Reviewers should be identifiable by Gerrit (usually use their Gerrit username or email address). .It Fl u , Fl \-update Skip cached local copies and force updates from network resources. .It Fl l , Fl \-list List the available reviews on the Gerrit server for this project. .It Fl y , Fl \-yes Indicate that you do, in fact, understand if you are submitting more than one patch. .It Fl v , Fl \-verbose Turns on more verbose output. .It Fl D , Fl \-draft Submit review as a draft. Requires Gerrit 2.3 or newer. .It Fl R , Fl \-no\-rebase Do not automatically perform a rebase before submitting the change to Gerrit. .Pp When submitting a change for review, you will usually want it to be based on the tip of upstream branch in order to avoid possible conflicts. When amending a change and rebasing the new patchset, the Gerrit web interface will show a difference between the two patchsets which contains all commits in between. This may confuse many reviewers that would expect to see a much simpler difference. .Pp Also can be used for .Fl \-compare to skip automatic rebase of fetched reviews. .It Fl \-color Ar always|never|auto Enable or disable a color output. Default is "auto". .It Fl \-no\-color Same thing as \-\-color=never. .It Fl \-no\-custom\-script Do not run scripts, installed as hooks/{action}-review, where action is one of "pre", "draft", or "post". .It Fl \-track Choose the branch to submit the change against (and, if rebasing, to rebase against) from the branch being tracked (if a branch is being tracked), and set the tracking branch when downloading a change to point to the remote and branch against which patches should be submitted. See gitreview.track configuration. .It Fl \-no\-track Ignore any branch being tracked by the current branch, overriding gitreview.track. This option is implied by providing a specific branch name on the command line. .It Fl \-use-pushurl Use the pushurl option for the origin remote rather than conventional separate Gerrit remotes. .It Fl \-version Print the version number and exit. .El .Sh CONFIGURATION This utility can be configured by adding entries to Git configuration. .Pp The following configuration keys are supported: .Bl -tag .It gitreview.username Default username used to access the repository. If not specified in the Git configuration, Git remote or .Pa .gitreview file, the user will be prompted to specify the username. .Pp Example entry in the .Pa .gitconfig file: .Bd -literal -offset indent [gitreview] username=\fImygerrituser\fP .Ed .It gitreview.scheme This setting determines the default scheme (ssh/http/https) of gerrit remote .It gitreview.host This setting determines the default hostname of gerrit remote .It gitreview.port This setting determines the default port of gerrit remote .It gitreview.project This setting determines the default name of gerrit git repo .It gitreview.remote This setting determines the default name to use for gerrit remote .It gitreview.branch This setting determines the default branch .It gitreview.track Determines whether to prefer the currently-tracked branch (if any) and the branch against which the changeset was submitted to Gerrit (if there is exactly one such branch) to the defaultremote and defaultbranch for submitting and rebasing against. If the local topic branch is tracking a remote branch, the remote and branch that the local topic branch is tracking should be used for submit and rebase operations, rather than the defaultremote and defaultbranch. .Pp When downloading a patch, creates the local branch to track the appropriate remote and branch in order to choose that branch by default when submitting modifications to that changeset. .Pp A value of 'true' or 'false' should be specified. .Bl -tag .It true Do prefer the currently-tracked branch (if any) \- equivalent to setting .Fl \-track when submitting changes. .It false Ignore tracking branches \- equivalent to setting .Fl \-no\-track (the default) or providing an explicit branch name when submitting changes. This is the default value unless overridden by .Pa .gitreview file, and is implied by providing a specific branch name on the command line. .El .It gitreview.rebase This setting determines whether changes submitted will be rebased to the newest state of the branch. .Pp A value of 'true' or 'false' should be specified. .Bl -tag .It false Do not rebase changes on submit \- equivalent to setting .Fl R when submitting changes. .It true Do rebase changes on submit. This is the default value unless overridden by .Pa .gitreview file. .El .Pp This setting takes precedence over repository\-specific configuration in the .Pa .gitreview file. .El .Bl -tag .It color.review Whether to use ANSI escape sequences to add color to the output displayed by this command. Default value is determined by color.ui. .Bl -tag .It auto or true If you want output to use color when written to the terminal (default with Git 1.8.4 and newer). .It always If you want all output to use color .It never or false If you wish not to use color for any output. (default with Git older than 1.8.4) .El .El .Pp .Nm will query git credential system for Gerrit user/password when authentication failed over http(s). Unlike git, .Nm does not persist Gerrit user/password in git credential system for security purposes and git credential system configuration stays under user responsibility. .Sh FILES To use .Nm with your project, it is recommended that you create a file at the root of the repository named .Pa .gitreview and place information about your Gerrit installation in it. The format is similar to the Windows .ini file format: .Bd -literal -offset indent [gerrit] host=\fIhostname\fP port=\fITCP port number of gerrit\fP project=\fIproject name\fP defaultbranch=\fIbranch to work on\fP .Ed .Pp It is also possible to specify optional default name for the Git remote using the .Cm defaultremote configuration parameter. .Pp Setting .Cm defaultrebase to zero will make .Nm not to rebase changes by default (same as the .Fl R command line option) .Bd -literal -offset indent [gerrit] scheme=ssh host=review.example.com port=29418 project=department/project.git defaultbranch=master defaultremote=review defaultrebase=0 track=0 .Ed .Pp When the same option is provided through FILES and CONFIGURATION, the CONFIGURATION value wins. .Pp .Sh DIAGNOSTICS .Pp Normally, exit status is 0 if executed successfully. Exit status 1 indicates general error, sometimes more specific error codes are available: .Bl -tag -width 999 .It 2 Gerrit .Ar commit\-msg hook could not be successfully installed. .It 3 Could not parse malformed argument value or user input. .It 32 Cannot fetch list of open changesets from Gerrit. .It 33 Cannot parse list of open changesets received from Gerrit. .It 34 Cannot query information about changesets. .It 35 Cannot fetch information about the changeset to be downloaded. .It 36 Changeset not found. .It 37 Particular patchset cannot be fetched from the remote git repository. .It 38 Specified patchset number not found in the changeset. .It 39 Invalid patchsets for comparison. .It 40 Connection to Gerrit was closed. .It 64 Cannot checkout downloaded patchset into the new branch. .It 65 Cannot checkout downloaded patchset into existing branch. .It 66 Cannot hard reset working directory and git index after download. .It 67 Cannot switch to some other branch when trying to finish the current branch. .It 68 Cannot delete current branch. .It 69 Requested patchset cannot be fully applied to the current branch. This exit status will be returned when there are merge conflicts with the current branch. Possible reasons include an attempt to apply patchset from the different branch or code. This exit status will also be returned if the patchset is already applied to the current branch. .It 70 Cannot determine top level Git directory or .git subdirectory path. .It 101 Unauthorized (401) http request done by git-review. .It 104 Not Found (404) http request done by git-review. .El .Pp Exit status larger than 31 indicates problem with communication with Gerrit or remote Git repository, exit status larger than 63 means there was a problem with a local repository or a working copy. .Pp Exit status larger than or equal to 128 means internal error in running the "git" command. .Pp .Sh EXAMPLES To fetch a remote change number 3004: .Pp .Bd -literal -offset indent $ git\-review \-d 3004 Downloading refs/changes/04/3004/1 from gerrit into review/someone/topic_name Switched to branch 'review/someone/topic_name $ git branch master * review/author/topic_name .Ed .Pp Gerrit looks up both name of the author and the topic name from Gerrit to name a local branch. This facilitates easier identification of changes. .Pp To fetch a remote patchset number 5 from change number 3004: .Pp .Bd -literal -offset indent $ git\-review \-d 3004,5 Downloading refs/changes/04/3004/5 from gerrit into review/someone/topic_name\-patch5 Switched to branch 'review/someone/topic_name\-patch5 $ git branch master * review/author/topic_name\-patch5 .Ed .Pp To send a change for review and delete local branch afterwards: .Bd -literal -offset indent $ git\-review \-f remote: Resolving deltas: 0% (0/8) To ssh://username@review.example.com/department/project.git * [new branch] HEAD \-> refs/for/master/topic_name Switched to branch 'master' Deleted branch 'review/someone/topic_name' $ git branch * master .Ed .Pp An example .Pa .gitreview configuration file for a project .Pa department/project hosted on .Cm review.example.com port .Cm 29418 in the branch .Cm master : .Bd -literal -offset indent [gerrit] host=review.example.com port=29418 project=department/project.git defaultbranch=master .Ed .Sh BUGS Bug reports can be submitted to .Lk https://launchpad.net/git\-review .Sh AUTHORS .Nm is maintained by .An "OpenStack, LLC" .Pp This manpage has been enhanced by: .An "Antoine Musso" Aq hashar@free.fr .An "Marcin Cieslak" Aq saper@saper.info .An "Pavel Sedlák" Aq psedlak@redhat.com git-review-1.26.0/PKG-INFO0000664000175100017510000000241313203667433015012 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: git-review Version: 1.26.0 Summary: Tool to submit code to Gerrit Home-page: http://docs.openstack.org/infra/git-review/ Author: OpenStack Author-email: openstack-infra@lists.openstack.org License: Apache License (2.0) Description-Content-Type: UNKNOWN Description: git-review ========== A git command for submitting branches to Gerrit git-review is a tool that helps submitting git branches to gerrit for review. * Free software: Apache license * Documentation: http://docs.openstack.org/infra/git-review/ * Source: https://git.openstack.org/cgit/openstack-infra/git-review * Bugs: https://storyboard.openstack.org/#!/project/719 Keywords: git gerrit review Platform: UNKNOWN Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent git-review-1.26.0/requirements.txt0000666000175100017510000000001613203667220017172 0ustar zuulzuul00000000000000requests>=1.1 git-review-1.26.0/CONTRIBUTING.rst0000666000175100017510000000247313203667234016365 0ustar zuulzuul00000000000000============================ Contributing to git-review ============================ This tool is considered mostly feature-complete by its authors. It is meant to provide a simple, convenient tool for users of basic Gerrit change workflows. Contributions fixing bugs or regressions, maintaining support for newer Gerrit/Git releases and improving test coverage are welcome and encouraged. It is not, however, intended as an all-encompassing Gerrit client (there are plenty of other tools available supporting more advanced interactions), so proposed feature additions may make more sense implemented as complimentary ``git`` subcommands or similar related but separate projects. To get the latest code, see: https://git.openstack.org/cgit/openstack-infra/git-review Bugs are handled at: https://storyboard.openstack.org/#!/project/719 There is a mailing list at: http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-infra Code reviews, as you might expect, are handled by gerrit at: https://review.openstack.org See http://wiki.openstack.org/GerritWorkflow for details. Pull requests submitted through GitHub will be ignored. Use ``git review`` to submit patches (after creating a gerrit account that links to your launchpad account). Example:: # Do your commits git review # Enter your username if prompted git-review-1.26.0/doc/0000775000175100017510000000000013203667433014462 5ustar zuulzuul00000000000000git-review-1.26.0/doc/source/0000775000175100017510000000000013203667433015762 5ustar zuulzuul00000000000000git-review-1.26.0/doc/source/usage.rst0000666000175100017510000000212513203667220017614 0ustar zuulzuul00000000000000======= Usage ======= Hack on some code, then:: git review If you want to submit that code to a branch other than "master", then:: git review branchname If you want to submit to a different remote:: git review -r my-remote If you want to supply a review topic:: git review -t topic/awesome-feature If you want to subscribe some reviewers:: git review --reviewers a@example.com b@example.com If you want to disable autogenerated topic:: git review -T If you want to submit a branch for review and then remove the local branch:: git review -f If you want to skip the automatic "git rebase -i" step:: git review -R If you want to download change 781 from gerrit to review it:: git review -d 781 If you want to download patchset 4 for change 781 from gerrit to review it:: git review -d 781,4 If you want to compare patchset 4 with patchset 10 of change 781 from gerrit:: git review -m 781,4-10 If you want to see a list of open reviews:: git review -l If you just want to do the commit message and remote setup steps:: git review -s git-review-1.26.0/doc/source/conf.py0000666000175100017510000001743413203667220017266 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # # git-review documentation build configuration file, created by # sphinx-quickstart on Mon Dec 1 14:06:22 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'oslosphinx', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'git-review' copyright = u'2014, OpenStack Contributors' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'git-reviewdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'git-review.tex', u'git-review Documentation', u'OpenStack Contributors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'git-review', u'git-review Documentation', [u'OpenStack Contributors'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'git-review', u'git-review Documentation', u'OpenStack Contributors', 'git-review', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False git-review-1.26.0/doc/source/developing.rst0000666000175100017510000000120713203667220020644 0ustar zuulzuul00000000000000.. include:: ../../CONTRIBUTING.rst Running tests ============= Running tests for git-review means running a local copy of Gerrit to check that git-review interacts correctly with it. This requires the following: * a Java Runtime Environment on the machine to run tests on * Internet access to download the gerrit.war file, or a locally cached copy (it needs to be located in a .gerrit directory at the top level of the git-review project) To run git-review integration tests the following commands may by run:: tox -e py27 tox -e py26 tox -e py32 tox -e py33 depending on what Python interpreter would you like to use. git-review-1.26.0/doc/source/installation.rst0000666000175100017510000000676113203667234021230 0ustar zuulzuul00000000000000================================ Installation and Configuration ================================ Installing git-review ===================== ``git-review`` can be often be installed via system packages, ``pypi`` releases or other platform-specific methods. See ``__ for platform information. For assistance installing pacakges from ``pypi`` on your OS check out `get-pip.py `__. For installation from source simply add ``git-review`` to your $PATH after installing the dependencies listed in requirements.txt .. note:: ``git-review`` requires git version 1.8 or greater. Windows ------- The Windows ``cmd`` console has a number of issues with Python and Unicode encodings which can manifest when reviews include non-ASCII characters. Python 3.6 and beyond has addressed most issues and is recommended for Windows users. For earlier Python versions, modifying the local install with `win-unicode-console `__ may also help. Setup ===== By default, git-review will look for a remote named 'gerrit' for working with Gerrit. If the remote exists, git-review will submit the current branch to HEAD:refs/for/master at that remote. If the Gerrit remote does not exist, git-review looks for a file called .gitreview at the root of the repository with information about the gerrit remote. Assuming that file is present, git-review should be able to automatically configure your repository the first time it is run. The name of the Gerrit remote is configurable; see the configuration section below. .gitreview file format ====================== Example .gitreview file (used to upload for git-review itself):: [gerrit] host=review.openstack.org port=29418 project=openstack-infra/git-review.git defaultbranch=master Required values: host, project Optional values: port (default: 29418), defaultbranch (default: master), defaultremote (default: gerrit). **Notes** * Username is not required because it is requested on first run * Unlike git config files, there cannot be any whitespace before the name of the variable. * Upon first run, git-review will create a remote for working with Gerrit, if it does not already exist. By default, the remote name is 'gerrit', but this can be overridden with the 'defaultremote' configuration option. * You can specify different values to be used as defaults in ~/.config/git-review/git-review.conf or /etc/git-review/git-review.conf. * Git-review will query git credential system for gerrit user/password when authentication failed over http(s). Unlike git, git-review does not persist gerrit user/password in git credential system for security purposes and git credential system configuration stays under user responsibility. Hooks ===== git-review has a custom hook mechanism to run a script before certain actions. This is done in the same spirit as the classic hooks in git. There are two types of hooks, a global one which is stored in ~/.config/git-review/hooks/ and one local to the repository stored in .git/hooks/ with the other git hook scripts. **The script needs be executable before getting executed** The name of the script is $action-review where action can be : * pre - run at first before doing anything. * post - run at the end after the review was sent. * draft - run when in draft mode. if the script returns with an exit status different than zero, git-review will exit with the a custom shell exit code 71. git-review-1.26.0/doc/source/index.rst0000666000175100017510000000043513203667220017621 0ustar zuulzuul00000000000000============ git-review ============ ``git-review`` is a tool that helps submitting git branches to gerrit for review. .. toctree:: :maxdepth: 2 installation usage developing Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` git-review-1.26.0/doc/Makefile0000666000175100017510000001520313203667220016117 0ustar zuulzuul00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/git-review.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/git-review.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/git-review" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/git-review" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." git-review-1.26.0/AUTHORS0000664000175100017510000000564613203667432014777 0ustar zuulzuul00000000000000Aaron Schulz Alexander Jones Anders Kaseorg Andreas Jaeger Andreas Jaeger Andrew Karnani Anita Kuno Antoine Musso Atsushi SAKAI Benjamin Pflanz Carlo Marcelo Arenas Belon Catrope Cedric Brandily Chmouel Boudjnah Christian Berendt Christoph Settgast Clark Boylan Clint Adams Daniel Lublin Daniel P. Berrange Darragh Bailey Darragh Bailey David Caro David Ostrovsky Dereckson Dexter Fryar Diederik Dina Belova Dmitry Ratushnyy Doug Hellmann Eric Harney Frederic Lepied Hugh Saunders Ian Wienand Ian Y. Choi JC Delay James E. Blair James E. Blair Jason Axelson Jeremy Stanley John Vandenberg Julien Danjou K Jonathan Harker Khai Do Kiall Mac Innes Krenair Lorin Hochstein Lukas Bednar Marcin Cieslak Mark McLoughlin Matthieu Baerts Merlijn van Deen Michael Johnson Michael Krotscheck Michael Pratt Michael Still Miklos Vajna Monty Taylor Morgan Fainberg Ori Livneh Paul Belanger Pavel Sedlák Pete Zaitcev Roger Luethi Sachi King Saggi Mizrahi Sorin Sbarnea Steve Kowalik Swapnil Kulkarni (coolsvap) Thomas Goirand Tim Burke Tim Landscheidt Vishvananda Ishaya Yuriy Taraday Zane Bitter Zuul david gholt julien.marinfrisonroche liuyang1 pangwa saper git-review-1.26.0/setup.cfg0000666000175100017510000000175513203667433015550 0ustar zuulzuul00000000000000[metadata] name = git-review summary = Tool to submit code to Gerrit description-file = README.rst license = Apache License (2.0) classifiers = Programming Language :: Python :: 2 Programming Language :: Python :: 3 Programming Language :: Python Development Status :: 5 - Production/Stable Environment :: Console Environment :: OpenStack Intended Audience :: Developers Intended Audience :: Information Technology License :: OSI Approved :: Apache Software License Operating System :: OS Independent keywords = git gerrit review author = OpenStack author-email = openstack-infra@lists.openstack.org home-page = http://docs.openstack.org/infra/git-review/ project-url = http://docs.openstack.org/infra/ [files] packages = git_review [entry_points] console_scripts = git-review = git_review.cmd:main [wheel] universal = 1 [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [pbr] manpages = git-review.1 warnerrors = True [egg_info] tag_build = tag_date = 0 git-review-1.26.0/test-requirements.txt0000666000175100017510000000017313203667220020153 0ustar zuulzuul00000000000000hacking>=0.10.0,<0.11 mock fixtures>=0.3.14 testrepository>=0.0.18 testtools>=0.9.34 oslosphinx sphinx>=1.1.2,!=1.2.0,<1.3 git-review-1.26.0/LICENSE0000666000175100017510000002645013203667220014725 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. git-review-1.26.0/git_review.egg-info/0000775000175100017510000000000013203667433017553 5ustar zuulzuul00000000000000git-review-1.26.0/git_review.egg-info/entry_points.txt0000664000175100017510000000006413203667432023050 0ustar zuulzuul00000000000000[console_scripts] git-review = git_review.cmd:main git-review-1.26.0/git_review.egg-info/top_level.txt0000664000175100017510000000001313203667432022276 0ustar zuulzuul00000000000000git_review git-review-1.26.0/git_review.egg-info/pbr.json0000664000175100017510000000005613203667432021231 0ustar zuulzuul00000000000000{"git_version": "ad59206", "is_release": true}git-review-1.26.0/git_review.egg-info/SOURCES.txt0000664000175100017510000000144413203667433021442 0ustar zuulzuul00000000000000.mailmap .testr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE MANIFEST.in README.rst git-review.1 requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/Makefile doc/source/conf.py doc/source/developing.rst doc/source/index.rst doc/source/installation.rst doc/source/usage.rst git_review/__init__.py git_review/cmd.py git_review.egg-info/PKG-INFO git_review.egg-info/SOURCES.txt git_review.egg-info/dependency_links.txt git_review.egg-info/entry_points.txt git_review.egg-info/not-zip-safe git_review.egg-info/pbr.json git_review.egg-info/requires.txt git_review.egg-info/top_level.txt git_review/tests/__init__.py git_review/tests/check_test_id_hashes.py git_review/tests/prepare.py git_review/tests/test_git_review.py git_review/tests/test_unit.py git_review/tests/utils.pygit-review-1.26.0/git_review.egg-info/PKG-INFO0000664000175100017510000000241313203667432020647 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: git-review Version: 1.26.0 Summary: Tool to submit code to Gerrit Home-page: http://docs.openstack.org/infra/git-review/ Author: OpenStack Author-email: openstack-infra@lists.openstack.org License: Apache License (2.0) Description-Content-Type: UNKNOWN Description: git-review ========== A git command for submitting branches to Gerrit git-review is a tool that helps submitting git branches to gerrit for review. * Free software: Apache license * Documentation: http://docs.openstack.org/infra/git-review/ * Source: https://git.openstack.org/cgit/openstack-infra/git-review * Bugs: https://storyboard.openstack.org/#!/project/719 Keywords: git gerrit review Platform: UNKNOWN Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent git-review-1.26.0/git_review.egg-info/not-zip-safe0000664000175100017510000000000113203667414022000 0ustar zuulzuul00000000000000 git-review-1.26.0/git_review.egg-info/dependency_links.txt0000664000175100017510000000000113203667432023620 0ustar zuulzuul00000000000000 git-review-1.26.0/git_review.egg-info/requires.txt0000664000175100017510000000001613203667432022147 0ustar zuulzuul00000000000000requests>=1.1 git-review-1.26.0/ChangeLog0000664000175100017510000003344613203667432015500 0ustar zuulzuul00000000000000CHANGES ======= 1.26.0 ------ * Add a note about contribution priorities * Add a note on Windows and Unicode * Fix output printing with python3 * Handle http queries below / * Support git 2.15 and newer * show the config value result after fetching it in verbose mode * Actually output the warning * Fix listing changes over SSH for 2.14 * Provide link to MediaWiki platform specific page * Better username detection for add\_remote() * Refactor displaying of reviews * Added topic field to the list output * Switch to string format to avoid interpolation issues * Refactor Isolated Env to use in unit tests * Set author and committer explicitly * Use hash of test ID to pick Gerrit ports in tests * Clarify that submitting multiple commits is OK * Remove discover from test-requirements * Install bc libs when running testsuite * Add several missing options to the man page * Fix AttributeError when can not connect to Gerrit * Set a default EXIT\_CODE for GitReviewException * Use consistent formatting for deprecations * Fix git-review -d behavior with branches * Support git without git credential * Remove worthless print * Fix no\_git\_dir UnboundLocalError in except block * Update gerrit version for testing * Remove argparse from requirements * Correct metadata URLs * Avoid AttributeError when raising raw GitReviewExceptions * fix encoding issue on Windows * Fix one typo on git-review documentation * Use git push-url instead of a second remote * Don't parse git log header for topic * Ignore .eggs cruft * Fix H238 violation and enable check for that rule * Update to newer hacking checks * Remove spurious mdoc directives 1.25.0 ------ * Add “git-review -l” to man page synopsis and usage doc * Add reviewers on upload * Update project links * Override E-mail in Gerrit integration tests * Fixed a dead link to the git-review docs * Provide an explanation on failed rebase * Switch to requests to support proxying of 'https' * Use plumbing rev-parse to get the branch name * Isolate tests from user/system git configuration * Push language override down into the cmd wrapper * git review -sv gets more verbose * Add utf-8 char support * Choose tracked branch for rebase when submitting * pbr should install the manpage * get\_remote\_url(): also honor url.\*.pushInsteadOf * Support authentication in run\_http\_exc * Split README.rst into separate doc files * Handle correctly http error raise in run\_http\_exc * Fix encoding header * Workflow documentation is now in infra-manual * Update tests to use Gerrit 2.9.2 * Use 'no\_proxy' env variable in addition to uppercase * Enable color support based on tty and config * get\_remote\_url(): honor any "url..insteadOf" config setting * Convert add\_remote to use GitReviewExceptions * Prefer git-config over git-review config files * Isolate tests from user/system config * Switched documentation to point to storyboard * Fix ---list and "departement" typos in man page * Align git-review and python -m git\_review.cmd behaviors * Define -T/--no-topic to disable review submit with topic * Work toward Python 3.4 support and testing * -F/--force-rebase has no effect if rebase is disabled by config * Remove useless constants * Improve windows support for git-review --setup * Fix groff warnings with manpage * Enabled hacking checks H305 and H307 * Prevent long subjects in reviews causing spurious blank lines * added link to get-pip to readme * Disable ssh/scp password authentication during tests * Update tests to use Gerrit 2.8.6.1 * Build universal wheels 1.24 ---- * Update homepage on PyPI * Update requirements to OpenStack's recommendations * Update the README to mention dependencies * Avoid a stacktrace when no reviews are pending * Ensure username is set for all tests * Provide nicer user message for missing remote ref * Fix a typo in HACKING.rst * Ignore newline in bp/bug search in commit message * Restrict tests SSH auth to only the provided key * Disable proxies for tests that clone over http * Keep track of gerrit.war and golden\_site versions * Fix typo in manpage s/gireview/gitreview/ * Correct git review -l over http(s) * Topic: do not use '(detached' when detached * Use gerrit 2.8.5 instead of gerrit 2.6.1 in tests * Allow to specify default scheme in .gitreview file * Correct test\_remote to support branchs without upstream * Remove parsing of --help and variants from our code * Python2: fixed UnicodeEncodeError * Skip invalid unicode in commit messages * Git review assumes the wrong ssh default port * Add http(s) protocol support to fetch\_review and list\_reviews * git-review.1 manpage fix for groff warnings * Fix parsing of SCP-style URLs, as these are valid in Git itself * "git review --setup" failed in Chinese locale * Bump hacking version in requirements * Reduce testr concurrnecy to 2 * Add http(s) protocol support to set\_hooks\_commit\_msg * Retrieve remote pushurl independently of user's locale * Add http(s) protocol support to test\_remote * Verify if every attached file exists. Attach gerrig.config * Wrap exceptions that occur while running external process * Make Gerrit port and dir selection deterministic * Don't try to attach known\_hosts if it's not there * Remove tox locale overrides * Fix the regex for setting topic * Add 2m timeout to tests * Attach Gerrit logs and known\_hosts to failed tests * Change test gerrit ssh/http ports offset * Correct .Fl typo WRT --compare in the manual page * Ignore content of merge commits in reporting * Remove empty lines from git log output * Preserve merges when doing a rebase * Split git rev-parse --show-toplevel --git-dir on newline * Prefer .gitconfig username * Add more deterministic port selection for Gerrit * Document source location as git.openstack.org * Implement integration tests * Migrate to pbr * No longer check for new git-review releases 1.23 ---- * Wrap long lines * Pin hacking <0.6 * Fix str(None) bug in port detection for hook setup * Fix pep8 deps for pyflakes version conflict * Expand multiple commit submission warning * Start development of 1.23 1.22 ---- * Provide usage help even if not in Git directory * Document defaultremote option & site/user configs * Allow per-site and per-user Gerrit defaults * Rename README.md to README.rst * Add venv testenv to tox.ini * Start development of 1.22 1.21 ---- * Align to OpenStack Hacking guidelines * Switch to flake8 from pep8 * Allow per-user override of -R setting * git\_config\_get\_value to return None * Use the local branch name as topic only if it is different from remote * Jeremy's manpath workaround breaks on Fedora * Compare different Patch Sets of Review * bug/1144800: Fix fatal: both revision and filename * Changed the text of the manpage to read "make it easier to comprehend Gerrit" * Don't call get\_branch\_name from assert\_one\_change * Add custom scripts features * Download specific Patch Set for Review * Make README.md ReST-friendly * Document gitreview.username * Better determine git directories * Don't fetch remote outside of rebase flow * Make it possible to cherry-pick a change * Check HEAD for extra commits instead of master * Check that the owner of a change has name defined * Topic name should be determined only for git push * Fix regression in repeated -d of the same change * Remove two else: blocks in main() * Update README for project move * Updated .gitreview location * Add mailing list to README * Use exceptions for list\_reviews * Use exceptions for finish\_review * Use exceptions for download\_review * git-review(1): explain exit code ranges * Introduce base exception classes * Follow up I92b8637c: Fix Python 3 compatibility * Start development on 1.21 * Remove reference to nonexistent requirements.txt 1.20 ---- * Avoid symlinks in the manpage path * Start development on 1.20 1.19 ---- * Revert "Introduce base exception classes" * Introduce base exception classes * Revert "Introduce base exception classes" * Revert "git-review(1): explain exit code ranges" * git-review(1): explain exit code ranges * Introduce base exception classes * Review list fields should have constant widths * manpage minor fixes with no output changes * Make setup.py less Linux-specific, more UNIX-compliant * Fixing ponctuation issue * Introduce CommandFailed exception for run\_command * Use run\_command\_status \*argv for ssh and scp * Refactor run\_command to use \*args, \*\*kwargs * Get rid of "status" parameter in run\_command * Due to gerrit bug regeneration of Change-Id must be nessecary * Don't rebase if the rebase doesn't conflict * Allow download of reviews from deleted accounts * Python 3 compatibility fixes * Add flag to push to refs/for/\* for compatibilty * Add Python 3 support * Just fixing a small typo * Revert to 1.17 * Configure a pep8 tox environment * Fixes typos and omission in failed review list * Allow download of reviews from deleted accounts * Start development on 1.19 1.18 ---- * Reversed the is:reviewable logic * Add ability to upload as work in progress * Filter list by is:reviewable by default * Due to gerrit bug regeneration of Change-Id must be nessecary * Don't rebase if the rebase doesn't conflict * Add open action * Add setup, list and download actions * Get current branch more concisely * fix missing username attribute in JSON stream * Rename submit action to upload * Fix no-change detection * Fix pep8 errors with 1.3.1 * Add optional 'submit' positional argument * Add flag to push to refs/for/\* for compatibilty * Add review scores to listing * Add Review, PatchSet and Hacker classes * Return a json result iterator from run\_ssh\_query() * Only list reviews for the current branch * Refactor out run\_ssh\_query() method * Add Python 3 support * Just fixing a small typo * Start dev on 1.18 1.17 ---- * Fixed hook installation for git with submodules * Update publish ref from refs/for to refs/publish * Run 'git remote update' from assert\_one\_change * Fix --list breakage in 3531a5bffd4f * Disable ssh X11 forwarding * Add support to list changes for review * Removed parsing out of team * fix -d not reusing already existing local branch * Start development on 1.17 1.16 ---- * Change draft ref to drafts * Fix scope error with configParser * Retrieve project & team name from fetch url * minor glitches in manpage * More resilient downloading * Override remote options: update manpage * Reformat manpage to mdoc(7) * Allow the user to override remote consequently * enhance man page * bump year references from 2011 to 2012 * Start development on 1.16 1.15 ---- * Actually fix the urlparse bug in OSX * Start dev on 1.15 1.14 ---- * Fix an obvious breakage on OSX * Start dev on 1.14 1.13 ---- * Workaround for OSX urlparse issue * Include timing info in verbose output * Remove automagic requirements.txt * Use dirname instead of basename * accepts color.ui boolean values * Started 1.13 1.12 ---- * Provide easy flag for disabling update check * Freeze requirements for sdist packages * Start 1.12 1.11 ---- * Start 1.11 1.10 ---- * Added tox support for testing pep8 * Added dependencies in requirements.txt * Add myself to AUTHORS * Add support for Gerrit 2.2.2's "Draft" status * Remove useless username parameter from add\_remote * Make the default branch configurable * Break out the config reading into its own method * Don't hardcode 'master' * Fix typo (Ammending -> Amending) * Switch from 'git show' to 'git log' * Migrate to argparse. optparse is deprecated * Make readme links clickable * Add installation instructions to readme * Clarify instructions for contributing * Add .gitreview instructions to readme * Post 1.9 release version bump 1.9 --- * Remove commit --amend from hook installation * Bump version post release 1.8 --- * Prep for release of 1.8 * Document the new command line options * Only pass plain strings to shlex.split * Add --finish option to git-review * Remove the commands module and use subprocess * Add workaround for broken urlparse * Install hook via scp rather than http * Use specified remote for getting commit hook * Fix bug 901030 1.7 --- * Use fetch/checkout instead of pull * Only use color on systems that support it * Create .git/hooks directory if not present * Version bump post release 1.6 --- * Check git config for default username * Handle usernames better in remote setup * Added Saggi to AUTHORS file. Thanks Saggi! * Spruced up the README just a bit * Show branches and tags when showing git log * Better handling of the color git configuration * commit\_msg declared but never used * In detached head, use the target branch as topic * Always show the list of outstanding commits * Fix multi-change confirmation testing * Force single-version-externally-managed * Removed old doc from MANIFEST.in * setup.py should point to launchpad project page * Check to ensure that only one change is submitted * Post release version bump 1.5 --- * Oops. Left git-review.1 out of the MANIFEST.in 1.4 --- * Replace sphinx manpage with manual one * Bump version after release 1.3 --- * Clean up documentation; add --version * Bump version after release 1.2 --- * Better guessing of username * Check .gitreview file in repo for location of gerrit * fixed problem downloading changes * Replace git fetch with git remote update * Add --setup command for a pro-active repo setup * Updated docs to describe setup * Wrap sphinx calls in a try/except block * Update version after release 1.1 --- * Replace tuple with list to unconfuse setuptools * Added support for fixing existing gerrit remotes * Always run git fetch so that commits are available for rebase * Pull version in directly from git-review * Fix manpage installation for setup.py install * Add remote arg to git remote update * Install man pages * Added support for download command * Handle repos cloned from github git urls * Fix git push command * Bumped version after release to PyPI 1.0 --- * Support openstack-ci github organization * Added the remote option and updated README * Bug fixes relating to first-time runs * Added support for updating * Ported rfc.sh to a standalone program