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
|
||||
dist
|
||||
git_review.egg-info
|
||||
git_restack.egg-info
|
||||
MANIFEST
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
@ -1,4 +1,4 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
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} \
|
||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
||||
${PYTHON:-python} -m subunit.run discover -t ./ ./git_review/tests $LISTOPT $IDOPTION
|
||||
${PYTHON:-python} -m subunit.run discover -t ./ ./git_restack/tests $LISTOPT $IDOPTION
|
||||
|
||||
test_id_option=--load-list $IDFILE
|
||||
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
|
||||
|
||||
Code reviews, as you might expect, are handled by gerrit at:
|
||||
https://review.openstack.org
|
||||
Code reviews are handled by gerrit at: https://review.openstack.org
|
||||
|
||||
See http://wiki.openstack.org/GerritWorkflow for details. Pull
|
||||
requests submitted through GitHub will be ignored.
|
||||
|
@ -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/
|
||||
|
||||
Instructions on submitting patches can be found at
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
git-review should, in general, not depend on a huge number of external
|
||||
git-restack should, in general, not depend on a huge number of external
|
||||
libraries, so that installing it is a lightweight operation.
|
||||
|
||||
OpenStack Style Commandments
|
||||
|
@ -3,5 +3,5 @@ include LICENSE
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
include HACKING.rst
|
||||
include git-review.1
|
||||
include git-restack.1
|
||||
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
|
||||
review.
|
||||
git-restack is a tool that performs an interactive git rebase of a
|
||||
branch without changing the commit upon which the branch is based.
|
||||
|
||||
* Free software: Apache license
|
||||
* Documentation: http://docs.openstack.org/infra/git-review/
|
||||
* Source: https://git.openstack.org/cgit/openstack-infra/git-review
|
||||
* Bugs: https://storyboard.openstack.org/#!/project/719
|
||||
* Documentation: http://docs.openstack.org/infra/git-restack/
|
||||
* Source: https://git.openstack.org/cgit/openstack-infra/git-restack
|
||||
* Bugs: https://storyboard.openstack.org/#!/project/838
|
||||
|
@ -85,17 +85,17 @@ qthelp:
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/git-review.qhcp"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/git-restack.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/git-review.qhc"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/git-restack.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/git-review"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/git-review"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/git-restack"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/git-restack"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
@ -46,7 +46,7 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'git-review'
|
||||
project = u'git-restack'
|
||||
copyright = u'2014, OpenStack Contributors'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
@ -170,7 +170,7 @@ html_theme = 'default'
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'git-reviewdoc'
|
||||
htmlhelp_basename = 'git-restackdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
@ -190,7 +190,7 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'git-review.tex', u'git-review Documentation',
|
||||
('index', 'git-restack.tex', u'git-restack Documentation',
|
||||
u'OpenStack Contributors', 'manual'),
|
||||
]
|
||||
|
||||
@ -220,7 +220,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'git-review', u'git-review Documentation',
|
||||
('index', 'git-restack', u'git-restack Documentation',
|
||||
[u'OpenStack Contributors'], 1)
|
||||
]
|
||||
|
||||
@ -234,8 +234,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'git-review', u'git-review Documentation',
|
||||
u'OpenStack Contributors', 'git-review', 'One line description of project.',
|
||||
('index', 'git-restack', u'git-restack Documentation',
|
||||
u'OpenStack Contributors', 'git-restack', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
@ -2,18 +2,7 @@
|
||||
|
||||
Running tests
|
||||
=============
|
||||
|
||||
Running tests for git-review means running a local copy of Gerrit to
|
||||
check that git-review interacts correctly with it. This requires the
|
||||
following:
|
||||
|
||||
* a Java Runtime Environment on the machine to run tests on
|
||||
|
||||
* Internet access to download the gerrit.war file, or a locally
|
||||
cached copy (it needs to be located in a .gerrit directory at the
|
||||
top level of the git-review project)
|
||||
|
||||
To run git-review integration tests the following commands may by run::
|
||||
To run git-restack tests the following commands may by run::
|
||||
|
||||
tox -e py27
|
||||
tox -e py26
|
||||
|
@ -1,9 +1,9 @@
|
||||
============
|
||||
git-review
|
||||
git-restack
|
||||
============
|
||||
|
||||
``git-review`` is a tool that helps submitting git branches to gerrit
|
||||
for review.
|
||||
``git-restack`` is a tool that helps edit a series of commits without
|
||||
rebasing.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
@ -2,89 +2,13 @@
|
||||
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:
|
||||
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
|
||||
|
||||
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
|
||||
=======
|
||||
|
||||
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
|
||||
|
||||
If you want to submit to a different remote::
|
||||
|
||||
git review -r my-remote
|
||||
|
||||
If you want to supply a review topic::
|
||||
|
||||
git review -t topic/awesome-feature
|
||||
|
||||
If you want to subscribe some reviewers::
|
||||
|
||||
git review --reviewers a@example.com b@example.com
|
||||
|
||||
If you want to disable autogenerated topic::
|
||||
|
||||
git review -T
|
||||
|
||||
If you want to submit a branch for review and then remove the local branch::
|
||||
|
||||
git review -f
|
||||
|
||||
If you want to skip the automatic "git rebase -i" step::
|
||||
|
||||
git review -R
|
||||
|
||||
If you want to download change 781 from gerrit to review it::
|
||||
|
||||
git review -d 781
|
||||
|
||||
If you want to download patchset 4 for change 781 from gerrit to review it::
|
||||
|
||||
git review -d 781,4
|
||||
|
||||
If you want to compare patchset 4 with patchset 10 of change 781 from gerrit::
|
||||
|
||||
git review -m 781,4-10
|
||||
|
||||
If you want to see a list of open reviews::
|
||||
|
||||
git review -l
|
||||
|
||||
If you just want to do the commit message and remote setup steps::
|
||||
|
||||
git review -s
|
||||
git restack branchname
|
||||
|
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'])
|
||||
|
||||
try:
|
||||
os.environ['EDITOR'] = '/bin/cat'
|
||||
proc = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
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
|
||||
requests>=1.1
|
||||
|
14
setup.cfg
14
setup.cfg
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = git-review
|
||||
summary = Tool to submit code to Gerrit
|
||||
name = git-restack
|
||||
summary = Tool to rebase a git branch
|
||||
description-file = README.rst
|
||||
license = Apache License (2.0)
|
||||
classifiers =
|
||||
@ -14,19 +14,19 @@ classifiers =
|
||||
Intended Audience :: Information Technology
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: OS Independent
|
||||
keywords = git gerrit review
|
||||
keywords = git gerrit restack
|
||||
author = OpenStack
|
||||
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/
|
||||
|
||||
[files]
|
||||
packages =
|
||||
git_review
|
||||
git_restack
|
||||
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
git-review = git_review.cmd:main
|
||||
git-restack = git_restack.cmd:main
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
||||
@ -38,5 +38,5 @@ all_files = 1
|
||||
|
||||
[pbr]
|
||||
manpages =
|
||||
git-review.1
|
||||
git-restack.1
|
||||
warnerrors = True
|
||||
|
@ -1,6 +1,5 @@
|
||||
hacking>=0.10.0,<0.11
|
||||
discover
|
||||
mock
|
||||
fixtures>=0.3.14
|
||||
testrepository>=0.0.18
|
||||
testtools>=0.9.34
|
||||
|
Loading…
Reference in New Issue
Block a user