Initial commit of git-restack
Change-Id: I5508816fcd5c6362ee17a494ae8b997de29505d3
This commit is contained in:
parent
cfb726e4a0
commit
c6a59489fa
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
|||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
git_review.egg-info
|
git_restack.egg-info
|
||||||
MANIFEST
|
MANIFEST
|
||||||
AUTHORS
|
AUTHORS
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[gerrit]
|
[gerrit]
|
||||||
host=review.openstack.org
|
host=review.openstack.org
|
||||||
port=29418
|
port=29418
|
||||||
project=openstack-infra/git-review.git
|
project=openstack-infra/git-restack.git
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
||||||
${PYTHON:-python} -m subunit.run discover -t ./ ./git_review/tests $LISTOPT $IDOPTION
|
${PYTHON:-python} -m subunit.run discover -t ./ ./git_restack/tests $LISTOPT $IDOPTION
|
||||||
|
|
||||||
test_id_option=--load-list $IDFILE
|
test_id_option=--load-list $IDFILE
|
||||||
test_list_option=--list
|
test_list_option=--list
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
============================
|
============================
|
||||||
Contributing to git-review
|
Contributing to git-restack
|
||||||
============================
|
============================
|
||||||
|
|
||||||
To get the latest code, see: https://git.openstack.org/cgit/openstack-infra/git-review
|
To get the latest code, see: https://git.openstack.org/cgit/openstack-infra/git-restack
|
||||||
|
|
||||||
Bugs are handled at: https://storyboard.openstack.org/#!/project/719
|
Bugs are handled at: https://storyboard.openstack.org/#!/project/838
|
||||||
|
|
||||||
There is a mailing list at: http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-infra
|
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:
|
Code reviews are handled by gerrit at: https://review.openstack.org
|
||||||
https://review.openstack.org
|
|
||||||
|
|
||||||
See http://wiki.openstack.org/GerritWorkflow for details. Pull
|
See http://wiki.openstack.org/GerritWorkflow for details. Pull
|
||||||
requests submitted through GitHub will be ignored.
|
requests submitted through GitHub will be ignored.
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
Hacking git-review
|
Hacking git-restack
|
||||||
==================
|
==================
|
||||||
|
|
||||||
Development of git-review is managed by OpenStack's Gerrit, which can be
|
Development of git-restack is managed by OpenStack's Gerrit, which can be
|
||||||
found at https://review.openstack.org/
|
found at https://review.openstack.org/
|
||||||
|
|
||||||
Instructions on submitting patches can be found at
|
Instructions on submitting patches can be found at
|
||||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||||
|
|
||||||
git-review should, in general, not depend on a huge number of external
|
git-restack should, in general, not depend on a huge number of external
|
||||||
libraries, so that installing it is a lightweight operation.
|
libraries, so that installing it is a lightweight operation.
|
||||||
|
|
||||||
OpenStack Style Commandments
|
OpenStack Style Commandments
|
||||||
|
@ -3,5 +3,5 @@ include LICENSE
|
|||||||
include AUTHORS
|
include AUTHORS
|
||||||
include ChangeLog
|
include ChangeLog
|
||||||
include HACKING.rst
|
include HACKING.rst
|
||||||
include git-review.1
|
include git-restack.1
|
||||||
include tox.ini
|
include tox.ini
|
||||||
|
16
README.rst
16
README.rst
@ -1,12 +1,12 @@
|
|||||||
git-review
|
git-restack
|
||||||
==========
|
===========
|
||||||
|
|
||||||
A git command for submitting branches to Gerrit
|
A git command for editing a series of commits without rebasing.
|
||||||
|
|
||||||
git-review is a tool that helps submitting git branches to gerrit for
|
git-restack is a tool that performs an interactive git rebase of a
|
||||||
review.
|
branch without changing the commit upon which the branch is based.
|
||||||
|
|
||||||
* Free software: Apache license
|
* Free software: Apache license
|
||||||
* Documentation: http://docs.openstack.org/infra/git-review/
|
* Documentation: http://docs.openstack.org/infra/git-restack/
|
||||||
* Source: https://git.openstack.org/cgit/openstack-infra/git-review
|
* Source: https://git.openstack.org/cgit/openstack-infra/git-restack
|
||||||
* Bugs: https://storyboard.openstack.org/#!/project/719
|
* Bugs: https://storyboard.openstack.org/#!/project/838
|
||||||
|
@ -85,17 +85,17 @@ qthelp:
|
|||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/git-review.qhcp"
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/git-restack.qhcp"
|
||||||
@echo "To view the help file:"
|
@echo "To view the help file:"
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/git-review.qhc"
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/git-restack.qhc"
|
||||||
|
|
||||||
devhelp:
|
devhelp:
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished."
|
@echo "Build finished."
|
||||||
@echo "To view the help file:"
|
@echo "To view the help file:"
|
||||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/git-review"
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/git-restack"
|
||||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/git-review"
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/git-restack"
|
||||||
@echo "# devhelp"
|
@echo "# devhelp"
|
||||||
|
|
||||||
epub:
|
epub:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# git-review documentation build configuration file, created by
|
# git-restack documentation build configuration file, created by
|
||||||
# sphinx-quickstart on Mon Dec 1 14:06:22 2014.
|
# sphinx-quickstart on Mon Dec 1 14:06:22 2014.
|
||||||
#
|
#
|
||||||
# This file is execfile()d with the current directory set to its
|
# This file is execfile()d with the current directory set to its
|
||||||
@ -46,7 +46,7 @@ source_suffix = '.rst'
|
|||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'git-review'
|
project = u'git-restack'
|
||||||
copyright = u'2014, OpenStack Contributors'
|
copyright = u'2014, OpenStack Contributors'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
@ -170,7 +170,7 @@ html_theme = 'default'
|
|||||||
#html_file_suffix = None
|
#html_file_suffix = None
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'git-reviewdoc'
|
htmlhelp_basename = 'git-restackdoc'
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
@ -190,7 +190,7 @@ latex_elements = {
|
|||||||
# (source start file, target name, title,
|
# (source start file, target name, title,
|
||||||
# author, documentclass [howto, manual, or own class]).
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'git-review.tex', u'git-review Documentation',
|
('index', 'git-restack.tex', u'git-restack Documentation',
|
||||||
u'OpenStack Contributors', 'manual'),
|
u'OpenStack Contributors', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -220,7 +220,7 @@ latex_documents = [
|
|||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
('index', 'git-review', u'git-review Documentation',
|
('index', 'git-restack', u'git-restack Documentation',
|
||||||
[u'OpenStack Contributors'], 1)
|
[u'OpenStack Contributors'], 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -234,8 +234,8 @@ man_pages = [
|
|||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
('index', 'git-review', u'git-review Documentation',
|
('index', 'git-restack', u'git-restack Documentation',
|
||||||
u'OpenStack Contributors', 'git-review', 'One line description of project.',
|
u'OpenStack Contributors', 'git-restack', 'One line description of project.',
|
||||||
'Miscellaneous'),
|
'Miscellaneous'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2,18 +2,7 @@
|
|||||||
|
|
||||||
Running tests
|
Running tests
|
||||||
=============
|
=============
|
||||||
|
To run git-restack tests the following commands may by run::
|
||||||
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 py27
|
||||||
tox -e py26
|
tox -e py26
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
============
|
============
|
||||||
git-review
|
git-restack
|
||||||
============
|
============
|
||||||
|
|
||||||
``git-review`` is a tool that helps submitting git branches to gerrit
|
``git-restack`` is a tool that helps edit a series of commits without
|
||||||
for review.
|
rebasing.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
@ -2,89 +2,13 @@
|
|||||||
Installation and Configuration
|
Installation and Configuration
|
||||||
================================
|
================================
|
||||||
|
|
||||||
Installing git-review
|
Installing git-restack
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Install with pip install git-review
|
Install with pip install git-restack
|
||||||
|
|
||||||
For assistance installing pip on your os check out get-pip:
|
For assistance installing pip on your os check out get-pip:
|
||||||
http://pip.readthedocs.org/en/latest/installing.html
|
http://pip.readthedocs.org/en/latest/installing.html
|
||||||
|
|
||||||
For installation from source simply add git-review to your $PATH
|
For installation from source simply add git-restack to your $PATH
|
||||||
after installing the dependencies listed in requirements.txt
|
after installing the dependencies listed in requirements.txt
|
||||||
|
|
||||||
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.
|
|
||||||
|
@ -2,54 +2,11 @@
|
|||||||
Usage
|
Usage
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Hack on some code, then::
|
To interactively rebase the current branch against the most recent
|
||||||
|
commit in common with the master branch, run::
|
||||||
|
|
||||||
git review
|
git restack
|
||||||
|
|
||||||
If you want to submit that code to a branch other than "master", then::
|
If your branch is based on a different branch, run::
|
||||||
|
|
||||||
git review branchname
|
git restack 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
|
|
||||||
|
78
git-restack.1
Normal file
78
git-restack.1
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
.\" Uses mdoc(7). See `man 7 mdoc` for details about the syntax used here
|
||||||
|
.\"
|
||||||
|
.Dd December 18th, 2015
|
||||||
|
.Dt GIT\-RESTACK 1
|
||||||
|
.Sh NAME
|
||||||
|
.Nm git\-restack
|
||||||
|
.Nd Edit a series of commits without rebasing
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm
|
||||||
|
.Op Ar branch
|
||||||
|
.Nm
|
||||||
|
.Fl \-version
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
.Nm
|
||||||
|
performs an interactive git rebase of the current branch based on the
|
||||||
|
most recent commit in a target branch. When maintaining a large patch
|
||||||
|
series, it frequently becomes necessary to edit individual patches in
|
||||||
|
the series. Simply rebasing the series on the tip of the remote
|
||||||
|
branch has the secondary effect of changing the branch point of the
|
||||||
|
series. In some cases this may be desirable, but in others, such as
|
||||||
|
when using a code review system like Gerrit, it makes it difficult to
|
||||||
|
examine diffs between different versions of patchsets.
|
||||||
|
.Nm
|
||||||
|
will allow you to rebase the series without changing the commit the
|
||||||
|
series is based on.
|
||||||
|
.Pp
|
||||||
|
If supplied,
|
||||||
|
.Ar branch
|
||||||
|
indicates the branch this series is based on. If it is not present,
|
||||||
|
.Nm
|
||||||
|
will check git configuration or look for a
|
||||||
|
.Pa .gitreview
|
||||||
|
file and use the default branch specified there. If neither is found,
|
||||||
|
it defaults to the master branch.
|
||||||
|
.Pp
|
||||||
|
The following options are available:
|
||||||
|
.Bl -tag -width indent
|
||||||
|
.It gitreview.branch
|
||||||
|
This setting determines the default base branch
|
||||||
|
.Sh FILES
|
||||||
|
If there is a
|
||||||
|
.Pa .gitreview
|
||||||
|
file in the project,
|
||||||
|
.Nm
|
||||||
|
will use it to determine the default base branch.
|
||||||
|
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
|
||||||
|
When the same option is provided through FILES and CONFIGURATION, the
|
||||||
|
CONFIGURATION value wins.
|
||||||
|
.Pp
|
||||||
|
.Sh EXAMPLES
|
||||||
|
To perform an interactive rebase against the master branch:
|
||||||
|
.Pp
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
$ git\-restack
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
To perform an interactive rebase against a branch named
|
||||||
|
.Pa stable
|
||||||
|
:
|
||||||
|
.Pp
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
$ git\-restack stable
|
||||||
|
.Ed
|
||||||
|
.Sh BUGS
|
||||||
|
Bug reports can be submitted to
|
||||||
|
.Lk https://storyboard.openstack.org/#!/project/838
|
||||||
|
.Sh AUTHORS
|
||||||
|
.Nm
|
||||||
|
is maintained by
|
||||||
|
.An "The OpenStack project"
|
458
git-review.1
458
git-review.1
@ -1,458 +0,0 @@
|
|||||||
.\" 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 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 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 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 \-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 \-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 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
|
|
277
git_restack/cmd.py
Executable file
277
git_restack/cmd.py
Executable file
@ -0,0 +1,277 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
COPYRIGHT = """\
|
||||||
|
Copyright (C) 2011-2012 OpenStack LLC.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied.
|
||||||
|
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
if sys.version < '3':
|
||||||
|
import ConfigParser
|
||||||
|
import urllib
|
||||||
|
import urlparse
|
||||||
|
urlencode = urllib.urlencode
|
||||||
|
urljoin = urlparse.urljoin
|
||||||
|
urlparse = urlparse.urlparse
|
||||||
|
do_input = raw_input
|
||||||
|
else:
|
||||||
|
import configparser as ConfigParser
|
||||||
|
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
urlencode = urllib.parse.urlencode
|
||||||
|
urljoin = urllib.parse.urljoin
|
||||||
|
urlparse = urllib.parse.urlparse
|
||||||
|
do_input = input
|
||||||
|
|
||||||
|
VERBOSE = False
|
||||||
|
UPDATE = False
|
||||||
|
LOCAL_MODE = 'GITREVIEW_LOCAL_MODE' in os.environ
|
||||||
|
CONFIGDIR = os.path.expanduser("~/.config/git-review")
|
||||||
|
GLOBAL_CONFIG = "/etc/git-review/git-review.conf"
|
||||||
|
USER_CONFIG = os.path.join(CONFIGDIR, "git-review.conf")
|
||||||
|
DEFAULTS = dict(branch='master')
|
||||||
|
|
||||||
|
|
||||||
|
class GitRestackException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CommandFailed(GitRestackException):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
Exception.__init__(self, *args)
|
||||||
|
(self.rc, self.output, self.argv, self.envp) = args
|
||||||
|
self.quickmsg = dict([
|
||||||
|
("argv", " ".join(self.argv)),
|
||||||
|
("rc", self.rc),
|
||||||
|
("output", self.output)])
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.__doc__ + """
|
||||||
|
The following command failed with exit code %(rc)d
|
||||||
|
"%(argv)s"
|
||||||
|
-----------------------
|
||||||
|
%(output)s
|
||||||
|
-----------------------""" % self.quickmsg
|
||||||
|
|
||||||
|
|
||||||
|
class GitDirectoriesException(CommandFailed):
|
||||||
|
"Cannot determine where .git directory is."
|
||||||
|
EXIT_CODE = 70
|
||||||
|
|
||||||
|
|
||||||
|
class GitMergeBaseException(CommandFailed):
|
||||||
|
"Cannot determine merge base."
|
||||||
|
EXIT_CODE = 71
|
||||||
|
|
||||||
|
|
||||||
|
class GitConfigException(CommandFailed):
|
||||||
|
"""Git config value retrieval failed."""
|
||||||
|
EXIT_CODE = 128
|
||||||
|
|
||||||
|
|
||||||
|
def run_command_foreground(*argv, **kwargs):
|
||||||
|
if VERBOSE:
|
||||||
|
print(datetime.datetime.now(), "Running:", " ".join(argv))
|
||||||
|
if len(argv) == 1:
|
||||||
|
# for python2 compatibility with shlex
|
||||||
|
if sys.version_info < (3,) and isinstance(argv[0], unicode):
|
||||||
|
argv = shlex.split(argv[0].encode('utf-8'))
|
||||||
|
else:
|
||||||
|
argv = shlex.split(str(argv[0]))
|
||||||
|
subprocess.call(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def run_command_status(*argv, **kwargs):
|
||||||
|
if VERBOSE:
|
||||||
|
print(datetime.datetime.now(), "Running:", " ".join(argv))
|
||||||
|
if len(argv) == 1:
|
||||||
|
# for python2 compatibility with shlex
|
||||||
|
if sys.version_info < (3,) and isinstance(argv[0], unicode):
|
||||||
|
argv = shlex.split(argv[0].encode('utf-8'))
|
||||||
|
else:
|
||||||
|
argv = shlex.split(str(argv[0]))
|
||||||
|
stdin = kwargs.pop('stdin', None)
|
||||||
|
newenv = os.environ.copy()
|
||||||
|
newenv['LANG'] = 'C'
|
||||||
|
newenv['LANGUAGE'] = 'C'
|
||||||
|
newenv.update(kwargs)
|
||||||
|
p = subprocess.Popen(argv,
|
||||||
|
stdin=subprocess.PIPE if stdin else None,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
env=newenv)
|
||||||
|
(out, nothing) = p.communicate(stdin)
|
||||||
|
out = out.decode('utf-8', 'replace')
|
||||||
|
return (p.returncode, out.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def run_command(*argv, **kwargs):
|
||||||
|
(rc, output) = run_command_status(*argv, **kwargs)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def run_command_exc(klazz, *argv, **env):
|
||||||
|
"""Run command *argv, on failure raise klazz
|
||||||
|
|
||||||
|
klazz should be derived from CommandFailed
|
||||||
|
"""
|
||||||
|
(rc, output) = run_command_status(*argv, **env)
|
||||||
|
if rc != 0:
|
||||||
|
raise klazz(rc, output, argv, env)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def get_version():
|
||||||
|
requirement = pkg_resources.Requirement.parse('git-restack')
|
||||||
|
provider = pkg_resources.get_provider(requirement)
|
||||||
|
return provider.version
|
||||||
|
|
||||||
|
|
||||||
|
def git_directories():
|
||||||
|
"""Determine (absolute git work directory path, .git subdirectory path)."""
|
||||||
|
cmd = ("git", "rev-parse", "--show-toplevel", "--git-dir")
|
||||||
|
out = run_command_exc(GitDirectoriesException, *cmd)
|
||||||
|
try:
|
||||||
|
return out.splitlines()
|
||||||
|
except ValueError:
|
||||||
|
raise GitDirectoriesException(0, out, cmd, {})
|
||||||
|
|
||||||
|
|
||||||
|
def git_config_get_value(section, option, default=None, as_bool=False):
|
||||||
|
"""Get config value for section/option."""
|
||||||
|
cmd = ["git", "config", "--get", "%s.%s" % (section, option)]
|
||||||
|
if as_bool:
|
||||||
|
cmd.insert(2, "--bool")
|
||||||
|
if LOCAL_MODE:
|
||||||
|
__, git_dir = git_directories()
|
||||||
|
cmd[2:2] = ['-f', os.path.join(git_dir, 'config')]
|
||||||
|
try:
|
||||||
|
return run_command_exc(GitConfigException, *cmd).strip()
|
||||||
|
except GitConfigException as exc:
|
||||||
|
if exc.rc == 1:
|
||||||
|
return default
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
"""Expose as dictionary configuration options."""
|
||||||
|
|
||||||
|
def __init__(self, config_file=None):
|
||||||
|
self.config = DEFAULTS.copy()
|
||||||
|
filenames = [] if LOCAL_MODE else [GLOBAL_CONFIG, USER_CONFIG]
|
||||||
|
if config_file:
|
||||||
|
filenames.append(config_file)
|
||||||
|
for filename in filenames:
|
||||||
|
if os.path.exists(filename):
|
||||||
|
if filename != config_file:
|
||||||
|
msg = ("Using global/system git-review config files (%s) "
|
||||||
|
"is deprecated")
|
||||||
|
print(msg % filename)
|
||||||
|
self.config.update(load_config_file(filename))
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
value = git_config_get_value('gitreview', key)
|
||||||
|
if value is None:
|
||||||
|
value = self.config[key]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def load_config_file(config_file):
|
||||||
|
"""Load configuration options from a file."""
|
||||||
|
configParser = ConfigParser.ConfigParser()
|
||||||
|
configParser.read(config_file)
|
||||||
|
options = {
|
||||||
|
'scheme': 'scheme',
|
||||||
|
'hostname': 'host',
|
||||||
|
'port': 'port',
|
||||||
|
'project': 'project',
|
||||||
|
'branch': 'defaultbranch',
|
||||||
|
'remote': 'defaultremote',
|
||||||
|
'rebase': 'defaultrebase',
|
||||||
|
'track': 'track',
|
||||||
|
'usepushurl': 'usepushurl',
|
||||||
|
}
|
||||||
|
config = {}
|
||||||
|
for config_key, option_name in options.items():
|
||||||
|
if configParser.has_option('gerrit', option_name):
|
||||||
|
config[config_key] = configParser.get('gerrit', option_name)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
usage = "git restack [BRANCH]"
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(usage=usage, description=COPYRIGHT)
|
||||||
|
|
||||||
|
parser.add_argument("-v", "--verbose", dest="verbose", action="store_true",
|
||||||
|
help="Output more information about what's going on")
|
||||||
|
parser.add_argument("--license", dest="license", action="store_true",
|
||||||
|
help="Print the license and exit")
|
||||||
|
parser.add_argument("--version", action="version",
|
||||||
|
version='%s version %s' %
|
||||||
|
(os.path.split(sys.argv[0])[-1], get_version()))
|
||||||
|
parser.add_argument("branch", nargs="?")
|
||||||
|
|
||||||
|
parser.set_defaults(verbose=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
(top_dir, git_dir) = git_directories()
|
||||||
|
except GitDirectoriesException as no_git_dir:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
no_git_dir = False
|
||||||
|
config = Config(os.path.join(top_dir, ".gitreview"))
|
||||||
|
options = parser.parse_args()
|
||||||
|
if no_git_dir:
|
||||||
|
raise no_git_dir
|
||||||
|
|
||||||
|
if options.license:
|
||||||
|
print(COPYRIGHT)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
global VERBOSE
|
||||||
|
VERBOSE = options.verbose
|
||||||
|
|
||||||
|
if options.branch is None:
|
||||||
|
branch = config['branch']
|
||||||
|
else:
|
||||||
|
branch = options.branch
|
||||||
|
|
||||||
|
if branch is None:
|
||||||
|
branch = 'master'
|
||||||
|
|
||||||
|
status = 0
|
||||||
|
|
||||||
|
cmd = "git merge-base HEAD origin/%s" % branch
|
||||||
|
base = run_command_exc(GitMergeBaseException, cmd)
|
||||||
|
|
||||||
|
run_command_foreground("git rebase -i %s" % base, stdin=sys.stdin)
|
||||||
|
|
||||||
|
sys.exit(status)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
86
git_restack/tests/__init__.py
Normal file
86
git_restack/tests/__init__.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# 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 fixtures
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from git_restack.tests import utils
|
||||||
|
|
||||||
|
|
||||||
|
class BaseGitRestackTestCase(testtools.TestCase):
|
||||||
|
"""Base class for the git-restack tests."""
|
||||||
|
|
||||||
|
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(BaseGitRestackTestCase, self).setUp()
|
||||||
|
self.useFixture(fixtures.Timeout(2 * 60, True))
|
||||||
|
|
||||||
|
self.root_dir = self.useFixture(fixtures.TempDir()).path
|
||||||
|
self.upstream_dir = os.path.join(self.root_dir, "upstream")
|
||||||
|
self.local_dir = os.path.join(self.root_dir, "local")
|
||||||
|
|
||||||
|
os.makedirs(self._dir('upstream'))
|
||||||
|
self._run_git('upstream', 'init')
|
||||||
|
self._simple_change('upstream', 'initial text', 'initial commit')
|
||||||
|
self._simple_change('upstream', 'second text', 'second commit')
|
||||||
|
self._run_git('upstream', 'checkout', '-b', 'branch1')
|
||||||
|
self._simple_change('upstream', 'branch1 text', 'branch1 commit')
|
||||||
|
self._run_git('upstream', 'checkout', 'master')
|
||||||
|
self._run_git('upstream', 'checkout', '-b', 'branch2')
|
||||||
|
|
||||||
|
gitreview = '[gerrit]\ndefaultbranch=branch2\n'
|
||||||
|
self._simple_change('upstream', gitreview, 'branch2 commit',
|
||||||
|
file_=self._dir('upstream', '.gitreview'))
|
||||||
|
self._run_git('upstream', 'checkout', 'master')
|
||||||
|
|
||||||
|
def _dir(self, base, *args):
|
||||||
|
"""Creates directory name from base name and other parameters."""
|
||||||
|
return os.path.join(getattr(self, base + '_dir'), *args)
|
||||||
|
|
||||||
|
def _run_git(self, dirname, command, *args):
|
||||||
|
"""Run git command using test git directory."""
|
||||||
|
if command == 'clone':
|
||||||
|
return utils.run_git(command, args[0], self._dir(dirname))
|
||||||
|
return utils.run_git('--git-dir=' + self._dir(dirname, '.git'),
|
||||||
|
'--work-tree=' + self._dir(dirname),
|
||||||
|
command, *args)
|
||||||
|
|
||||||
|
def _run_git_restack(self, *args, **kwargs):
|
||||||
|
"""Run git-restack utility from source."""
|
||||||
|
git_restack = utils.run_cmd('which', 'git-restack')
|
||||||
|
kwargs.setdefault('chdir', self.local_dir)
|
||||||
|
return utils.run_cmd(git_restack, *args, **kwargs)
|
||||||
|
|
||||||
|
def _simple_change(self, dirname, change_text, commit_message,
|
||||||
|
file_=None):
|
||||||
|
"""Helper method to create small changes and commit them."""
|
||||||
|
if file_ is None:
|
||||||
|
file_ = self._dir(dirname, 'test_file.txt')
|
||||||
|
utils.write_to_file(file_, change_text.encode())
|
||||||
|
self._run_git(dirname, 'add', file_)
|
||||||
|
self._run_git(dirname, 'commit', '-m', commit_message)
|
||||||
|
|
||||||
|
def _git_log(self, dirname):
|
||||||
|
out = self._run_git(dirname, 'log', '--oneline')
|
||||||
|
commits = []
|
||||||
|
for line in out.split('\n'):
|
||||||
|
commits.append(line.split(' ', 1))
|
||||||
|
return commits
|
92
git_restack/tests/test_git_restack.py
Normal file
92
git_restack/tests/test_git_restack.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# -*- 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.
|
||||||
|
|
||||||
|
from git_restack import tests
|
||||||
|
|
||||||
|
|
||||||
|
class GitRestackTestCase(tests.BaseGitRestackTestCase):
|
||||||
|
"""Class for config tests."""
|
||||||
|
|
||||||
|
def test_git_restack(self):
|
||||||
|
self._run_git('local', 'clone', self._dir('upstream'))
|
||||||
|
self._simple_change('local', 'b1 text', 'b1')
|
||||||
|
self._simple_change('local', 'b2 text', 'b2')
|
||||||
|
self._simple_change('local', 'b3 text', 'b3')
|
||||||
|
|
||||||
|
commits = self._git_log('local')
|
||||||
|
self.assertEqual(commits[0][1], 'b3')
|
||||||
|
self.assertEqual(commits[1][1], 'b2')
|
||||||
|
self.assertEqual(commits[2][1], 'b1')
|
||||||
|
self.assertEqual(commits[3][1], 'second commit')
|
||||||
|
self.assertEqual(commits[4][1], 'initial commit')
|
||||||
|
out = self._run_git_restack()
|
||||||
|
lines = out.split('\n')
|
||||||
|
self.assertEqual(lines[0], 'pick %s %s' %
|
||||||
|
(commits[2][0], commits[2][1]))
|
||||||
|
self.assertEqual(lines[1], 'pick %s %s' %
|
||||||
|
(commits[1][0], commits[1][1]))
|
||||||
|
self.assertEqual(lines[2], 'pick %s %s' %
|
||||||
|
(commits[0][0], commits[0][1]))
|
||||||
|
self.assertEqual(lines[3], '')
|
||||||
|
|
||||||
|
def test_git_restack_gitreview(self):
|
||||||
|
self._run_git('local', 'clone', self._dir('upstream'))
|
||||||
|
self._run_git('local', 'checkout', 'branch2')
|
||||||
|
self._simple_change('local', 'b1 text', 'b1')
|
||||||
|
self._simple_change('local', 'b2 text', 'b2')
|
||||||
|
self._simple_change('local', 'b3 text', 'b3')
|
||||||
|
|
||||||
|
commits = self._git_log('local')
|
||||||
|
self.assertEqual(commits[0][1], 'b3')
|
||||||
|
self.assertEqual(commits[1][1], 'b2')
|
||||||
|
self.assertEqual(commits[2][1], 'b1')
|
||||||
|
self.assertEqual(commits[3][1], 'branch2 commit')
|
||||||
|
self.assertEqual(commits[4][1], 'second commit')
|
||||||
|
self.assertEqual(commits[5][1], 'initial commit')
|
||||||
|
out = self._run_git_restack()
|
||||||
|
lines = out.split('\n')
|
||||||
|
self.assertEqual(lines[0], 'pick %s %s' %
|
||||||
|
(commits[2][0], commits[2][1]))
|
||||||
|
self.assertEqual(lines[1], 'pick %s %s' %
|
||||||
|
(commits[1][0], commits[1][1]))
|
||||||
|
self.assertEqual(lines[2], 'pick %s %s' %
|
||||||
|
(commits[0][0], commits[0][1]))
|
||||||
|
self.assertEqual(lines[3], '')
|
||||||
|
|
||||||
|
def test_git_restack_arg(self):
|
||||||
|
self._run_git('local', 'clone', self._dir('upstream'))
|
||||||
|
self._run_git('local', 'checkout', 'branch1')
|
||||||
|
self._simple_change('local', 'b1 text', 'b1')
|
||||||
|
self._simple_change('local', 'b2 text', 'b2')
|
||||||
|
self._simple_change('local', 'b3 text', 'b3')
|
||||||
|
|
||||||
|
commits = self._git_log('local')
|
||||||
|
self.assertEqual(commits[0][1], 'b3')
|
||||||
|
self.assertEqual(commits[1][1], 'b2')
|
||||||
|
self.assertEqual(commits[2][1], 'b1')
|
||||||
|
self.assertEqual(commits[3][1], 'branch1 commit')
|
||||||
|
self.assertEqual(commits[4][1], 'second commit')
|
||||||
|
self.assertEqual(commits[5][1], 'initial commit')
|
||||||
|
out = self._run_git_restack('branch1')
|
||||||
|
lines = out.split('\n')
|
||||||
|
self.assertEqual(lines[0], 'pick %s %s' %
|
||||||
|
(commits[2][0], commits[2][1]))
|
||||||
|
self.assertEqual(lines[1], 'pick %s %s' %
|
||||||
|
(commits[1][0], commits[1][1]))
|
||||||
|
self.assertEqual(lines[2], 'pick %s %s' %
|
||||||
|
(commits[0][0], commits[0][1]))
|
||||||
|
self.assertEqual(lines[3], '')
|
@ -27,6 +27,7 @@ def run_cmd(*args, **kwargs):
|
|||||||
return os.chdir(kwargs['chdir'])
|
return os.chdir(kwargs['chdir'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
os.environ['EDITOR'] = '/bin/cat'
|
||||||
proc = subprocess.Popen(args, stdin=subprocess.PIPE,
|
proc = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT, env=os.environ,
|
stderr=subprocess.STDOUT, env=os.environ,
|
1577
git_review/cmd.py
1577
git_review/cmd.py
File diff suppressed because it is too large
Load Diff
@ -1,335 +0,0 @@
|
|||||||
# 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 shutil
|
|
||||||
import stat
|
|
||||||
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 = 'https://gerrit-releases.storage.googleapis.com/gerrit-2.9.2.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 = '2'
|
|
||||||
|
|
||||||
|
|
||||||
class GerritHelpers(object):
|
|
||||||
|
|
||||||
def _dir(self, base, *args):
|
|
||||||
"""Creates directory name from base name and other parameters."""
|
|
||||||
return os.path.join(getattr(self, base + '_dir'), *args)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
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."""
|
|
||||||
|
|
||||||
_test_counter = 0
|
|
||||||
_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))
|
|
||||||
BaseGitReviewTestCase._test_counter += 1
|
|
||||||
|
|
||||||
# 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')
|
|
||||||
|
|
||||||
# ensure user proxy conf doesn't interfere with tests
|
|
||||||
os.environ['no_proxy'] = os.environ['NO_PROXY'] = '*'
|
|
||||||
|
|
||||||
# isolate tests from user and system git configuration
|
|
||||||
self.home_dir = self._dir('site', 'tmp', 'home')
|
|
||||||
self.xdg_config_dir = self._dir('home', '.xdgconfig')
|
|
||||||
os.environ['HOME'] = self.home_dir
|
|
||||||
os.environ['XDG_CONFIG_HOME'] = self.xdg_config_dir
|
|
||||||
os.environ['GIT_CONFIG_NOSYSTEM'] = "1"
|
|
||||||
os.environ['EMAIL'] = "you@example.com"
|
|
||||||
if not os.path.exists(self.home_dir):
|
|
||||||
os.mkdir(self.home_dir)
|
|
||||||
if not os.path.exists(self.xdg_config_dir):
|
|
||||||
os.mkdir(self.xdg_config_dir)
|
|
||||||
self.addCleanup(shutil.rmtree, self.home_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')
|
|
||||||
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):
|
|
||||||
pid = os.getpid()
|
|
||||||
host = '127.%s.%s.%s' % (self._test_counter, pid >> 8, pid & 255)
|
|
||||||
return host, 29418, host, 8080, self._dir('gerrit', 'site-' + host)
|
|
||||||
|
|
||||||
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')
|
|
@ -1,26 +0,0 @@
|
|||||||
# 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()
|
|
@ -1,554 +0,0 @@
|
|||||||
# -*- 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'))
|
|
||||||
|
|
||||||
# second download should also work correct
|
|
||||||
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',
|
|
||||||
branch,
|
|
||||||
'remotes/origin/other')
|
|
||||||
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')
|
|
||||||
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
|
|
@ -1,296 +0,0 @@
|
|||||||
# -*- 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 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'
|
|
||||||
}, {
|
|
||||||
'number': '9877',
|
|
||||||
'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):
|
|
||||||
super(GitReviewConsole, self).setUp()
|
|
||||||
# ensure all tests get a separate git dir to work in to avoid
|
|
||||||
# local git config from interfering
|
|
||||||
self.tempdir = self.useFixture(fixtures.TempDir())
|
|
||||||
self._run_git = functools.partial(utils.run_git,
|
|
||||||
chdir=self.tempdir.path)
|
|
||||||
|
|
||||||
self.run_cmd_patcher = mock.patch('git_review.cmd.run_command_status')
|
|
||||||
run_cmd_partial = functools.partial(
|
|
||||||
cmd.run_command_status, GIT_WORK_TREE=self.tempdir.path,
|
|
||||||
GIT_DIR=os.path.join(self.tempdir.path, '.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_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)
|
|
||||||
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_exc')
|
|
||||||
@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_exc',
|
|
||||||
return_value=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)
|
|
||||||
mock_run.assert_called_once_with(mock.ANY, 'git', 'credential', 'fill',
|
|
||||||
stdin='url=%s' % url)
|
|
||||||
calls = [mock.call(url), mock.call(url, auth=('user', 'pass'))]
|
|
||||||
mock_get.assert_has_calls(calls)
|
|
||||||
|
|
||||||
@mock.patch('git_review.cmd.run_command_exc',
|
|
||||||
return_value=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)
|
|
||||||
mock_run.assert_called_once_with(mock.ANY, 'git', 'credential', 'fill',
|
|
||||||
stdin='url=%s' % url)
|
|
||||||
calls = [mock.call(url), mock.call(url, auth=('user', 'pass'))]
|
|
||||||
mock_get.assert_has_calls(calls)
|
|
||||||
|
|
||||||
@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)
|
|
@ -1,2 +1 @@
|
|||||||
argparse
|
argparse
|
||||||
requests>=1.1
|
|
||||||
|
14
setup.cfg
14
setup.cfg
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = git-review
|
name = git-restack
|
||||||
summary = Tool to submit code to Gerrit
|
summary = Tool to rebase a git branch
|
||||||
description-file = README.rst
|
description-file = README.rst
|
||||||
license = Apache License (2.0)
|
license = Apache License (2.0)
|
||||||
classifiers =
|
classifiers =
|
||||||
@ -14,19 +14,19 @@ classifiers =
|
|||||||
Intended Audience :: Information Technology
|
Intended Audience :: Information Technology
|
||||||
License :: OSI Approved :: Apache Software License
|
License :: OSI Approved :: Apache Software License
|
||||||
Operating System :: OS Independent
|
Operating System :: OS Independent
|
||||||
keywords = git gerrit review
|
keywords = git gerrit restack
|
||||||
author = OpenStack
|
author = OpenStack
|
||||||
author-email = openstack-infra@lists.openstack.org
|
author-email = openstack-infra@lists.openstack.org
|
||||||
home-page = https://docs.openstack.org/infra/git-review/
|
home-page = https://docs.openstack.org/infra/git-restack/
|
||||||
project-url = https://docs.openstack.org/infra/
|
project-url = https://docs.openstack.org/infra/
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
packages =
|
packages =
|
||||||
git_review
|
git_restack
|
||||||
|
|
||||||
[entry_points]
|
[entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
git-review = git_review.cmd:main
|
git-restack = git_restack.cmd:main
|
||||||
|
|
||||||
[wheel]
|
[wheel]
|
||||||
universal = 1
|
universal = 1
|
||||||
@ -38,5 +38,5 @@ all_files = 1
|
|||||||
|
|
||||||
[pbr]
|
[pbr]
|
||||||
manpages =
|
manpages =
|
||||||
git-review.1
|
git-restack.1
|
||||||
warnerrors = True
|
warnerrors = True
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
hacking>=0.10.0,<0.11
|
hacking>=0.10.0,<0.11
|
||||||
discover
|
discover
|
||||||
mock
|
|
||||||
fixtures>=0.3.14
|
fixtures>=0.3.14
|
||||||
testrepository>=0.0.18
|
testrepository>=0.0.18
|
||||||
testtools>=0.9.34
|
testtools>=0.9.34
|
||||||
|
1
tox.ini
1
tox.ini
@ -7,7 +7,6 @@ setenv =
|
|||||||
VIRTUAL_ENV={envdir}
|
VIRTUAL_ENV={envdir}
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
python -m git_review.tests.prepare
|
|
||||||
python setup.py testr --slowest --testr-args='--concurrency=2 {posargs}'
|
python setup.py testr --slowest --testr-args='--concurrency=2 {posargs}'
|
||||||
|
|
||||||
deps =
|
deps =
|
||||||
|
Loading…
Reference in New Issue
Block a user