Browse Source

Initial commit of git-restack

Change-Id: I5508816fcd5c6362ee17a494ae8b997de29505d3
tags/1.0.0^0
James E. Blair 3 years ago
parent
commit
c6a59489fa

+ 1
- 1
.gitignore View File

@@ -1,6 +1,6 @@
1 1
 build
2 2
 dist
3
-git_review.egg-info
3
+git_restack.egg-info
4 4
 MANIFEST
5 5
 AUTHORS
6 6
 ChangeLog

+ 1
- 1
.gitreview View File

@@ -1,4 +1,4 @@
1 1
 [gerrit]
2 2
 host=review.openstack.org
3 3
 port=29418
4
-project=openstack-infra/git-review.git
4
+project=openstack-infra/git-restack.git

+ 1
- 1
.testr.conf View File

@@ -2,7 +2,7 @@
2 2
 test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
3 3
              OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
4 4
              OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
5
-             ${PYTHON:-python} -m subunit.run discover -t ./ ./git_review/tests $LISTOPT $IDOPTION
5
+             ${PYTHON:-python} -m subunit.run discover -t ./ ./git_restack/tests $LISTOPT $IDOPTION
6 6
 
7 7
 test_id_option=--load-list $IDFILE
8 8
 test_list_option=--list

+ 4
- 5
CONTRIBUTING.rst View File

@@ -1,15 +1,14 @@
1 1
 ============================
2
- Contributing to git-review
2
+ Contributing to git-restack
3 3
 ============================
4 4
 
5
-To get the latest code, see: https://git.openstack.org/cgit/openstack-infra/git-review
5
+To get the latest code, see: https://git.openstack.org/cgit/openstack-infra/git-restack
6 6
 
7
-Bugs are handled at: https://storyboard.openstack.org/#!/project/719
7
+Bugs are handled at: https://storyboard.openstack.org/#!/project/838
8 8
 
9 9
 There is a mailing list at: http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-infra
10 10
 
11
-Code reviews, as you might expect, are handled by gerrit at:
12
-https://review.openstack.org
11
+Code reviews are handled by gerrit at: https://review.openstack.org
13 12
 
14 13
 See http://wiki.openstack.org/GerritWorkflow for details. Pull
15 14
 requests submitted through GitHub will be ignored.

+ 3
- 3
HACKING.rst View File

@@ -1,13 +1,13 @@
1
-Hacking git-review
1
+Hacking git-restack
2 2
 ==================
3 3
 
4
-Development of git-review is managed by OpenStack's Gerrit, which can be
4
+Development of git-restack is managed by OpenStack's Gerrit, which can be
5 5
 found at https://review.openstack.org/
6 6
 
7 7
 Instructions on submitting patches can be found at
8 8
 http://docs.openstack.org/infra/manual/developers.html#development-workflow
9 9
 
10
-git-review should, in general, not depend on a huge number of external
10
+git-restack should, in general, not depend on a huge number of external
11 11
 libraries, so that installing it is a lightweight operation.
12 12
 
13 13
 OpenStack Style Commandments

+ 1
- 1
MANIFEST.in View File

@@ -3,5 +3,5 @@ include LICENSE
3 3
 include AUTHORS
4 4
 include ChangeLog
5 5
 include HACKING.rst
6
-include git-review.1
6
+include git-restack.1
7 7
 include tox.ini

+ 8
- 8
README.rst View File

@@ -1,12 +1,12 @@
1
-git-review
2
-==========
1
+git-restack
2
+===========
3 3
 
4
-A git command for submitting branches to Gerrit
4
+A git command for editing a series of commits without rebasing.
5 5
 
6
-git-review is a tool that helps submitting git branches to gerrit for
7
-review.
6
+git-restack is a tool that performs an interactive git rebase of a
7
+branch without changing the commit upon which the branch is based.
8 8
 
9 9
 * Free software: Apache license
10
-* Documentation: http://docs.openstack.org/infra/git-review/
11
-* Source: https://git.openstack.org/cgit/openstack-infra/git-review
12
-* Bugs: https://storyboard.openstack.org/#!/project/719
10
+* Documentation: http://docs.openstack.org/infra/git-restack/
11
+* Source: https://git.openstack.org/cgit/openstack-infra/git-restack
12
+* Bugs: https://storyboard.openstack.org/#!/project/838

+ 4
- 4
doc/Makefile View File

@@ -85,17 +85,17 @@ qthelp:
85 85
 	@echo
86 86
 	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
87 87
 	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
88
-	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/git-review.qhcp"
88
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/git-restack.qhcp"
89 89
 	@echo "To view the help file:"
90
-	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/git-review.qhc"
90
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/git-restack.qhc"
91 91
 
92 92
 devhelp:
93 93
 	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
94 94
 	@echo
95 95
 	@echo "Build finished."
96 96
 	@echo "To view the help file:"
97
-	@echo "# mkdir -p $$HOME/.local/share/devhelp/git-review"
98
-	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/git-review"
97
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/git-restack"
98
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/git-restack"
99 99
 	@echo "# devhelp"
100 100
 
101 101
 epub:

+ 7
- 7
doc/source/conf.py View File

@@ -1,6 +1,6 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 #
3
-# git-review documentation build configuration file, created by
3
+# git-restack documentation build configuration file, created by
4 4
 # sphinx-quickstart on Mon Dec  1 14:06:22 2014.
5 5
 #
6 6
 # This file is execfile()d with the current directory set to its
@@ -46,7 +46,7 @@ source_suffix = '.rst'
46 46
 master_doc = 'index'
47 47
 
48 48
 # General information about the project.
49
-project = u'git-review'
49
+project = u'git-restack'
50 50
 copyright = u'2014, OpenStack Contributors'
51 51
 
52 52
 # The language for content autogenerated by Sphinx. Refer to documentation
@@ -170,7 +170,7 @@ html_theme = 'default'
170 170
 #html_file_suffix = None
171 171
 
172 172
 # Output file base name for HTML help builder.
173
-htmlhelp_basename = 'git-reviewdoc'
173
+htmlhelp_basename = 'git-restackdoc'
174 174
 
175 175
 
176 176
 # -- Options for LaTeX output ---------------------------------------------
@@ -190,7 +190,7 @@ latex_elements = {
190 190
 # (source start file, target name, title,
191 191
 #  author, documentclass [howto, manual, or own class]).
192 192
 latex_documents = [
193
-  ('index', 'git-review.tex', u'git-review Documentation',
193
+  ('index', 'git-restack.tex', u'git-restack Documentation',
194 194
    u'OpenStack Contributors', 'manual'),
195 195
 ]
196 196
 
@@ -220,7 +220,7 @@ latex_documents = [
220 220
 # One entry per manual page. List of tuples
221 221
 # (source start file, name, description, authors, manual section).
222 222
 man_pages = [
223
-    ('index', 'git-review', u'git-review Documentation',
223
+    ('index', 'git-restack', u'git-restack Documentation',
224 224
      [u'OpenStack Contributors'], 1)
225 225
 ]
226 226
 
@@ -234,8 +234,8 @@ man_pages = [
234 234
 # (source start file, target name, title, author,
235 235
 #  dir menu entry, description, category)
236 236
 texinfo_documents = [
237
-  ('index', 'git-review', u'git-review Documentation',
238
-   u'OpenStack Contributors', 'git-review', 'One line description of project.',
237
+  ('index', 'git-restack', u'git-restack Documentation',
238
+   u'OpenStack Contributors', 'git-restack', 'One line description of project.',
239 239
    'Miscellaneous'),
240 240
 ]
241 241
 

+ 1
- 12
doc/source/developing.rst View File

@@ -2,18 +2,7 @@
2 2
 
3 3
 Running tests
4 4
 =============
5
-
6
-Running tests for git-review means running a local copy of Gerrit to
7
-check that git-review interacts correctly with it. This requires the
8
-following:
9
-
10
-* a Java Runtime Environment on the machine to run tests on
11
-
12
-* Internet access to download the gerrit.war file, or a locally
13
-  cached copy (it needs to be located in a .gerrit directory at the
14
-  top level of the git-review project)
15
-
16
-To run git-review integration tests the following commands may by run::
5
+To run git-restack tests the following commands may by run::
17 6
 
18 7
     tox -e py27
19 8
     tox -e py26

+ 3
- 3
doc/source/index.rst View File

@@ -1,9 +1,9 @@
1 1
 ============
2
- git-review
2
+ git-restack
3 3
 ============
4 4
 
5
-``git-review`` is a tool that helps submitting git branches to gerrit
6
-for review.
5
+``git-restack`` is a tool that helps edit a series of commits without
6
+rebasing.
7 7
 
8 8
 .. toctree::
9 9
    :maxdepth: 2

+ 3
- 79
doc/source/installation.rst View File

@@ -2,89 +2,13 @@
2 2
  Installation and Configuration
3 3
 ================================
4 4
 
5
-Installing git-review
5
+Installing git-restack
6 6
 =====================
7 7
 
8
-Install with pip install git-review
8
+Install with pip install git-restack
9 9
 
10 10
 For assistance installing pip on your os check out get-pip:
11 11
 http://pip.readthedocs.org/en/latest/installing.html
12 12
 
13
-For installation from source simply add git-review to your $PATH
13
+For installation from source simply add git-restack to your $PATH
14 14
 after installing the dependencies listed in requirements.txt
15
-
16
-Setup
17
-=====
18
-
19
-By default, git-review will look for a remote named 'gerrit' for working
20
-with Gerrit. If the remote exists, git-review will submit the current
21
-branch to HEAD:refs/for/master at that remote.
22
-
23
-If the Gerrit remote does not exist, git-review looks for a file
24
-called .gitreview at the root of the repository with information about
25
-the gerrit remote.  Assuming that file is present, git-review should
26
-be able to automatically configure your repository the first time it
27
-is run.
28
-
29
-The name of the Gerrit remote is configurable; see the configuration
30
-section below.
31
-
32
-.gitreview file format
33
-======================
34
-
35
-Example .gitreview file (used to upload for git-review itself)::
36
-
37
-    [gerrit]
38
-    host=review.openstack.org
39
-    port=29418
40
-    project=openstack-infra/git-review.git
41
-    defaultbranch=master
42
-
43
-Required values: host, project
44
-
45
-Optional values: port (default: 29418), defaultbranch (default: master),
46
-defaultremote (default: gerrit).
47
-
48
-**Notes**
49
-
50
-* Username is not required because it is requested on first run
51
-
52
-* Unlike git config files, there cannot be any whitespace before the name
53
-  of the variable.
54
-
55
-* Upon first run, git-review will create a remote for working with Gerrit,
56
-  if it does not already exist. By default, the remote name is 'gerrit',
57
-  but this can be overridden with the 'defaultremote' configuration
58
-  option.
59
-
60
-* You can specify different values to be used as defaults in
61
-  ~/.config/git-review/git-review.conf or /etc/git-review/git-review.conf.
62
-
63
-* Git-review will query git credential system for gerrit user/password when
64
-  authentication failed over http(s). Unlike git, git-review does not persist
65
-  gerrit user/password in git credential system for security purposes and git
66
-  credential system configuration stays under user responsibility.
67
-
68
-Hooks
69
-=====
70
-
71
-git-review has a custom hook mechanism to run a script before certain
72
-actions. This is done in the same spirit as the classic hooks in git.
73
-
74
-There are two types of hooks, a global one which is stored in
75
-~/.config/git-review/hooks/ and one local to the repository stored in
76
-.git/hooks/ with the other git hook scripts.
77
-
78
-**The script needs be executable before getting executed**
79
-
80
-The name of the script is $action-review where action can be
81
-:
82
-
83
-* pre - run at first before doing anything.
84
-
85
-* post - run at the end after the review was sent.
86
-
87
-* draft - run when in draft mode.
88
-
89
-if the script returns with an exit status different than zero,
90
-git-review will exit with the a custom shell exit code 71.

+ 5
- 48
doc/source/usage.rst View File

@@ -2,54 +2,11 @@
2 2
  Usage
3 3
 =======
4 4
 
5
-Hack on some code, then::
5
+To interactively rebase the current branch against the most recent
6
+commit in common with the master branch, run::
6 7
 
7
-    git review
8
+    git restack
8 9
 
9
-If you want to submit that code to a branch other than "master", then::
10
+If your branch is based on a different branch, run::
10 11
 
11
-    git review branchname
12
-
13
-If you want to submit to a different remote::
14
-
15
-    git review -r my-remote
16
-
17
-If you want to supply a review topic::
18
-
19
-    git review -t topic/awesome-feature
20
-
21
-If you want to subscribe some reviewers::
22
-
23
-    git review --reviewers a@example.com b@example.com
24
-
25
-If you want to disable autogenerated topic::
26
-
27
-    git review -T
28
-
29
-If you want to submit a branch for review and then remove the local branch::
30
-
31
-    git review -f
32
-
33
-If you want to skip the automatic "git rebase -i" step::
34
-
35
-    git review -R
36
-
37
-If you want to download change 781 from gerrit to review it::
38
-
39
-    git review -d 781
40
-
41
-If you want to download patchset 4 for change 781 from gerrit to review it::
42
-
43
-    git review -d 781,4
44
-
45
-If you want to compare patchset 4 with patchset 10 of change 781 from gerrit::
46
-
47
-    git review -m 781,4-10
48
-
49
-If you want to see a list of open reviews::
50
-
51
-    git review -l
52
-
53
-If you just want to do the commit message and remote setup steps::
54
-
55
-    git review -s
12
+    git restack branchname

+ 78
- 0
git-restack.1 View File

@@ -0,0 +1,78 @@
1
+.\" Uses mdoc(7). See `man 7 mdoc` for details about the syntax used here
2
+.\"
3
+.Dd December 18th, 2015
4
+.Dt GIT\-RESTACK 1
5
+.Sh NAME
6
+.Nm git\-restack
7
+.Nd Edit a series of commits without rebasing
8
+.Sh SYNOPSIS
9
+.Nm
10
+.Op Ar branch
11
+.Nm
12
+.Fl \-version
13
+.Sh DESCRIPTION
14
+.Nm
15
+performs an interactive git rebase of the current branch based on the
16
+most recent commit in a target branch.  When maintaining a large patch
17
+series, it frequently becomes necessary to edit individual patches in
18
+the series.  Simply rebasing the series on the tip of the remote
19
+branch has the secondary effect of changing the branch point of the
20
+series.  In some cases this may be desirable, but in others, such as
21
+when using a code review system like Gerrit, it makes it difficult to
22
+examine diffs between different versions of patchsets.
23
+.Nm
24
+will allow you to rebase the series without changing the commit the
25
+series is based on.
26
+.Pp
27
+If supplied,
28
+.Ar branch
29
+indicates the branch this series is based on.  If it is not present,
30
+.Nm
31
+will check git configuration or look for a
32
+.Pa .gitreview
33
+file and use the default branch specified there.  If neither is found,
34
+it defaults to the master branch.
35
+.Pp
36
+The following options are available:
37
+.Bl -tag -width indent
38
+.It gitreview.branch
39
+This setting determines the default base branch
40
+.Sh FILES
41
+If there is a
42
+.Pa .gitreview
43
+file in the project,
44
+.Nm
45
+will use it to determine the default base branch.
46
+The format is similar to the Windows .ini file format:
47
+.Bd -literal -offset indent
48
+[gerrit]
49
+host=\fIhostname\fP
50
+port=\fITCP port number of gerrit\fP
51
+project=\fIproject name\fP
52
+defaultbranch=\fIbranch to work on\fP
53
+.Ed
54
+.Pp
55
+When the same option is provided through FILES and CONFIGURATION, the
56
+CONFIGURATION value wins.
57
+.Pp
58
+.Sh EXAMPLES
59
+To perform an interactive rebase against the master branch:
60
+.Pp
61
+.Bd -literal -offset indent
62
+$ git\-restack
63
+.Ed
64
+.Pp
65
+To perform an interactive rebase against a branch named
66
+.Pa stable
67
+:
68
+.Pp
69
+.Bd -literal -offset indent
70
+$ git\-restack stable
71
+.Ed
72
+.Sh BUGS
73
+Bug reports can be submitted to
74
+.Lk https://storyboard.openstack.org/#!/project/838
75
+.Sh AUTHORS
76
+.Nm
77
+is maintained by
78
+.An "The OpenStack project"

+ 0
- 458
git-review.1 View File

@@ -1,458 +0,0 @@
1
-.\" Uses mdoc(7). See `man 7 mdoc` for details about the syntax used here
2
-.\"
3
-.Dd June 12th, 2015
4
-.Dt GIT\-REVIEW 1
5
-.Sh NAME
6
-.Nm git\-review
7
-.Nd Submit changes to Gerrit for review
8
-.Sh SYNOPSIS
9
-.Nm
10
-.Op Fl r Ar remote
11
-.Op Fl uv
12
-.Fl d Ar change
13
-.Op Ar branch
14
-.Nm
15
-.Op Fl r Ar remote
16
-.Op Fl uv
17
-.Fl x Ar change
18
-.Op Ar branch
19
-.Nm
20
-.Op Fl r Ar remote
21
-.Op Fl uv
22
-.Fl N Ar change
23
-.Op Ar branch
24
-.Nm
25
-.Op Fl r Ar remote
26
-.Op Fl uv
27
-.Fl X Ar change
28
-.Op Ar branch
29
-.Nm
30
-.Op Fl r Ar remote
31
-.Op Fl uv
32
-.Fl m
33
-.Ar change\-ps\-range
34
-.Op Ar branch
35
-.Nm
36
-.Op Fl r Ar remote
37
-.Op Fl fnuv
38
-.Fl s
39
-.Op Ar branch
40
-.Nm
41
-.Op Fl fnuvDRT
42
-.Op Fl r Ar remote
43
-.Op Fl t Ar topic
44
-.Op Fl \-reviewers Ar reviewer ...
45
-.Op Ar branch
46
-.Nm
47
-.Fl l
48
-.Nm
49
-.Fl \-version
50
-.Sh DESCRIPTION
51
-.Nm
52
-automates and streamlines some of the tasks involved with
53
-submitting local changes to a Gerrit server for review. It is
54
-designed to make it easier to comprehend Gerrit, especially for
55
-users that have recently switched to Git from another version
56
-control system.
57
-.Pp
58
-.Ar change
59
-can be
60
-.Ar changeNumber
61
-as obtained using
62
-.Fl \-list
63
-option, or it can be 
64
-.Ar changeNumber,patchsetNumber
65
-for fetching exact patchset from the change.
66
-In that case local branch name will have a \-patch[patchsetNumber] suffix.
67
-.Pp
68
-The following options are available:
69
-.Bl -tag -width indent
70
-.It Fl d Ar change , Fl \-download= Ns Ar change
71
-Download
72
-.Ar change
73
-from Gerrit
74
-into a local branch. The branch will be named after the patch author and the name of a topic.
75
-If the local branch already exists, it will attempt to update with the latest patchset for this change.
76
-.It Fl x Ar change , Fl \-cherrypick= Ns Ar change
77
-Apply
78
-.Ar change
79
-from Gerrit and commit into the current local branch ("cherry pick").
80
-No additional branch is created.
81
-.Pp
82
-This makes it possible to review a change without creating a local branch for
83
-it. On the other hand, be aware: if you are not careful, this can easily result
84
-in additional patch sets for dependent changes. Also, if the current branch is
85
-different enough, the change may not apply at all or produce merge conflicts
86
-that need to be resolved by hand.
87
-.It Fl N Ar change , Fl \-cherrypickonly= Ns Ar change
88
-Apply
89
-.Ar change
90
-from Gerrit
91
-into the current working directory, add it to the staging area ("git index"), but do not commit it.
92
-.Pp
93
-This makes it possible to review a change without creating a local commit for
94
-it. Useful if you want to merge several commits into one that will be submitted for review.
95
-.Pp
96
-If the current branch is different enough, the change may not apply at all
97
-or produce merge conflicts that need to be resolved by hand.
98
-.It Fl X Ar change , Fl \-cherrypickindicate= Ns Ar change
99
-Apply
100
-.Ar change
101
-from Gerrit and commit into the current local branch ("cherry pick"),
102
-indicating which commit this change was cherry\-picked from.
103
-.Pp
104
-This makes it possible to re\-review a change for a different branch without
105
-creating a local branch for it.
106
-.Pp
107
-If the current branch is different enough, the change may not apply at all
108
-or produce merge conflicts that need to be resolved by hand.
109
-.It Fl m Ar change\-ps\-range , Fl \-compare= Ns Ar change\-ps\-range
110
-Download the specified  patchsets for
111
-.Ar change
112
-from Gerrit, rebase both on master and display differences (git\-diff).
113
-.Pp
114
-.Ar change\-ps\-range
115
-can be specified as
116
-.Ar changeNumber, Ns Ar oldPatchSetNumber Ns Op Ns Ar \-newPatchSetNumber
117
-.Pp
118
-.Ar oldPatchSetNumber
119
-is mandatory, and if
120
-.Ar newPatchSetNumber
121
-is not specified, the latest patchset will be used.
122
-.Pp
123
-This makes it possible to easily compare what has changed from last time you
124
-reviewed the proposed change.
125
-.Pp
126
-If the master branch is different enough, the rebase can produce merge conflicts.
127
-If that happens rebasing will be aborted and diff displayed for not\-rebased branches.
128
-You can also use
129
-.Ar \-\-no\-rebase ( Ar \-R )
130
-to always skip rebasing.
131
-.It Fl f , Fl \-finish
132
-Close down the local branch and switch back to the target branch on
133
-successful submission.
134
-.It Fl n , Fl \-dry\-run
135
-Don\(aqt actually perform any commands that have direct effects. Print them
136
-instead.
137
-.It Fl r Ar remote , Fl \-remote= Ns Ar remote
138
-Git remote to use for Gerrit.
139
-.It Fl s , Fl \-setup
140
-Just run the repo setup commands but don\(aqt submit anything.
141
-.It Fl t Ar topic , Fl \-topic= Ns Ar topic
142
-Sets the target topic for this change on the gerrit server.
143
-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.
144
-.It Fl T , Fl \-no\-topic
145
-Submit review without topic.
146
-.It Fl \-reviewers Ar reviewer ...
147
-Subscribe one or more reviewers to the uploaded patch sets.  Reviewers should be identifiable by Gerrit (usually use their Gerrit username or email address).
148
-.It Fl u , Fl \-update
149
-Skip cached local copies and force updates from network resources.
150
-.It Fl l , Fl \-list
151
-List the available reviews on the gerrit server for this project.
152
-.It Fl y , Fl \-yes
153
-Indicate that you do, in fact, understand if you are submitting more than
154
-one patch.
155
-.It Fl v Fl \-verbose
156
-Turns on more verbose output.
157
-.It Fl D , Fl \-draft
158
-Submit review as a draft. Requires Gerrit 2.3 or newer.
159
-.It Fl R , Fl \-no\-rebase
160
-Do not automatically perform a rebase before submitting the change to
161
-Gerrit.
162
-.Pp
163
-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.
164
-.Pp
165
-Also can be used for
166
-.Fl \-compare
167
-to skip automatic rebase of fetched reviews.
168
-.It Fl \-track
169
-Choose the branch to submit the change against (and, if
170
-rebasing, to rebase against) from the branch being tracked
171
-(if a branch is being tracked), and set the tracking branch
172
-when downloading a change to point to the remote and branch
173
-against which patches should be submitted.
174
-See gitreview.track configuration.
175
-.It Fl \-no\-track
176
-Ignore any branch being tracked by the current branch,
177
-overriding gitreview.track.
178
-This option is implied by providing a specific branch name
179
-on the command line.
180
-.It Fl \-version
181
-Print the version number and exit.
182
-.El
183
-.Sh CONFIGURATION
184
-This utility can be configured by adding entries to Git configuration.
185
-.Pp
186
-The following configuration keys are supported:
187
-.Bl -tag
188
-.It gitreview.username
189
-Default username used to access the repository. If not specified
190
-in the Git configuration, Git remote or
191
-.Pa .gitreview
192
-file, the user will be prompted to specify the username.
193
-.Pp
194
-Example entry in the
195
-.Pa .gitconfig
196
-file:
197
-.Bd -literal -offset indent
198
-[gitreview]
199
-username=\fImygerrituser\fP
200
-.Ed
201
-.It gitreview.scheme
202
-This setting determines the default scheme (ssh/http/https) of gerrit remote
203
-.It gitreview.host
204
-This setting determines the default hostname of gerrit remote
205
-.It gitreview.port
206
-This setting determines the default port of gerrit remote
207
-.It gitreview.project
208
-This setting determines the default name of gerrit git repo
209
-.It gitreview.remote
210
-This setting determines the default name to use for gerrit remote
211
-.It gitreview.branch
212
-This setting determines the default branch
213
-.It gitreview.track
214
-Determines whether to prefer the currently-tracked branch (if any)
215
-and the branch against which the changeset was submitted to Gerrit
216
-(if there is exactly one such branch) to the defaultremote and
217
-defaultbranch for submitting and rebasing against.
218
-If the local topic branch is tracking a remote branch, the remote
219
-and branch that the local topic branch is tracking should be used
220
-for submit and rebase operations, rather than the defaultremote
221
-and defaultbranch.
222
-.Pp
223
-When downloading a patch, creates the local branch to track the
224
-appropriate remote and branch in order to choose that branch by
225
-default when submitting modifications to that changeset.
226
-.Pp
227
-A value of 'true' or 'false' should be specified.
228
-.Bl -tag
229
-.It true
230
-Do prefer the currently-tracked branch (if any) \- equivalent
231
-to setting
232
-.Fl \-track
233
-when submitting changes.
234
-.It false
235
-Ignore tracking branches \- equivalent to setting
236
-.Fl \-no\-track
237
-(the default) or providing an explicit branch name when submitting
238
-changes. This is the default value unless overridden by
239
-.Pa .gitreview
240
-file, and is implied by providing a specific branch name on the
241
-command line.
242
-.El
243
-.It gitreview.rebase
244
-This setting determines whether changes submitted will
245
-be rebased to the newest state of the branch.
246
-.Pp
247
-A value of 'true' or 'false' should be specified.
248
-.Bl -tag
249
-.It false
250
-Do not rebase changes on submit \- equivalent to setting
251
-.Fl R
252
-when submitting changes.
253
-.It true
254
-Do rebase changes on submit. This is the default value unless
255
-overridden by
256
-.Pa .gitreview
257
-file.
258
-.El
259
-.Pp
260
-This setting takes precedence over repository\-specific configuration
261
-in the
262
-.Pa .gitreview
263
-file.
264
-.El
265
-.Bl -tag
266
-.It color.review
267
-Whether to use ANSI escape sequences to add color to the output displayed by
268
-this command. Default value is determined by color.ui.
269
-.Bl -tag
270
-.It auto or true
271
-If you want output to use color when written to the terminal (default with Git
272
-1.8.4 and newer).
273
-.It always
274
-If you want all output to use color
275
-.It never or false
276
-If you wish not to use color for any output. (default with Git older than 1.8.4)
277
-.El
278
-.El
279
-.Pp
280
-.Nm
281
-will query git credential system for gerrit user/password when
282
-authentication failed over http(s). Unlike git,
283
-.Nm
284
-does not persist gerrit user/password in git credential system for security
285
-purposes and git credential system configuration stays under user responsibility.
286
-.Sh FILES
287
-To use
288
-.Nm
289
-with your project, it is recommended that you create
290
-a file at the root of the repository named
291
-.Pa .gitreview
292
-and place information about your gerrit installation in it.  The format is similar to the Windows .ini file format:
293
-.Bd -literal -offset indent
294
-[gerrit]
295
-host=\fIhostname\fP
296
-port=\fITCP port number of gerrit\fP
297
-project=\fIproject name\fP
298
-defaultbranch=\fIbranch to work on\fP
299
-.Ed
300
-.Pp
301
-It is also possible to specify optional default name for
302
-the Git remote using the
303
-.Cm defaultremote
304
-configuration parameter.
305
-.Pp
306
-Setting
307
-.Cm defaultrebase
308
-to zero will make
309
-.Nm
310
-not to rebase changes by default (same as the
311
-.Fl R
312
-command line option)
313
-.Bd -literal -offset indent
314
-[gerrit]
315
-scheme=ssh
316
-host=review.example.com
317
-port=29418
318
-project=department/project.git
319
-defaultbranch=master
320
-defaultremote=review
321
-defaultrebase=0
322
-track=0
323
-.Ed
324
-.Pp
325
-When the same option is provided through FILES and CONFIGURATION, the
326
-CONFIGURATION value wins.
327
-.Pp
328
-.Sh DIAGNOSTICS
329
-.Pp
330
-Normally, exit status is 0 if executed successfully.
331
-Exit status 1 indicates general error, sometimes more
332
-specific error codes are available:
333
-.Bl -tag -width 999
334
-.It 2
335
-Gerrit
336
-.Ar commit\-msg
337
-hook could not be successfully installed.
338
-.It 3
339
-Could not parse malformed argument value or user input.
340
-.It 32
341
-Cannot fetch list of open changesets from Gerrit.
342
-.It 33
343
-Cannot parse list of open changesets received from Gerrit.
344
-.It 34
345
-Cannot query information about changesets.
346
-.It 35
347
-Cannot fetch information about the changeset to be downloaded.
348
-.It 36
349
-Changeset not found.
350
-.It 37
351
-Particular patchset cannot be fetched from the remote git repository.
352
-.It 38
353
-Specified patchset number not found in the changeset.
354
-.It 39
355
-Invalid patchsets for comparison.
356
-.It 64
357
-Cannot checkout downloaded patchset into the new branch.
358
-.It 65
359
-Cannot checkout downloaded patchset into existing branch.
360
-.It 66
361
-Cannot hard reset working directory and git index after download.
362
-.It 67
363
-Cannot switch to some other branch when trying to finish
364
-the current branch.
365
-.It 68
366
-Cannot delete current branch.
367
-.It 69
368
-Requested patchset cannot be fully applied to the current branch.  This exit
369
-status will be returned when there are merge conflicts with the current branch.
370
-Possible reasons include an attempt to apply patchset from the different branch
371
-or code.  This exit status will also be returned if the patchset is already
372
-applied to the current branch.
373
-.It 70
374
-Cannot determine top level Git directory or .git subdirectory path.
375
-.It 101
376
-Unauthorized (401) http request done by git-review.
377
-.It 104
378
-Not Found (404) http request done by git-review.
379
-.El
380
-.Pp
381
-Exit status larger than 31 indicates problem with
382
-communication with Gerrit or remote Git repository,
383
-exit status larger than 63 means there was a problem with
384
-a local repository or a working copy.
385
-.Pp
386
-Exit status larger than or equal to 128 means internal
387
-error in running the "git" command.
388
-.Pp
389
-.Sh EXAMPLES
390
-To fetch a remote change number 3004:
391
-.Pp
392
-.Bd -literal -offset indent
393
-$ git\-review \-d 3004
394
-Downloading refs/changes/04/3004/1 from gerrit into
395
-review/someone/topic_name
396
-Switched to branch 'review/someone/topic_name
397
-$ git branch
398
-  master
399
-* review/author/topic_name
400
-.Ed
401
-.Pp
402
-Gerrit looks up both name of the author and the topic name from Gerrit
403
-to name a local branch. This facilitates easier identification of changes.
404
-.Pp
405
-To fetch a remote patchset number 5 from change number 3004:
406
-.Pp
407
-.Bd -literal -offset indent
408
-$ git\-review \-d 3004,5
409
-Downloading refs/changes/04/3004/5 from gerrit into
410
-review/someone/topic_name\-patch5
411
-Switched to branch 'review/someone/topic_name\-patch5
412
-$ git branch
413
-  master
414
-* review/author/topic_name\-patch5
415
-.Ed
416
-.Pp
417
-To send a change for review and delete local branch afterwards:
418
-.Bd -literal -offset indent
419
-$ git\-review \-f
420
-remote: Resolving deltas:   0% (0/8)
421
-To ssh://username@review.example.com/department/project.git
422
- * [new branch]      HEAD \-> refs/for/master/topic_name
423
-Switched to branch 'master'
424
-Deleted branch 'review/someone/topic_name'
425
-$ git branch
426
-* master
427
-.Ed
428
-.Pp
429
-An example
430
-.Pa .gitreview
431
-configuration file for a project
432
-.Pa department/project
433
-hosted on
434
-.Cm review.example.com
435
-port
436
-.Cm 29418
437
-in the branch
438
-.Cm master
439
-:
440
-.Bd -literal -offset indent
441
-[gerrit]
442
-host=review.example.com
443
-port=29418
444
-project=department/project.git
445
-defaultbranch=master
446
-.Ed
447
-.Sh BUGS
448
-Bug reports can be submitted to
449
-.Lk https://launchpad.net/git\-review
450
-.Sh AUTHORS
451
-.Nm
452
-is maintained by
453
-.An "OpenStack, LLC"
454
-.Pp
455
-This manpage has been enhanced by:
456
-.An "Antoine Musso" Aq hashar@free.fr
457
-.An "Marcin Cieslak" Aq saper@saper.info
458
-.An "Pavel Sedlák" Aq psedlak@redhat.com

git_review/__init__.py → git_restack/__init__.py View File


+ 277
- 0
git_restack/cmd.py View File

@@ -0,0 +1,277 @@
1
+#!/usr/bin/env python
2
+from __future__ import print_function
3
+
4
+COPYRIGHT = """\
5
+Copyright (C) 2011-2012 OpenStack LLC.
6
+
7
+Licensed under the Apache License, Version 2.0 (the "License");
8
+you may not use this file except in compliance with the License.
9
+You may obtain a copy of the License at
10
+
11
+   http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+Unless required by applicable law or agreed to in writing, software
14
+distributed under the License is distributed on an "AS IS" BASIS,
15
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+implied.
17
+
18
+See the License for the specific language governing permissions and
19
+limitations under the License."""
20
+
21
+import argparse
22
+import datetime
23
+import os
24
+import shlex
25
+import subprocess
26
+import sys
27
+
28
+import pkg_resources
29
+
30
+if sys.version < '3':
31
+    import ConfigParser
32
+    import urllib
33
+    import urlparse
34
+    urlencode = urllib.urlencode
35
+    urljoin = urlparse.urljoin
36
+    urlparse = urlparse.urlparse
37
+    do_input = raw_input
38
+else:
39
+    import configparser as ConfigParser
40
+
41
+    import urllib.parse
42
+    import urllib.request
43
+    urlencode = urllib.parse.urlencode
44
+    urljoin = urllib.parse.urljoin
45
+    urlparse = urllib.parse.urlparse
46
+    do_input = input
47
+
48
+VERBOSE = False
49
+UPDATE = False
50
+LOCAL_MODE = 'GITREVIEW_LOCAL_MODE' in os.environ
51
+CONFIGDIR = os.path.expanduser("~/.config/git-review")
52
+GLOBAL_CONFIG = "/etc/git-review/git-review.conf"
53
+USER_CONFIG = os.path.join(CONFIGDIR, "git-review.conf")
54
+DEFAULTS = dict(branch='master')
55
+
56
+
57
+class GitRestackException(Exception):
58
+    pass
59
+
60
+
61
+class CommandFailed(GitRestackException):
62
+
63
+    def __init__(self, *args):
64
+        Exception.__init__(self, *args)
65
+        (self.rc, self.output, self.argv, self.envp) = args
66
+        self.quickmsg = dict([
67
+            ("argv", " ".join(self.argv)),
68
+            ("rc", self.rc),
69
+            ("output", self.output)])
70
+
71
+    def __str__(self):
72
+        return self.__doc__ + """
73
+The following command failed with exit code %(rc)d
74
+    "%(argv)s"
75
+-----------------------
76
+%(output)s
77
+-----------------------""" % self.quickmsg
78
+
79
+
80
+class GitDirectoriesException(CommandFailed):
81
+    "Cannot determine where .git directory is."
82
+    EXIT_CODE = 70
83
+
84
+
85
+class GitMergeBaseException(CommandFailed):
86
+    "Cannot determine merge base."
87
+    EXIT_CODE = 71
88
+
89
+
90
+class GitConfigException(CommandFailed):
91
+    """Git config value retrieval failed."""
92
+    EXIT_CODE = 128
93
+
94
+
95
+def run_command_foreground(*argv, **kwargs):
96
+    if VERBOSE:
97
+        print(datetime.datetime.now(), "Running:", " ".join(argv))
98
+    if len(argv) == 1:
99
+        # for python2 compatibility with shlex
100
+        if sys.version_info < (3,) and isinstance(argv[0], unicode):
101
+            argv = shlex.split(argv[0].encode('utf-8'))
102
+        else:
103
+            argv = shlex.split(str(argv[0]))
104
+    subprocess.call(argv)
105
+
106
+
107
+def run_command_status(*argv, **kwargs):
108
+    if VERBOSE:
109
+        print(datetime.datetime.now(), "Running:", " ".join(argv))
110
+    if len(argv) == 1:
111
+        # for python2 compatibility with shlex
112
+        if sys.version_info < (3,) and isinstance(argv[0], unicode):
113
+            argv = shlex.split(argv[0].encode('utf-8'))
114
+        else:
115
+            argv = shlex.split(str(argv[0]))
116
+    stdin = kwargs.pop('stdin', None)
117
+    newenv = os.environ.copy()
118
+    newenv['LANG'] = 'C'
119
+    newenv['LANGUAGE'] = 'C'
120
+    newenv.update(kwargs)
121
+    p = subprocess.Popen(argv,
122
+                         stdin=subprocess.PIPE if stdin else None,
123
+                         stdout=subprocess.PIPE,
124
+                         stderr=subprocess.STDOUT,
125
+                         env=newenv)
126
+    (out, nothing) = p.communicate(stdin)
127
+    out = out.decode('utf-8', 'replace')
128
+    return (p.returncode, out.strip())
129
+
130
+
131
+def run_command(*argv, **kwargs):
132
+    (rc, output) = run_command_status(*argv, **kwargs)
133
+    return output
134
+
135
+
136
+def run_command_exc(klazz, *argv, **env):
137
+    """Run command *argv, on failure raise klazz
138
+
139
+    klazz should be derived from CommandFailed
140
+    """
141
+    (rc, output) = run_command_status(*argv, **env)
142
+    if rc != 0:
143
+        raise klazz(rc, output, argv, env)
144
+    return output
145
+
146
+
147
+def get_version():
148
+    requirement = pkg_resources.Requirement.parse('git-restack')
149
+    provider = pkg_resources.get_provider(requirement)
150
+    return provider.version
151
+
152
+
153
+def git_directories():
154
+    """Determine (absolute git work directory path, .git subdirectory path)."""
155
+    cmd = ("git", "rev-parse", "--show-toplevel", "--git-dir")
156
+    out = run_command_exc(GitDirectoriesException, *cmd)
157
+    try:
158
+        return out.splitlines()
159
+    except ValueError:
160
+        raise GitDirectoriesException(0, out, cmd, {})
161
+
162
+
163
+def git_config_get_value(section, option, default=None, as_bool=False):
164
+    """Get config value for section/option."""
165
+    cmd = ["git", "config", "--get", "%s.%s" % (section, option)]
166
+    if as_bool:
167
+        cmd.insert(2, "--bool")
168
+    if LOCAL_MODE:
169
+        __, git_dir = git_directories()
170
+        cmd[2:2] = ['-f', os.path.join(git_dir, 'config')]
171
+    try:
172
+        return run_command_exc(GitConfigException, *cmd).strip()
173
+    except GitConfigException as exc:
174
+        if exc.rc == 1:
175
+            return default
176
+        raise
177
+
178
+
179
+class Config(object):
180
+    """Expose as dictionary configuration options."""
181
+
182
+    def __init__(self, config_file=None):
183
+        self.config = DEFAULTS.copy()
184
+        filenames = [] if LOCAL_MODE else [GLOBAL_CONFIG, USER_CONFIG]
185
+        if config_file:
186
+            filenames.append(config_file)
187
+        for filename in filenames:
188
+            if os.path.exists(filename):
189
+                if filename != config_file:
190
+                    msg = ("Using global/system git-review config files (%s) "
191
+                           "is deprecated")
192
+                    print(msg % filename)
193
+                self.config.update(load_config_file(filename))
194
+
195
+    def __getitem__(self, key):
196
+        value = git_config_get_value('gitreview', key)
197
+        if value is None:
198
+            value = self.config[key]
199
+        return value
200
+
201
+
202
+def load_config_file(config_file):
203
+    """Load configuration options from a file."""
204
+    configParser = ConfigParser.ConfigParser()
205
+    configParser.read(config_file)
206
+    options = {
207
+        'scheme': 'scheme',
208
+        'hostname': 'host',
209
+        'port': 'port',
210
+        'project': 'project',
211
+        'branch': 'defaultbranch',
212
+        'remote': 'defaultremote',
213
+        'rebase': 'defaultrebase',
214
+        'track': 'track',
215
+        'usepushurl': 'usepushurl',
216
+    }
217
+    config = {}
218
+    for config_key, option_name in options.items():
219
+        if configParser.has_option('gerrit', option_name):
220
+            config[config_key] = configParser.get('gerrit', option_name)
221
+    return config
222
+
223
+
224
+def main():
225
+    usage = "git restack [BRANCH]"
226
+
227
+    parser = argparse.ArgumentParser(usage=usage, description=COPYRIGHT)
228
+
229
+    parser.add_argument("-v", "--verbose", dest="verbose", action="store_true",
230
+                        help="Output more information about what's going on")
231
+    parser.add_argument("--license", dest="license", action="store_true",
232
+                        help="Print the license and exit")
233
+    parser.add_argument("--version", action="version",
234
+                        version='%s version %s' %
235
+                        (os.path.split(sys.argv[0])[-1], get_version()))
236
+    parser.add_argument("branch", nargs="?")
237
+
238
+    parser.set_defaults(verbose=False)
239
+
240
+    try:
241
+        (top_dir, git_dir) = git_directories()
242
+    except GitDirectoriesException as no_git_dir:
243
+        pass
244
+    else:
245
+        no_git_dir = False
246
+        config = Config(os.path.join(top_dir, ".gitreview"))
247
+    options = parser.parse_args()
248
+    if no_git_dir:
249
+        raise no_git_dir
250
+
251
+    if options.license:
252
+        print(COPYRIGHT)
253
+        sys.exit(0)
254
+
255
+    global VERBOSE
256
+    VERBOSE = options.verbose
257
+
258
+    if options.branch is None:
259
+        branch = config['branch']
260
+    else:
261
+        branch = options.branch
262
+
263
+    if branch is None:
264
+        branch = 'master'
265
+
266
+    status = 0
267
+
268
+    cmd = "git merge-base HEAD origin/%s" % branch
269
+    base = run_command_exc(GitMergeBaseException, cmd)
270
+
271
+    run_command_foreground("git rebase -i %s" % base, stdin=sys.stdin)
272
+
273
+    sys.exit(status)
274
+
275
+
276
+if __name__ == "__main__":
277
+    main()

+ 86
- 0
git_restack/tests/__init__.py View File

@@ -0,0 +1,86 @@
1
+# Copyright (c) 2013 Mirantis Inc.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#    http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
+# implied.
13
+# See the License for the specific language governing permissions and
14
+# limitations under the License.
15
+
16
+import os
17
+
18
+import fixtures
19
+import testtools
20
+
21
+from git_restack.tests import utils
22
+
23
+
24
+class BaseGitRestackTestCase(testtools.TestCase):
25
+    """Base class for the git-restack tests."""
26
+
27
+    def setUp(self):
28
+        """Configure testing environment.
29
+
30
+        Prepare directory for the testing and clone test Git repository.
31
+        Require Gerrit war file in the .gerrit directory to run Gerrit local.
32
+        """
33
+        super(BaseGitRestackTestCase, self).setUp()
34
+        self.useFixture(fixtures.Timeout(2 * 60, True))
35
+
36
+        self.root_dir = self.useFixture(fixtures.TempDir()).path
37
+        self.upstream_dir = os.path.join(self.root_dir, "upstream")
38
+        self.local_dir = os.path.join(self.root_dir, "local")
39
+
40
+        os.makedirs(self._dir('upstream'))
41
+        self._run_git('upstream', 'init')
42
+        self._simple_change('upstream', 'initial text', 'initial commit')
43
+        self._simple_change('upstream', 'second text', 'second commit')
44
+        self._run_git('upstream', 'checkout', '-b', 'branch1')
45
+        self._simple_change('upstream', 'branch1 text', 'branch1 commit')
46
+        self._run_git('upstream', 'checkout', 'master')
47
+        self._run_git('upstream', 'checkout', '-b', 'branch2')
48
+
49
+        gitreview = '[gerrit]\ndefaultbranch=branch2\n'
50
+        self._simple_change('upstream', gitreview, 'branch2 commit',
51
+                            file_=self._dir('upstream', '.gitreview'))
52
+        self._run_git('upstream', 'checkout', 'master')
53
+
54
+    def _dir(self, base, *args):
55
+        """Creates directory name from base name and other parameters."""
56
+        return os.path.join(getattr(self, base + '_dir'), *args)
57
+
58
+    def _run_git(self, dirname, command, *args):
59
+        """Run git command using test git directory."""
60
+        if command == 'clone':
61
+            return utils.run_git(command, args[0], self._dir(dirname))
62
+        return utils.run_git('--git-dir=' + self._dir(dirname, '.git'),
63
+                             '--work-tree=' + self._dir(dirname),
64
+                             command, *args)
65
+
66
+    def _run_git_restack(self, *args, **kwargs):
67
+        """Run git-restack utility from source."""
68
+        git_restack = utils.run_cmd('which', 'git-restack')
69
+        kwargs.setdefault('chdir', self.local_dir)
70
+        return utils.run_cmd(git_restack, *args, **kwargs)
71
+
72
+    def _simple_change(self, dirname, change_text, commit_message,
73
+                       file_=None):
74
+        """Helper method to create small changes and commit them."""
75
+        if file_ is None:
76
+            file_ = self._dir(dirname, 'test_file.txt')
77
+        utils.write_to_file(file_, change_text.encode())
78
+        self._run_git(dirname, 'add', file_)
79
+        self._run_git(dirname, 'commit', '-m', commit_message)
80
+
81
+    def _git_log(self, dirname):
82
+        out = self._run_git(dirname, 'log', '--oneline')
83
+        commits = []
84
+        for line in out.split('\n'):
85
+            commits.append(line.split(' ', 1))
86
+        return commits

+ 92
- 0
git_restack/tests/test_git_restack.py View File

@@ -0,0 +1,92 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+# Copyright (c) 2013 Mirantis Inc.
4
+#
5
+# Licensed under the Apache License, Version 2.0 (the "License");
6
+# you may not use this file except in compliance with the License.
7
+# You may obtain a copy of the License at
8
+#
9
+#    http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+# Unless required by applicable law or agreed to in writing, software
12
+# distributed under the License is distributed on an "AS IS" BASIS,
13
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14
+# implied.
15
+# See the License for the specific language governing permissions and
16
+# limitations under the License.
17
+
18
+from git_restack import tests
19
+
20
+
21
+class GitRestackTestCase(tests.BaseGitRestackTestCase):
22
+    """Class for config tests."""
23
+
24
+    def test_git_restack(self):
25
+        self._run_git('local', 'clone', self._dir('upstream'))
26
+        self._simple_change('local', 'b1 text', 'b1')
27
+        self._simple_change('local', 'b2 text', 'b2')
28
+        self._simple_change('local', 'b3 text', 'b3')
29
+
30
+        commits = self._git_log('local')
31
+        self.assertEqual(commits[0][1], 'b3')
32
+        self.assertEqual(commits[1][1], 'b2')
33
+        self.assertEqual(commits[2][1], 'b1')
34
+        self.assertEqual(commits[3][1], 'second commit')
35
+        self.assertEqual(commits[4][1], 'initial commit')
36
+        out = self._run_git_restack()
37
+        lines = out.split('\n')
38
+        self.assertEqual(lines[0], 'pick %s %s' %
39
+                         (commits[2][0], commits[2][1]))
40
+        self.assertEqual(lines[1], 'pick %s %s' %
41
+                         (commits[1][0], commits[1][1]))
42
+        self.assertEqual(lines[2], 'pick %s %s' %
43
+                         (commits[0][0], commits[0][1]))
44
+        self.assertEqual(lines[3], '')
45
+
46
+    def test_git_restack_gitreview(self):
47
+        self._run_git('local', 'clone', self._dir('upstream'))
48
+        self._run_git('local', 'checkout', 'branch2')
49
+        self._simple_change('local', 'b1 text', 'b1')
50
+        self._simple_change('local', 'b2 text', 'b2')
51
+        self._simple_change('local', 'b3 text', 'b3')
52
+
53
+        commits = self._git_log('local')
54
+        self.assertEqual(commits[0][1], 'b3')
55
+        self.assertEqual(commits[1][1], 'b2')
56
+        self.assertEqual(commits[2][1], 'b1')
57
+        self.assertEqual(commits[3][1], 'branch2 commit')
58
+        self.assertEqual(commits[4][1], 'second commit')
59
+        self.assertEqual(commits[5][1], 'initial commit')
60
+        out = self._run_git_restack()
61
+        lines = out.split('\n')
62
+        self.assertEqual(lines[0], 'pick %s %s' %
63
+                         (commits[2][0], commits[2][1]))
64
+        self.assertEqual(lines[1], 'pick %s %s' %
65
+                         (commits[1][0], commits[1][1]))
66
+        self.assertEqual(lines[2], 'pick %s %s' %
67
+                         (commits[0][0], commits[0][1]))
68
+        self.assertEqual(lines[3], '')
69
+
70
+    def test_git_restack_arg(self):
71
+        self._run_git('local', 'clone', self._dir('upstream'))
72
+        self._run_git('local', 'checkout', 'branch1')
73
+        self._simple_change('local', 'b1 text', 'b1')
74
+        self._simple_change('local', 'b2 text', 'b2')
75
+        self._simple_change('local', 'b3 text', 'b3')
76
+
77
+        commits = self._git_log('local')
78
+        self.assertEqual(commits[0][1], 'b3')
79
+        self.assertEqual(commits[1][1], 'b2')
80
+        self.assertEqual(commits[2][1], 'b1')
81
+        self.assertEqual(commits[3][1], 'branch1 commit')
82
+        self.assertEqual(commits[4][1], 'second commit')
83
+        self.assertEqual(commits[5][1], 'initial commit')
84
+        out = self._run_git_restack('branch1')
85
+        lines = out.split('\n')
86
+        self.assertEqual(lines[0], 'pick %s %s' %
87
+                         (commits[2][0], commits[2][1]))
88
+        self.assertEqual(lines[1], 'pick %s %s' %
89
+                         (commits[1][0], commits[1][1]))
90
+        self.assertEqual(lines[2], 'pick %s %s' %
91
+                         (commits[0][0], commits[0][1]))
92
+        self.assertEqual(lines[3], '')

git_review/tests/utils.py → git_restack/tests/utils.py View File

@@ -27,6 +27,7 @@ def run_cmd(*args, **kwargs):
27 27
             return os.chdir(kwargs['chdir'])
28 28
 
29 29
     try:
30
+        os.environ['EDITOR'] = '/bin/cat'
30 31
         proc = subprocess.Popen(args, stdin=subprocess.PIPE,
31 32
                                 stdout=subprocess.PIPE,
32 33
                                 stderr=subprocess.STDOUT, env=os.environ,

+ 0
- 1577
git_review/cmd.py
File diff suppressed because it is too large
View File


+ 0
- 335
git_review/tests/__init__.py View File

@@ -1,335 +0,0 @@
1
-# Copyright (c) 2013 Mirantis Inc.
2
-#
3
-# Licensed under the Apache License, Version 2.0 (the "License");
4
-# you may not use this file except in compliance with the License.
5
-# You may obtain a copy of the License at
6
-#
7
-#    http://www.apache.org/licenses/LICENSE-2.0
8
-#
9
-# Unless required by applicable law or agreed to in writing, software
10
-# distributed under the License is distributed on an "AS IS" BASIS,
11
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
-# implied.
13
-# See the License for the specific language governing permissions and
14
-# limitations under the License.
15
-
16
-import os
17
-import shutil
18
-import stat
19
-import sys
20
-
21
-if sys.version < '3':
22
-    import urllib
23
-    import urlparse
24
-    urlparse = urlparse.urlparse
25
-else:
26
-    import urllib.parse
27
-    import urllib.request
28
-    urlparse = urllib.parse.urlparse
29
-
30
-import fixtures
31
-import requests
32
-import testtools
33
-from testtools import content
34
-
35
-from git_review.tests import utils
36
-
37
-WAR_URL = 'https://gerrit-releases.storage.googleapis.com/gerrit-2.9.2.war'
38
-# Update GOLDEN_SITE_VER for every change altering golden site, including
39
-# WAR_URL changes. Set new value to something unique (just +1 it for example)
40
-GOLDEN_SITE_VER = '2'
41
-
42
-
43
-class GerritHelpers(object):
44
-
45
-    def _dir(self, base, *args):
46
-        """Creates directory name from base name and other parameters."""
47
-        return os.path.join(getattr(self, base + '_dir'), *args)
48
-
49
-    def init_dirs(self):
50
-        self.primary_dir = os.path.abspath(os.path.curdir)
51
-        self.gerrit_dir = self._dir('primary', '.gerrit')
52
-        self.gsite_dir = self._dir('gerrit', 'golden_site')
53
-        self.gerrit_war = self._dir('gerrit', WAR_URL.split('/')[-1])
54
-
55
-    def ensure_gerrit_war(self):
56
-        # check if gerrit.war file exists in .gerrit directory
57
-        if not os.path.exists(self.gerrit_dir):
58
-            os.mkdir(self.gerrit_dir)
59
-
60
-        if not os.path.exists(self.gerrit_war):
61
-            print("Downloading Gerrit binary from %s..." % WAR_URL)
62
-            resp = requests.get(WAR_URL)
63
-            if resp.status_code != 200:
64
-                raise RuntimeError("Problem requesting Gerrit war")
65
-            utils.write_to_file(self.gerrit_war, resp.content)
66
-            print("Saved to %s" % self.gerrit_war)
67
-
68
-    def init_gerrit(self):
69
-        """Run Gerrit from the war file and configure it."""
70
-        golden_ver_file = self._dir('gsite', 'golden_ver')
71
-        if os.path.exists(self.gsite_dir):
72
-            if not os.path.exists(golden_ver_file):
73
-                golden_ver = '0'
74
-            else:
75
-                with open(golden_ver_file) as f:
76
-                    golden_ver = f.read().strip()
77
-            if GOLDEN_SITE_VER != golden_ver:
78
-                print("Existing golden site has version %s, removing..." %
79
-                      golden_ver)
80
-                shutil.rmtree(self.gsite_dir)
81
-            else:
82
-                print("Golden site of version %s already exists" %
83
-                      GOLDEN_SITE_VER)
84
-                return
85
-
86
-        print("Creating a new golden site of version " + GOLDEN_SITE_VER)
87
-
88
-        # initialize Gerrit
89
-        utils.run_cmd('java', '-jar', self.gerrit_war,
90
-                      'init', '-d', self.gsite_dir,
91
-                      '--batch', '--no-auto-start', '--install-plugin',
92
-                      'download-commands')
93
-
94
-        with open(golden_ver_file, 'w') as f:
95
-            f.write(GOLDEN_SITE_VER)
96
-
97
-        # create SSH public key
98
-        key_file = self._dir('gsite', 'test_ssh_key')
99
-        utils.run_cmd('ssh-keygen', '-t', 'rsa', '-b', '4096',
100
-                                    '-f', key_file, '-N', '')
101
-        with open(key_file + '.pub', 'rb') as pub_key_file:
102
-            pub_key = pub_key_file.read()
103
-
104
-        # create admin user in Gerrit database
105
-        sql_query = """INSERT INTO ACCOUNTS (REGISTERED_ON) VALUES (NOW());
106
-        INSERT INTO ACCOUNT_GROUP_MEMBERS (ACCOUNT_ID, GROUP_ID) \
107
-            VALUES (0, 1);
108
-        INSERT INTO ACCOUNT_EXTERNAL_IDS (ACCOUNT_ID, EXTERNAL_ID, PASSWORD) \
109
-            VALUES (0, 'username:test_user', 'test_pass');
110
-        INSERT INTO ACCOUNT_SSH_KEYS (SSH_PUBLIC_KEY, VALID) \
111
-            VALUES ('%s', 'Y')""" % pub_key.decode()
112
-
113
-        utils.run_cmd('java', '-jar',
114
-                      self._dir('gsite', 'bin', 'gerrit.war'),
115
-                      'gsql', '-d', self.gsite_dir, '-c', sql_query)
116
-
117
-    def _run_gerrit_cli(self, command, *args):
118
-        """SSH to gerrit Gerrit server and run command there."""
119
-        return utils.run_cmd('ssh', '-p', str(self.gerrit_port),
120
-                             'test_user@' + self.gerrit_host, 'gerrit',
121
-                             command, *args)
122
-
123
-    def _run_git_review(self, *args, **kwargs):
124
-        """Run git-review utility from source."""
125
-        git_review = utils.run_cmd('which', 'git-review')
126
-        kwargs.setdefault('chdir', self.test_dir)
127
-        return utils.run_cmd(git_review, *args, **kwargs)
128
-
129
-
130
-class BaseGitReviewTestCase(testtools.TestCase, GerritHelpers):
131
-    """Base class for the git-review tests."""
132
-
133
-    _test_counter = 0
134
-    _remote = 'gerrit'
135
-
136
-    @property
137
-    def project_uri(self):
138
-        return self.project_ssh_uri
139
-
140
-    def setUp(self):
141
-        """Configure testing environment.
142
-
143
-        Prepare directory for the testing and clone test Git repository.
144
-        Require Gerrit war file in the .gerrit directory to run Gerrit local.
145
-        """
146
-        super(BaseGitReviewTestCase, self).setUp()
147
-        self.useFixture(fixtures.Timeout(2 * 60, True))
148
-        BaseGitReviewTestCase._test_counter += 1
149
-
150
-        # ensures git-review command runs in local mode (for functional tests)
151
-        self.useFixture(
152
-            fixtures.EnvironmentVariable('GITREVIEW_LOCAL_MODE', ''))
153
-
154
-        self.init_dirs()
155
-        ssh_addr, ssh_port, http_addr, http_port, self.site_dir = \
156
-            self._pick_gerrit_port_and_dir()
157
-        self.gerrit_host, self.gerrit_port = ssh_addr, ssh_port
158
-
159
-        self.test_dir = self._dir('site', 'tmp', 'test_project')
160
-        self.ssh_dir = self._dir('site', 'tmp', 'ssh')
161
-        self.project_ssh_uri = (
162
-            'ssh://test_user@%s:%s/test/test_project.git' % (
163
-                ssh_addr, ssh_port))
164
-        self.project_http_uri = (
165
-            'http://test_user:test_pass@%s:%s/test/test_project.git' % (
166
-                http_addr, http_port))
167
-
168
-        self._run_gerrit(ssh_addr, ssh_port, http_addr, http_port)
169
-        self._configure_ssh(ssh_addr, ssh_port)
170
-
171
-        # create Gerrit empty project
172
-        self._run_gerrit_cli('create-project', '--empty-commit',
173
-                             '--name', 'test/test_project')
174
-
175
-        # ensure user proxy conf doesn't interfere with tests
176
-        os.environ['no_proxy'] = os.environ['NO_PROXY'] = '*'
177
-
178
-        # isolate tests from user and system git configuration
179
-        self.home_dir = self._dir('site', 'tmp', 'home')
180
-        self.xdg_config_dir = self._dir('home', '.xdgconfig')
181
-        os.environ['HOME'] = self.home_dir
182
-        os.environ['XDG_CONFIG_HOME'] = self.xdg_config_dir
183
-        os.environ['GIT_CONFIG_NOSYSTEM'] = "1"
184
-        os.environ['EMAIL'] = "you@example.com"
185
-        if not os.path.exists(self.home_dir):
186
-            os.mkdir(self.home_dir)
187
-        if not os.path.exists(self.xdg_config_dir):
188
-            os.mkdir(self.xdg_config_dir)
189
-        self.addCleanup(shutil.rmtree, self.home_dir)
190
-
191
-        # prepare repository for the testing
192
-        self._run_git('clone', self.project_uri)
193
-        utils.write_to_file(self._dir('test', 'test_file.txt'),
194
-                            'test file created'.encode())
195
-        self._create_gitreview_file()
196
-
197
-        # push changes to the Gerrit
198
-        self._run_git('add', '--all')
199
-        self._run_git('commit', '-m', 'Test file and .gitreview added.')
200
-        self._run_git('push', 'origin', 'master')
201
-        shutil.rmtree(self.test_dir)
202
-
203
-        # go to the just cloned test Git repository
204
-        self._run_git('clone', self.project_uri)
205
-        self.configure_gerrit_remote()
206
-        self.addCleanup(shutil.rmtree, self.test_dir)
207
-
208
-        # ensure user is configured for all tests
209
-        self._configure_gitreview_username()
210
-
211
-    def set_remote(self, uri):
212
-        self._run_git('remote', 'set-url', self._remote, uri)
213
-
214
-    def reset_remote(self):
215
-        self._run_git('remote', 'rm', self._remote)
216
-
217
-    def attach_on_exception(self, filename):
218
-        @self.addOnException
219
-        def attach_file(exc_info):
220
-            if os.path.exists(filename):
221
-                content.attach_file(self, filename)
222
-            else:
223
-                self.addDetail(os.path.basename(filename),
224
-                               content.text_content('Not found'))
225
-
226
-    def _run_git(self, command, *args):
227
-        """Run git command using test git directory."""
228
-        if command == 'clone':
229
-            return utils.run_git(command, args[0], self._dir('test'))
230
-        return utils.run_git('--git-dir=' + self._dir('test', '.git'),
231
-                             '--work-tree=' + self._dir('test'),
232
-                             command, *args)
233
-
234
-    def _run_gerrit(self, ssh_addr, ssh_port, http_addr, http_port):
235
-        # create a copy of site dir
236
-        shutil.copytree(self.gsite_dir, self.site_dir)
237
-        self.addCleanup(shutil.rmtree, self.site_dir)
238
-        # write config
239
-        with open(self._dir('site', 'etc', 'gerrit.config'), 'w') as _conf:
240
-            new_conf = utils.get_gerrit_conf(
241
-                ssh_addr, ssh_port, http_addr, http_port)
242
-            _conf.write(new_conf)
243
-
244
-        # If test fails, attach Gerrit config and logs to the result
245
-        self.attach_on_exception(self._dir('site', 'etc', 'gerrit.config'))
246
-        for name in ['error_log', 'sshd_log', 'httpd_log']:
247
-            self.attach_on_exception(self._dir('site', 'logs', name))
248
-
249
-        # start Gerrit
250
-        gerrit_sh = self._dir('site', 'bin', 'gerrit.sh')
251
-        utils.run_cmd(gerrit_sh, 'start')
252
-        self.addCleanup(utils.run_cmd, gerrit_sh, 'stop')
253
-
254
-    def _simple_change(self, change_text, commit_message,
255
-                       file_=None):
256
-        """Helper method to create small changes and commit them."""
257
-        if file_ is None:
258
-            file_ = self._dir('test', 'test_file.txt')
259
-        utils.write_to_file(file_, change_text.encode())
260
-        self._run_git('add', file_)
261
-        self._run_git('commit', '-m', commit_message)
262
-
263
-    def _simple_amend(self, change_text, file_=None):
264
-        """Helper method to amend existing commit with change."""
265
-        if file_ is None:
266
-            file_ = self._dir('test', 'test_file_new.txt')
267
-        utils.write_to_file(file_, change_text.encode())
268
-        self._run_git('add', file_)
269
-        # cannot use --no-edit because it does not exist in older git
270
-        message = self._run_git('log', '-1', '--format=%s\n\n%b')
271
-        self._run_git('commit', '--amend', '-m', message)
272
-
273
-    def _configure_ssh(self, ssh_addr, ssh_port):
274
-        """Setup ssh and scp to run with special options."""
275
-
276
-        os.mkdir(self.ssh_dir)
277
-
278
-        ssh_key = utils.run_cmd('ssh-keyscan', '-p', str(ssh_port), ssh_addr)
279
-        utils.write_to_file(self._dir('ssh', 'known_hosts'), ssh_key.encode())
280
-        self.addCleanup(os.remove, self._dir('ssh', 'known_hosts'))
281
-
282
-        # Attach known_hosts to test results if anything fails
283
-        self.attach_on_exception(self._dir('ssh', 'known_hosts'))
284
-
285
-        for cmd in ('ssh', 'scp'):
286
-            cmd_file = self._dir('ssh', cmd)
287
-            s = '#!/bin/sh\n' \
288
-                '/usr/bin/%s -i %s -o UserKnownHostsFile=%s ' \
289
-                '-o IdentitiesOnly=yes ' \
290
-                '-o PasswordAuthentication=no $@' % \
291
-                (cmd,
292
-                 self._dir('gsite', 'test_ssh_key'),
293
-                 self._dir('ssh', 'known_hosts'))
294
-            utils.write_to_file(cmd_file, s.encode())
295
-            os.chmod(cmd_file, os.stat(cmd_file).st_mode | stat.S_IEXEC)
296
-
297
-        os.environ['PATH'] = self.ssh_dir + os.pathsep + os.environ['PATH']
298
-        os.environ['GIT_SSH'] = self._dir('ssh', 'ssh')
299
-
300
-    def configure_gerrit_remote(self):
301
-        self._run_git('remote', 'add', self._remote, self.project_uri)
302
-
303
-    def _configure_gitreview_username(self):
304
-        self._run_git('config', 'gitreview.username', 'test_user')
305
-
306
-    def _pick_gerrit_port_and_dir(self):
307
-        pid = os.getpid()
308
-        host = '127.%s.%s.%s' % (self._test_counter, pid >> 8, pid & 255)
309
-        return host, 29418, host, 8080, self._dir('gerrit', 'site-' + host)
310
-
311
-    def _create_gitreview_file(self, **kwargs):
312
-        cfg = ('[gerrit]\n'
313
-               'scheme=%s\n'
314
-               'host=%s\n'
315
-               'port=%s\n'
316
-               'project=test/test_project.git\n'
317
-               '%s')
318
-        parsed = urlparse(self.project_uri)
319
-        host_port = parsed.netloc.rpartition('@')[-1]
320
-        host, __, port = host_port.partition(':')
321
-        extra = '\n'.join('%s=%s' % kv for kv in kwargs.items())
322
-        cfg %= parsed.scheme, host, port, extra
323
-        utils.write_to_file(self._dir('test', '.gitreview'), cfg.encode())
324
-
325
-
326
-class HttpMixin(object):
327
-    """HTTP remote_url mixin."""
328
-
329
-    @property
330
-    def project_uri(self):
331
-        return self.project_http_uri
332
-
333
-    def _configure_gitreview_username(self):
334
-        # trick to set http password
335
-        self._run_git('config', 'gitreview.username', 'test_user:test_pass')

+ 0
- 26
git_review/tests/prepare.py View File

@@ -1,26 +0,0 @@
1
-# Copyright (c) 2013 Mirantis Inc.
2
-#
3
-# Licensed under the Apache License, Version 2.0 (the "License");
4
-# you may not use this file except in compliance with the License.
5
-# You may obtain a copy of the License at
6
-#
7
-#    http://www.apache.org/licenses/LICENSE-2.0
8
-#
9
-# Unless required by applicable law or agreed to in writing, software
10
-# distributed under the License is distributed on an "AS IS" BASIS,
11
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
-# implied.
13
-# See the License for the specific language governing permissions and
14
-# limitations under the License.
15
-
16
-from git_review import tests
17
-
18
-
19
-def main():
20
-    helpers = tests.GerritHelpers()
21
-    helpers.init_dirs()
22
-    helpers.ensure_gerrit_war()
23
-    helpers.init_gerrit()
24
-
25
-if __name__ == "__main__":
26
-    main()

+ 0
- 554
git_review/tests/test_git_review.py View File

@@ -1,554 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-# Copyright (c) 2013 Mirantis Inc.
4
-#
5
-# Licensed under the Apache License, Version 2.0 (the "License");
6
-# you may not use this file except in compliance with the License.
7
-# You may obtain a copy of the License at
8
-#
9
-#    http://www.apache.org/licenses/LICENSE-2.0
10
-#
11
-# Unless required by applicable law or agreed to in writing, software
12
-# distributed under the License is distributed on an "AS IS" BASIS,
13
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14
-# implied.
15
-# See the License for the specific language governing permissions and
16
-# limitations under the License.
17
-
18
-import json
19
-import os
20
-import shutil
21
-
22
-from git_review import tests
23
-from git_review.tests import utils
24
-
25
-
26
-class ConfigTestCase(tests.BaseGitReviewTestCase):
27
-    """Class for config tests."""
28
-
29
-    def test_get_config_from_cli(self):
30
-        self.reset_remote()
31
-        self._run_git('remote', 'rm', 'origin')
32
-        self._create_gitreview_file(defaultremote='remote-file')
33
-        self._run_git('config', 'gitreview.remote', 'remote-gitconfig')
34
-        self._run_git_review('-s', '-r', 'remote-cli')
35
-
36
-        remote = self._run_git('remote').strip()
37
-        self.assertEqual('remote-cli', remote)
38
-
39
-    def test_get_config_from_gitconfig(self):
40
-        self.reset_remote()
41
-        self._run_git('remote', 'rm', 'origin')
42
-        self._create_gitreview_file(defaultremote='remote-file')
43
-        self._run_git('config', 'gitreview.remote', 'remote-gitconfig')
44
-        self._run_git_review('-s')
45
-
46
-        remote = self._run_git('remote').strip()
47
-        self.assertEqual('remote-gitconfig', remote)
48
-
49
-    def test_get_config_from_file(self):
50
-        self.reset_remote()
51
-        self._run_git('remote', 'rm', 'origin')
52
-        self._create_gitreview_file(defaultremote='remote-file')
53
-        self._run_git_review('-s')
54
-
55
-        remote = self._run_git('remote').strip()
56
-        self.assertEqual('remote-file', remote)
57
-
58
-
59
-class GitReviewTestCase(tests.BaseGitReviewTestCase):
60
-    """Class for the git-review tests."""
61
-
62
-    def test_cloned_repo(self):
63
-        """Test git-review on the just cloned repository."""
64
-        self._simple_change('test file modified', 'test commit message')
65
-        self.assertNotIn('Change-Id:', self._run_git('log', '-1'))
66
-        self.assertIn('remote: New Changes:', self._run_git_review())
67
-        self.assertIn('Change-Id:', self._run_git('log', '-1'))
68
-
69
-    def test_git_review_s(self):
70
-        """Test git-review -s."""
71
-        self.reset_remote()
72
-        self._run_git_review('-s')
73
-        self._simple_change('test file modified', 'test commit message')
74
-        self.assertIn('Change-Id:', self._run_git('log', '-1'))
75
-
76
-    def test_git_review_s_in_detached_head(self):
77
-        """Test git-review -s in detached HEAD state."""
78
-        self.reset_remote()
79
-        master_sha1 = self._run_git('rev-parse', 'master')
80
-        self._run_git('checkout', master_sha1)
81
-        self._run_git_review('-s')
82
-        self._simple_change('test file modified', 'test commit message')
83
-        self.assertIn('Change-Id:', self._run_git('log', '-1'))
84
-
85
-    def test_git_review_s_with_outdated_repo(self):
86
-        """Test git-review -s with a outdated repo."""
87
-        self._simple_change('test file to outdate', 'test commit message 1')
88
-        self._run_git('push', 'origin', 'master')
89
-        self._run_git('reset', '--hard', 'HEAD^')
90
-
91
-        # Review setup with an outdated repo
92
-        self.reset_remote()
93
-        self._run_git_review('-s')
94
-        self._simple_change('test file modified', 'test commit message 2')
95
-        self.assertIn('Change-Id:', self._run_git('log', '-1'))
96
-
97
-    def test_git_review_s_from_subdirectory(self):
98
-        """Test git-review -s from subdirectory."""
99
-        self.reset_remote()
100
-        utils.run_cmd('mkdir', 'subdirectory', chdir=self.test_dir)
101
-        self._run_git_review(
102
-            '-s', chdir=os.path.join(self.test_dir, 'subdirectory'))
103
-
104
-    def test_git_review_d(self):
105
-        """Test git-review -d."""
106
-        self._run_git_review('-s')
107
-
108
-        # create new review to be downloaded
109
-        self._simple_change('test file modified', 'test commit message')
110
-        self._run_git_review()
111
-        change_id = self._run_git('log', '-1').split()[-1]
112
-
113
-        shutil.rmtree(self.test_dir)
114
-
115
-        # download clean Git repository and fresh change from Gerrit to it
116
-        self._run_git('clone', self.project_uri)
117
-        self.configure_gerrit_remote()
118
-        self._run_git_review('-d', change_id)
119
-        self.assertIn('test commit message', self._run_git('log', '-1'))
120
-
121
-        # second download should also work correct
122
-        self._run_git_review('-d', change_id)
123
-        self.assertIn('test commit message', self._run_git('show', 'HEAD'))
124
-        self.assertNotIn('test commit message',
125
-                         self._run_git('show', 'HEAD^1'))
126
-
127
-        # and branch is tracking
128
-        head = self._run_git('symbolic-ref', '-q', 'HEAD')
129
-        self.assertIn(
130
-            'refs/remotes/%s/master' % self._remote,
131
-            self._run_git("for-each-ref", "--format='%(upstream)'", head))
132
-
133
-    def test_multiple_changes(self):
134
-        """Test git-review asks about multiple changes.
135
-
136
-        Should register user's wish to send two change requests by interactive
137
-        'yes' message and by the -y option.
138
-        """
139
-        self._run_git_review('-s')
140
-
141
-        # 'yes' message
142
-        self._simple_change('test file modified 1st time',
143
-                            'test commit message 1')
144
-        self._simple_change('test file modified 2nd time',
145
-                            'test commit message 2')
146
-
147
-        review_res = self._run_git_review(confirm=True)
148
-        self.assertIn("Type 'yes' to confirm", review_res)
149
-        self.assertIn("Processing changes: new: 2", review_res)
150
-
151
-        # abandon changes sent to the Gerrit
152
-        head = self._run_git('rev-parse', 'HEAD')
153
-        head_1 = self._run_git('rev-parse', 'HEAD^1')
154
-        self._run_gerrit_cli('review', '--abandon', head)
155
-        self._run_gerrit_cli('review', '--abandon', head_1)
156
-
157
-        # -y option
158
-        self._simple_change('test file modified 3rd time',
159
-                            'test commit message 3')
160
-        self._simple_change('test file modified 4th time',
161
-                            'test commit message 4')
162
-        review_res = self._run_git_review('-y')
163
-        self.assertIn("Processing changes: new: 2", review_res)
164
-
165
-    def test_git_review_re(self):
166
-        """Test git-review adding reviewers to changes."""
167
-        self._run_git_review('-s')
168
-
169
-        # Create users to add as reviewers
170
-        self._run_gerrit_cli('create-account', '--email',
171
-                             'reviewer1@example.com', 'reviewer1')
172
-        self._run_gerrit_cli('create-account', '--email',
173
-                             'reviewer2@example.com', 'reviewer2')
174
-
175
-        self._simple_change('test file', 'test commit message')
176
-
177
-        review_res = self._run_git_review('--reviewers', 'reviewer1',
178
-                                          'reviewer2')
179
-        self.assertIn("Processing changes: new: 1", review_res)
180
-
181
-        # verify both reviewers are on patch set
182
-        head = self._run_git('rev-parse', 'HEAD')
183
-        change = self._run_gerrit_cli('query', '--format=JSON',
184
-                                      '--all-reviewers', head)
185
-        # The first result should be the one we want
186
-        change = json.loads(change.split('\n')[0])
187
-
188
-        self.assertEqual(2, len(change['allReviewers']))
189
-
190
-        reviewers = set()
191
-        for reviewer in change['allReviewers']:
192
-            reviewers.add(reviewer['username'])
193
-
194
-        self.assertEqual(set(['reviewer1', 'reviewer2']), reviewers)
195
-
196
-    def test_rebase_no_remote_branch_msg(self):
197
-        """Test message displayed where no remote branch exists."""
198
-        self._run_git_review('-s')
199
-        self._run_git('checkout', '-b', 'new_branch')
200
-        self._simple_change('simple message',
201
-                            'need to avoid noop message')
202
-        exc = self.assertRaises(Exception, self._run_git_review, 'new_branch')
203
-        self.assertIn("The branch 'new_branch' does not exist on the given "
204
-                      "remote '%s'" % self._remote, exc.args[0])
205
-
206
-    def test_need_rebase_no_upload(self):
207
-        """Test change needing a rebase does not upload."""
208
-        self._run_git_review('-s')
209
-        head_1 = self._run_git('rev-parse', 'HEAD^1')
210
-
211
-        self._run_git('checkout', '-b', 'test_branch', head_1)
212
-
213
-        self._simple_change('some other message',
214
-                            'create conflict with master')
215
-
216
-        exc = self.assertRaises(Exception, self._run_git_review)
217
-        self.assertIn(
218
-            "Errors running git rebase -p -i remotes/%s/master" % self._remote,
219
-            exc.args[0])
220
-        self.assertIn("It is likely that your change has a merge conflict.",
221
-                      exc.args[0])
222
-
223
-    def test_upload_without_rebase(self):
224
-        """Test change not needing a rebase can upload without rebasing."""
225
-        self._run_git_review('-s')
226
-        head_1 = self._run_git('rev-parse', 'HEAD^1')
227
-
228
-        self._run_git('checkout', '-b', 'test_branch', head_1)
229
-
230
-        self._simple_change('some new message',
231
-                            'just another file (no conflict)',
232
-                            self._dir('test', 'new_test_file.txt'))
233
-
234
-        review_res = self._run_git_review('-v')
235
-        self.assertIn(
236
-            "Running: git rebase -p -i remotes/%s/master" % self._remote,
237
-            review_res)
238
-        self.assertEqual(self._run_git('rev-parse', 'HEAD^1'), head_1)
239
-
240
-    def test_uploads_with_nondefault_rebase(self):
241
-        """Test changes rebase against correct branches."""
242
-        # prepare maintenance branch that is behind master
243
-        self._create_gitreview_file(track='true',
244
-                                    defaultremote='origin')
245
-        self._run_git('add', '.gitreview')
246
-        self._run_git('commit', '-m', 'track=true.')
247
-        self._simple_change('diverge master from maint',
248
-                            'no conflict',
249
-                            self._dir('test', 'test_file_to_diverge.txt'))
250
-        self._run_git('push', 'origin', 'master')
251
-        self._run_git('push', 'origin', 'master', 'master:other')
252
-        self._run_git_review('-s')
253
-        head_1 = self._run_git('rev-parse', 'HEAD^1')
254
-        self._run_gerrit_cli('create-branch',
255
-                             'test/test_project',
256
-                             'maint', head_1)
257
-        self._run_git('fetch')
258
-
259
-        br_out = self._run_git('checkout',
260
-                               '-b', 'test_branch', 'origin/maint')
261
-        expected_track = 'Branch test_branch set up to track remote' + \
262
-                         ' branch maint from origin.'
263
-        self.assertIn(expected_track, br_out)
264
-        branches = self._run_git('branch', '-a')
265
-        expected_branch = '* test_branch'
266
-        observed = branches.split('\n')
267
-        self.assertIn(expected_branch, observed)
268
-
269
-        self._simple_change('some new message',
270
-                            'just another file (no conflict)',
271
-                            self._dir('test', 'new_tracked_test_file.txt'))
272
-        change_id = self._run_git('log', '-1').split()[-1]
273
-
274
-        review_res = self._run_git_review('-v')
275
-        # no rebase needed; if it breaks it would try to rebase to master
276
-        self.assertNotIn("Running: git rebase -p -i remotes/origin/master",
277
-                         review_res)
278
-        # Don't need to query gerrit for the branch as the second half
279
-        # of this test will work only if the branch was correctly
280
-        # stored in gerrit
281
-
282
-        # delete branch locally
283
-        self._run_git('checkout', 'master')
284
-        self._run_git('branch', '-D', 'test_branch')
285
-
286
-        # download, amend, submit
287
-        self._run_git_review('-d', change_id)
288
-        self._simple_amend('just another file (no conflict)',
289
-                           self._dir('test', 'new_tracked_test_file_2.txt'))
290
-        new_change_id = self._run_git('log', '-1').split()[-1]
291
-        self.assertEqual(change_id, new_change_id)
292
-        review_res = self._run_git_review('-v')
293
-        # caused the right thing to happen
294
-        self.assertIn("Running: git rebase -p -i remotes/origin/maint",
295
-                      review_res)
296
-
297
-        # track different branch than expected in changeset
298
-        branch = self._run_git('rev-parse', '--abbrev-ref', 'HEAD')
299
-        self._run_git('branch',
300
-                      '--set-upstream',
301
-                      branch,
302
-                      'remotes/origin/other')
303
-        self.assertRaises(
304
-            Exception,  # cmd.BranchTrackingMismatch inside
305
-            self._run_git_review, '-d', change_id)
306
-
307
-    def test_no_rebase_check(self):
308
-        """Test -R causes a change to be uploaded without rebase checking."""
309
-        self._run_git_review('-s')
310
-        head_1 = self._run_git('rev-parse', 'HEAD^1')
311
-
312
-        self._run_git('checkout', '-b', 'test_branch', head_1)
313
-        self._simple_change('some new message', 'just another file',
314
-                            self._dir('test', 'new_test_file.txt'))
315
-
316
-        review_res = self._run_git_review('-v', '-R')
317
-        self.assertNotIn('rebase', review_res)
318
-        self.assertEqual(self._run_git('rev-parse', 'HEAD^1'), head_1)
319
-
320
-    def test_rebase_anyway(self):
321
-        """Test -F causes a change to be rebased regardless."""
322
-        self._run_git_review('-s')
323
-        head = self._run_git('rev-parse', 'HEAD')
324
-        head_1 = self._run_git('rev-parse', 'HEAD^1')
325
-
326
-        self._run_git('checkout', '-b', 'test_branch', head_1)
327
-        self._simple_change('some new message', 'just another file',
328
-                            self._dir('test', 'new_test_file.txt'))
329
-        review_res = self._run_git_review('-v', '-F')
330
-        self.assertIn('rebase', review_res)
331
-        self.assertEqual(self._run_git('rev-parse', 'HEAD^1'), head)
332
-
333
-    def _assert_branch_would_be(self, branch, extra_args=None):
334
-        extra_args = extra_args or []
335
-        output = self._run_git_review('-n', *extra_args)
336
-        # last non-empty line should be:
337
-        #       git push gerrit HEAD:refs/publish/master
338
-        last_line = output.strip().split('\n')[-1]
339
-        branch_was = last_line.rsplit(' ', 1)[-1].split('/', 2)[-1]
340
-        self.assertEqual(branch, branch_was)
341
-
342
-    def test_detached_head(self):
343
-        """Test on a detached state: we shouldn't have '(detached' as topic."""
344
-        self._run_git_review('-s')
345
-        curr_branch = self._run_git('rev-parse', '--abbrev-ref', 'HEAD')
346
-        # Note: git checkout --detach has been introduced in git 1.7.5 (2011)
347
-        self._run_git('checkout', curr_branch + '^0')
348
-        self._simple_change('some new message', 'just another file',
349
-                            self._dir('test', 'new_test_file.txt'))
350
-        # switch to French, 'git branch' should return '(détaché du HEAD)'
351
-        lang_env = os.getenv('LANG', 'C')
352
-        os.environ.update(LANG='fr_FR.UTF-8')
353
-        try:
354
-            self._assert_branch_would_be(curr_branch)
355
-        finally:
356
-            os.environ.update(LANG=lang_env)
357
-
358
-    def test_git_review_t(self):
359
-        self._run_git_review('-s')
360
-        self._simple_change('test file modified', 'commit message for bug 654')
361
-        self._assert_branch_would_be('master/zat', extra_args=['-t', 'zat'])
362
-
363
-    def test_bug_topic(self):
364
-        self._run_git_review('-s')
365
-        self._simple_change('a change', 'new change for bug 123')
366
-        self._assert_branch_would_be('master/bug/123')
367
-
368
-    def test_bug_topic_newline(self):
369
-        self._run_git_review('-s')
370
-        self._simple_change('a change', 'new change not for bug\n\n123')
371
-        self._assert_branch_would_be('master')
372
-
373
-    def test_bp_topic(self):
374
-        self._run_git_review('-s')
375
-        self._simple_change('a change', 'new change for blueprint asdf')
376
-        self._assert_branch_would_be('master/bp/asdf')
377
-
378
-    def test_bp_topic_newline(self):
379
-        self._run_git_review('-s')
380
-        self._simple_change('a change', 'new change not for blueprint\n\nasdf')
381
-        self._assert_branch_would_be('master')
382
-
383
-    def test_author_name_topic_bp(self):
384
-        old_author = None
385
-        if 'GIT_AUTHOR_NAME' in os.environ:
386
-            old_author = os.environ['GIT_AUTHOR_NAME']
387
-        try:
388
-            os.environ['GIT_AUTHOR_NAME'] = 'BPNAME'
389
-            self._run_git_review('-s')
390
-            self._simple_change('a change',
391
-                                'new change 1 with name but no topic')
392
-            self._assert_branch_would_be('master')
393
-        finally:
394
-            if old_author:
395
-                os.environ['GIT_AUTHOR_NAME'] = old_author
396
-            else:
397
-                del os.environ['GIT_AUTHOR_NAME']
398
-
399
-    def test_author_email_topic_bp(self):
400
-        old_author = None
401
-        if 'GIT_AUTHOR_EMAIL' in os.environ:
402
-            old_author = os.environ['GIT_AUTHOR_EMAIL']
403
-        try:
404
-            os.environ['GIT_AUTHOR_EMAIL'] = 'bpemail@example.com'
405
-            self._run_git_review('-s')
406
-            self._simple_change('a change',
407
-                                'new change 1 with email but no topic')
408
-            self._assert_branch_would_be('master')
409
-        finally:
410
-            if old_author:
411
-                os.environ['GIT_AUTHOR_EMAIL'] = old_author
412
-            else:
413
-                del os.environ['GIT_AUTHOR_EMAIL']
414
-
415
-    def test_author_name_topic_bug(self):
416
-        old_author = None
417
-        if 'GIT_AUTHOR_NAME' in os.environ:
418
-            old_author = os.environ['GIT_AUTHOR_NAME']
419
-        try:
420
-            os.environ['GIT_AUTHOR_NAME'] = 'Bug: #1234'
421
-            self._run_git_review('-s')
422
-            self._simple_change('a change',
423
-                                'new change 2 with name but no topic')
424
-            self._assert_branch_would_be('master')
425
-        finally:
426
-            if old_author:
427
-                os.environ['GIT_AUTHOR_NAME'] = old_author
428
-            else:
429
-                del os.environ['GIT_AUTHOR_NAME']
430
-
431
-    def test_author_email_topic_bug(self):
432
-        old_author = None
433
-        if 'GIT_AUTHOR_EMAIL' in os.environ:
434
-            old_author = os.environ['GIT_AUTHOR_EMAIL']
435
-        try:
436
-            os.environ['GIT_AUTHOR_EMAIL'] = 'bug5678@example.com'
437
-            self._run_git_review('-s')
438
-            self._simple_change('a change',
439
-                                'new change 2 with email but no topic')
440
-            self._assert_branch_would_be('master')
441
-        finally:
442
-            if old_author:
443
-                os.environ['GIT_AUTHOR_EMAIL'] = old_author
444
-            else:
445
-                del os.environ['GIT_AUTHOR_EMAIL']
446
-
447
-    def test_git_review_T(self):
448
-        self._run_git_review('-s')
449
-        self._simple_change('test file modified', 'commit message for bug 456')
450
-        self._assert_branch_would_be('master/bug/456')
451
-        self._assert_branch_would_be('master', extra_args=['-T'])
452
-
453
-    def test_git_review_T_t(self):
454
-        self.assertRaises(Exception, self._run_git_review, '-T', '-t', 'taz')
455
-
456
-    def test_git_review_l(self):
457
-        self._run_git_review('-s')
458
-
459
-        # Populate "project" repo
460
-        self._simple_change('project: test1', 'project: change1, merged')
461
-        self._simple_change('project: test2', 'project: change2, open')
462
-        self._simple_change('project: test3', 'project: change3, abandoned')
463
-        self._run_git_review('-y')
464
-        head = self._run_git('rev-parse', 'HEAD')
465
-        head_2 = self._run_git('rev-parse', 'HEAD^^')
466
-        self._run_gerrit_cli('review', head_2, '--code-review=+2', '--submit')
467
-        self._run_gerrit_cli('review', head, '--abandon')
468
-
469
-        # Populate "project2" repo
470
-        self._run_gerrit_cli('create-project', '--empty-commit', '--name',
471
-                             'test/test_project2')
472
-        project2_uri = self.project_uri.replace('test/test_project',
473
-                                                'test/test_project2')
474
-        self._run_git('fetch', project2_uri, 'HEAD')
475
-        self._run_git('checkout', 'FETCH_HEAD')
476
-        self._simple_change('project2: test1', 'project2: change1, open')
477
-        self._run_git('push', project2_uri, 'HEAD:refs/for/master')
478
-
479
-        # Only project1 open changes
480
-        result = self._run_git_review('-l')
481
-        self.assertNotIn('project: change1, merged', result)
482
-        self.assertIn('project: change2, open', result)
483
-        self.assertNotIn('project: change3, abandoned', result)
484
-        self.assertNotIn('project2:', result)
485
-
486
-    def _test_git_review_F(self, rebase):
487
-        self._run_git_review('-s')
488
-
489
-        # Populate repo
490
-        self._simple_change('create file', 'test commit message')
491
-        change1 = self._run_git('rev-parse', 'HEAD')
492
-        self._run_git_review()
493
-        self._run_gerrit_cli('review', change1, '--code-review=+2', '--submit')
494
-        self._run_git('reset', '--hard', 'HEAD^')
495
-
496
-        # Review with force_rebase
497
-        self._run_git('config', 'gitreview.rebase', rebase)
498
-        self._simple_change('create file2', 'test commit message 2',
499
-                            self._dir('test', 'test_file2.txt'))
500
-        self._run_git_review('-F')
501
-        head_1 = self._run_git('rev-parse', 'HEAD^')
502
-        self.assertEqual(change1, head_1)
503
-
504
-    def test_git_review_F(self):
505
-        self._test_git_review_F('1')
506
-
507
-    def test_git_review_F_norebase(self):
508
-        self._test_git_review_F('0')
509
-
510
-    def test_git_review_F_R(self):
511
-        self.assertRaises(Exception, self._run_git_review, '-F', '-R')
512
-
513
-    def test_config_instead_of_honored(self):
514
-        self.set_remote('test_project_url')
515
-
516
-        self.assertRaises(Exception, self._run_git_review, '-l')
517
-
518
-        self._run_git('config', '--add', 'url.%s.insteadof' % self.project_uri,
519
-                      'test_project_url')
520
-        self._run_git_review('-l')
521
-
522
-    def test_config_pushinsteadof_honored(self):
523
-        self.set_remote('test_project_url')
524
-
525
-        self.assertRaises(Exception, self._run_git_review, '-l')
526
-
527
-        self._run_git('config', '--add',
528
-                      'url.%s.pushinsteadof' % self.project_uri,
529
-                      'test_project_url')
530
-        self._run_git_review('-l')
531
-
532
-
533
-class PushUrlTestCase(GitReviewTestCase):
534
-    """Class for the git-review tests using origin push-url."""
535
-
536
-    _remote = 'origin'
537
-
538
-    def set_remote(self, uri):
539
-        self._run_git('remote', 'set-url', '--push', self._remote, uri)
540
-
541
-    def reset_remote(self):
542
-        self._run_git('config', '--unset', 'remote.%s.pushurl' % self._remote)
543
-
544
-    def configure_gerrit_remote(self):
545
-        self.set_remote(self.project_uri)
546
-        self._run_git('config', 'gitreview.usepushurl', '1')
547
-
548
-    def test_config_pushinsteadof_honored(self):
549
-        self.skipTest("pushinsteadof doesn't rewrite pushurls")
550
-
551
-
552
-class HttpGitReviewTestCase(tests.HttpMixin, GitReviewTestCase):
553
-    """Class for the git-review tests over HTTP(S)."""
554
-    pass

+ 0
- 296
git_review/tests/test_unit.py View File

@@ -1,296 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
4
-#
5
-# Licensed under the Apache License, Version 2.0 (the "License");
6
-# you may not use this file except in compliance with the License.
7
-# You may obtain a copy of the License at
8
-#
9
-#    http://www.apache.org/licenses/LICENSE-2.0
10
-#
11
-# Unless required by applicable law or agreed to in writing, software
12
-# distributed under the License is distributed on an "AS IS" BASIS,
13
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14
-# implied.
15
-# See the License for the specific language governing permissions and
16
-# limitations under the License.
17
-
18
-import functools
19
-import os
20
-import textwrap
21
-
22
-import fixtures
23
-import mock
24
-import testtools
25
-
26
-from git_review import cmd
27
-from git_review.tests import utils
28
-
29
-# Use of io.StringIO in python =< 2.7 requires all strings handled to be
30
-# unicode. See if StringIO.StringIO is available first
31
-try:
32
-    import StringIO as io
33
-except ImportError:
34
-    import io
35
-
36
-
37
-class ConfigTestCase(testtools.TestCase):
38
-    """Class testing config behavior."""
39
-
40
-    @mock.patch('git_review.cmd.LOCAL_MODE',
41
-                mock.PropertyMock(return_value=True))
42
-    @mock.patch('git_review.cmd.git_directories', return_value=['', 'fake'])
43
-    @mock.patch('git_review.cmd.run_command_exc')
44
-    def test_git_local_mode(self, run_mock, dir_mock):
45
-        cmd.git_config_get_value('abc', 'def')
46
-        run_mock.assert_called_once_with(
47
-            cmd.GitConfigException,
48
-            'git', 'config', '-f', 'fake/config', '--get', 'abc.def')
49
-
50
-    @mock.patch('git_review.cmd.LOCAL_MODE',
51
-                mock.PropertyMock(return_value=True))
52
-    @mock.patch('os.path.exists', return_value=False)
53
-    def test_gitreview_local_mode(self, exists_mock):
54
-        cmd.Config()
55
-        self.assertFalse(exists_mock.called)
56
-
57
-
58
-class GitReviewConsole(testtools.TestCase, fixtures.TestWithFixtures):
59
-    """Class for testing the console output of git-review."""
60
-
61
-    reviews = [
62
-        {
63
-            'number': '1010101',
64
-            'branch': 'master',
65
-            'subject': 'A simple short subject'
66
-        }, {
67
-            'number': '9877',
68
-            'branch': 'stable/codeword',
69
-            'subject': 'A longer and slightly more wordy subject'
70
-        }, {
71
-            'number': '12345',
72
-            'branch': 'master',
73
-            'subject': 'A ridiculously long subject that can exceed the '
74
-                       'normal console width, just need to ensure the '
75
-                       'max width is short enough'
76
-        }]
77
-
78
-    def setUp(self):
79
-        super(GitReviewConsole, self).setUp()
80
-        # ensure all tests get a separate git dir to work in to avoid
81
-        # local git config from interfering
82
-        self.tempdir = self.useFixture(fixtures.TempDir())
83
-        self._run_git = functools.partial(utils.run_git,
84
-                                          chdir=self.tempdir.path)
85
-
86
-        self.run_cmd_patcher = mock.patch('git_review.cmd.run_command_status')
87
-        run_cmd_partial = functools.partial(
88
-            cmd.run_command_status, GIT_WORK_TREE=self.tempdir.path,
89
-            GIT_DIR=os.path.join(self.tempdir.path, '.git'))
90
-        self.run_cmd_mock = self.run_cmd_patcher.start()
91
-        self.run_cmd_mock.side_effect = run_cmd_partial
92
-
93
-        self._run_git('init')
94
-        self._run_git('commit', '--allow-empty', '-m "initial commit"')
95
-        self._run_git('commit', '--allow-empty', '-m "2nd commit"')
96
-
97
-    def tearDown(self):
98
-        self.run_cmd_patcher.stop()
99
-        super(GitReviewConsole, self).tearDown()
100
-
101
-    @mock.patch('git_review.cmd.query_reviews')
102
-    @mock.patch('git_review.cmd.get_remote_url', mock.MagicMock)
103
-    @mock.patch('git_review.cmd._has_color', False)
104
-    def test_list_reviews_no_blanks(self, mock_query):
105
-
106
-        mock_query.return_value = self.reviews
107
-        with mock.patch('sys.stdout', new_callable=io.StringIO) as output:
108
-            cmd.list_reviews(None)
109
-            console_output = output.getvalue().split('\n')
110
-
111
-        wrapper = textwrap.TextWrapper(replace_whitespace=False,
112
-                                       drop_whitespace=False)
113
-        for text in console_output:
114
-            for line in wrapper.wrap(text):
115
-                self.assertEqual(line.isspace(), False,
116
-                                 "Extra blank lines appearing between reviews"
117
-                                 "in console output")
118
-
119
-    @mock.patch('git_review.cmd._use_color', None)
120
-    def test_color_output_disabled(self):
121
-        """Test disabling of colour output color.ui defaults to enabled
122
-        """
123
-
124
-        # git versions < 1.8.4 default to 'color.ui' being false
125
-        # so must be set to auto to correctly test
126
-        self._run_git("config", "color.ui", "auto")
127
-
128
-        self._run_git("config", "color.review", "never")
129
-        self.assertFalse(cmd.check_use_color_output(),
130
-                         "Failed to detect color output disabled")
131
-
132
-    @mock.patch('git_review.cmd._use_color', None)
133
-    def test_color_output_forced(self):
134
-        """Test force enable of colour output when color.ui
135
-        is defaulted to false
136
-        """
137
-
138
-        self._run_git("config", "color.ui", "never")
139
-
140
-        self._run_git("config", "color.review", "always")
141
-        self.assertTrue(cmd.check_use_color_output(),
142
-                        "Failed to detect color output forcefully "
143
-                        "enabled")
144
-
145
-    @mock.patch('git_review.cmd._use_color', None)
146
-    def test_color_output_fallback(self):
147
-        """Test fallback to using color.ui when color.review is not
148
-        set
149
-        """
150
-
151
-        self._run_git("config", "color.ui", "always")
152
-        self.assertTrue(cmd.check_use_color_output(),
153
-                        "Failed to use fallback to color.ui when "
154
-                        "color.review not present")
155
-
156
-
157
-class FakeResponse(object):
158
-
159
-    def __init__(self, code, text=""):
160
-        self.status_code = code
161
-        self.text = text
162
-
163
-
164
-class FakeException(Exception):
165
-
166
-    def __init__(self, code, *args, **kwargs):
167
-        super(FakeException, self).__init__(*args, **kwargs)
168
-        self.code = code
169
-
170
-
171
-FAKE_GIT_CREDENTIAL_FILL = """\
172
-protocol=http
173
-host=gerrit.example.com
174
-username=user
175
-password=pass
176
-"""
177
-
178
-
179
-class ResolveTrackingUnitTest(testtools.TestCase):
180
-    """Class for testing resolve_tracking."""
181
-    def setUp(self):
182
-        testtools.TestCase.setUp(self)
183
-        patcher = mock.patch('git_review.cmd.run_command_exc')
184
-        self.addCleanup(patcher.stop)
185
-        self.run_command_exc = patcher.start()
186
-
187
-    def test_track_local_branch(self):
188
-        'Test that local tracked branch is not followed.'
189
-        self.run_command_exc.side_effect = [
190
-            '',
191
-            'refs/heads/other/branch',
192
-        ]
193
-        self.assertEqual(cmd.resolve_tracking(u'remote', u'rbranch'),
194
-                         (u'remote', u'rbranch'))
195
-
196
-    def test_track_untracked_branch(self):
197
-        'Test that local untracked branch is not followed.'
198
-        self.run_command_exc.side_effect = [
199
-            '',
200
-            '',
201
-        ]
202
-        self.assertEqual(cmd.resolve_tracking(u'remote', u'rbranch'),
203
-                         (u'remote', u'rbranch'))
204
-
205
-    def test_track_remote_branch(self):
206