hubflow 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,192 @@
1
+ hub(1) -- git + hub = github
2
+ ============================
3
+
4
+ ## SYNOPSIS
5
+
6
+ `hub` [`--noop`] <COMMAND> <OPTIONS>
7
+ `hub alias` [`-s`] <SHELL>
8
+
9
+ ### Expanded git commands:
10
+
11
+ `git init -g` <OPTIONS>
12
+ `git clone` [`-p`] <OPTIONS> [<USER>/]<REPOSITORY> <DIRECTORY>
13
+ `git remote add` [`-p`] <OPTIONS> <USER>[/<REPOSITORY>]
14
+ `git remote set-url` [`-p`] <OPTIONS> <REMOTE-NAME> <USER>[/<REPOSITORY>]
15
+ `git fetch` <USER-1>,[<USER-2>,...]
16
+ `git checkout` <PULLREQ-URL> [<BRANCH>]
17
+ `git cherry-pick` <GITHUB-REF>
18
+ `git am` <GITHUB-URL>
19
+ `git apply` <GITHUB-URL>
20
+ `git push` <REMOTE-1>,<REMOTE-2>,...,<REMOTE-N> [<REF>]
21
+ `git submodule add` [`-p`] <OPTIONS> [<USER>/]<REPOSITORY> <DIRECTORY>
22
+
23
+ ### Custom git commands:
24
+
25
+ `git create` [<NAME>] [`-p`] [`-d` <DESCRIPTION>] [`-h` <HOMEPAGE>]
26
+ `git browse` [`-u`] [[<USER>`/`]<REPOSITORY>] [SUBPAGE]
27
+ `git compare` [`-u`] [<USER>] [<START>...]<END>
28
+ `git fork` [`--no-remote`]
29
+ `git pull-request` [`-f`] [<TITLE>|`-i` <ISSUE>] [`-b` <BASE>] [`-h` <HEAD>]:
30
+
31
+ ## DESCRIPTION
32
+
33
+ hub enhances various git commands to ease most common workflows with GitHub.
34
+
35
+ * `hub --noop` <COMMAND>:
36
+ Shows which command(s) would be run as a result of the current command.
37
+ Doesn't perform anything.
38
+
39
+ * `hub alias` [`-s`] <SHELL>:
40
+ Writes shell aliasing code for <SHELL> (`bash`, `sh`, `zsh`,
41
+ `csh`) to standard output. With the `-s` option, the output of
42
+ this command can be evaluated directly within the shell:
43
+ `eval $(hub alias -s bash)`
44
+
45
+ * `git init` `-g` <OPTIONS>:
46
+ Create a git repository as with git-init(1) and add remote `origin` at
47
+ "git@github.com:<USER>/<REPOSITORY>.git"; <USER> is your GitHub username and
48
+ <REPOSITORY> is the current working directory's basename.
49
+
50
+ * `git clone` [`-p`] <OPTIONS> [<USER>`/`]<REPOSITORY> <DIRECTORY>:
51
+ Clone repository "git://github.com/<USER>/<REPOSITORY>.git" into
52
+ <DIRECTORY> as with git-clone(1). When <USER>/ is omitted, assumes
53
+ your GitHub login. With `-p`, clone private repositories over SSH.
54
+ For repositories under your GitHub login, `-p` is implicit.
55
+
56
+ * `git remote add` [`-p`] <OPTIONS> <USER>[`/`<REPOSITORY>]:
57
+ Add remote "git://github.com/<USER>/<REPOSITORY>.git" as with
58
+ git-remote(1). When /<REPOSITORY> is omitted, the basename of the
59
+ current working directory is used. With `-p`, use private remote
60
+ "git@github.com:<USER>/<REPOSITORY>.git". If <USER> is "origin"
61
+ then uses your GitHub login.
62
+
63
+ * `git remote set-url` [`-p`] <OPTIONS> <REMOTE-NAME> <USER>[/<REPOSITORY>]:
64
+ Sets the url of remote <REMOTE-NAME> using the same rules as
65
+ `git remote add`.
66
+
67
+ * `git fetch` <USER-1>,[<USER-2>,...]:
68
+ Adds missing remote(s) with `git remote add` prior to fetching. New
69
+ remotes are only added if they correspond to valid forks on GitHub.
70
+
71
+ * `git checkout` <PULLREQ-URL> [<BRANCH>]:
72
+ Checks out the head of the pull request as a local branch, to allow for
73
+ reviewing, rebasing and otherwise cleaning up the commits in the pull
74
+ request before merging. The name of the local branch can explicitly be
75
+ set with <BRANCH>.
76
+
77
+ * `git cherry-pick` <GITHUB-REF>:
78
+ Cherry-pick a commit from a fork using either full URL to the commit
79
+ or GitHub-flavored Markdown notation, which is `user@sha`. If the remote
80
+ doesn't yet exist, it will be added. A `git fetch <user>` is issued
81
+ prior to the cherry-pick attempt.
82
+
83
+ * `git [am|apply]` <GITHUB-URL>:
84
+ Downloads the patch file for the pull request or commit at the URL and
85
+ applies that patch from disk with `git am` or `git apply`. Similar to
86
+ `cherry-pick`, but doesn't add new remotes. `git am` creates commits while
87
+ preserving authorship info while `apply` only applies the patch to the
88
+ working copy.
89
+
90
+ * `git push` <REMOTE-1>,<REMOTE-2>,...,<REMOTE-N> [<REF>]:
91
+ Push <REF> to each of <REMOTE-1> through <REMOTE-N> by executing
92
+ multiple `git push` commands.
93
+
94
+ * `git submodule add` [`-p`] <OPTIONS> [<USER>/]<REPOSITORY> <DIRECTORY>:
95
+ Submodule repository "git://github.com/<USER>/<REPOSITORY>.git" into
96
+ <DIRECTORY> as with git-submodule(1). When <USER>/ is omitted, assumes
97
+ your GitHub login. With `-p`, use private remote
98
+ "git@github.com:<USER>/<REPOSITORY>.git".
99
+
100
+ * `git help`:
101
+ Display enhanced git-help(1).
102
+
103
+ hub also adds some custom commands that are otherwise not present in git:
104
+
105
+ * `git create` [<NAME>] [`-p`] [`-d` <DESCRIPTION>] [`-h` <HOMEPAGE>]:
106
+ Create a new public GitHub repository from the current git
107
+ repository and add remote `origin` at
108
+ "git@github.com:<USER>/<REPOSITORY>.git"; <USER> is your GitHub
109
+ username and <REPOSITORY> is the current working directory name.
110
+ To explicitly name the new repository, pass in <NAME>, optionally in
111
+ <ORGANIZATION>/<NAME> form to create under an organization you're a
112
+ member of. With `-p`, create a private repository, and with `-d` and `-h`
113
+ set the repository's description and homepage URL, respectively.
114
+
115
+ * `git browse` [`-u`] [[<USER>`/`]<REPOSITORY>] [SUBPAGE]:
116
+ Open repository's GitHub page in the system's default web browser using
117
+ `open(1)` or the `BROWSER` env variable. If the repository isn't
118
+ specified, `browse` opens the page of the repository found in the current
119
+ directory. If SUBPAGE is specified, the browser will open on the specified
120
+ subpage: one of "wiki", "commits", "issues" or other (the default is
121
+ "tree").
122
+
123
+ * `git compare` [`-u`] [<USER>] [<START>...]<END>:
124
+ Open a GitHub compare view page in the system's default web browser.
125
+ <START> to <END> are branch names, tag names, or commit SHA1s specifying
126
+ the range of history to compare. If a range with two dots (`a..b`) is given,
127
+ it will be transformed into one with three dots. If <START> is omitted,
128
+ GitHub will compare against the base branch (the default is "master").
129
+
130
+ * `git fork` [`--no-remote`]:
131
+ Forks the original project (referenced by "origin" remote) on GitHub and
132
+ adds a new remote for it under your username. Requires `github.token` to
133
+ be set (see CONFIGURATION).
134
+
135
+ * `git pull-request` [`-f`] [<TITLE>|`-i` <ISSUE>] [`-b` <BASE>] [`-h` <HEAD>]:
136
+ `git pull-request` [`-f`] <ISSUE-URL> [`-h` <HEAD>]
137
+ Opens a pull request on GitHub for the project that the "origin" remote
138
+ points to. The default head of the pull request is the current branch.
139
+ Both base and head of the pull request can be explicitly given in one of
140
+ the following formats: "branch", "owner:branch", "owner/repo:branch".
141
+ This command will abort operation if it detects that the current topic
142
+ branch has local commits that are not yet pushed to its upstream branch
143
+ on the remote. To skip this check, use `-f`.
144
+
145
+ If <TITLE> is omitted, a text editor will open in which title and body of
146
+ the pull request can be entered in the same manner as git commit message.
147
+
148
+ If instead of normal <TITLE> an issue number is given with `-i`, the pull
149
+ request will be attached to an existing GitHub issue. Alternatively, instead
150
+ of title you can paste a full URL to an issue on GitHub.
151
+
152
+ ## CONFIGURATION
153
+
154
+ Use git-config(1) to display the currently configured GitHub username:
155
+
156
+ $ git config --global github.user
157
+
158
+ Or, set the GitHub username and token with:
159
+
160
+ $ git config --global github.user <username>
161
+ $ git config --global github.token <token>
162
+
163
+ You can override these values with <GITHUB_USER> and <GITHUB_TOKEN>
164
+ environment variables.
165
+
166
+ See <http://help.github.com/set-your-user-name-email-and-github-token/> for more
167
+ information.
168
+
169
+ If you prefer the HTTPS protocol for GitHub repositories, you can set
170
+ "hub.protocol" to "https". This will affect `clone`, `fork`, `remote add`
171
+ and other operations that expand references to GitHub repositories as full
172
+ URLs that otherwise use git and ssh protocols.
173
+
174
+ $ git config --global hub.protocol https
175
+
176
+ ## EXAMPLES
177
+
178
+ {{README}}
179
+
180
+ ## BUGS
181
+
182
+ <https://github.com/defunkt/hub/issues>
183
+
184
+ ## AUTHORS
185
+
186
+ <https://github.com/defunkt/hub/contributors>
187
+
188
+ ## SEE ALSO
189
+
190
+ git(1), git-clone(1), git-remote(1), git-init(1),
191
+ <http://github.com>,
192
+ <https://github.com/defunkt/hub>
@@ -0,0 +1,40 @@
1
+ require 'helper'
2
+
3
+ class AliasTest < Test::Unit::TestCase
4
+ def test_alias
5
+ instructions = hub("alias")
6
+ assert_includes "bash", instructions
7
+ assert_includes "sh", instructions
8
+ assert_includes "csh", instructions
9
+ assert_includes "zsh", instructions
10
+ assert_includes "fish", instructions
11
+ end
12
+
13
+ def test_alias_silent
14
+ assert_equal "alias git=hub\n", hub("alias -s bash")
15
+ end
16
+
17
+ def test_alias_bash
18
+ assert_alias_command "bash", "alias git=hub"
19
+ end
20
+
21
+ def test_alias_sh
22
+ assert_alias_command "sh", "alias git=hub"
23
+ end
24
+
25
+ def test_alias_zsh
26
+ assert_alias_command "zsh", 'function git(){hub "$@"}'
27
+ end
28
+
29
+ def test_alias_csh
30
+ assert_alias_command "csh", "alias git hub"
31
+ end
32
+
33
+ def test_alias_fish
34
+ assert_alias_command "fish", "alias git hub"
35
+ end
36
+
37
+ def test_alias_blah
38
+ assert_alias_command "blah", "fatal: never heard of `blah'"
39
+ end
40
+ end
@@ -0,0 +1 @@
1
+ webmock 1.3.0
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+ if [ "$1" = "--version" ] || [ "$1" = "version" ]; then
3
+ echo "git version 1.7.0.4"
4
+ elif [ "$1" = "--exec-path" ]; then
5
+ echo "/usr/lib/git-core"
6
+ elif [ "$1" = "--html-path" ]; then
7
+ echo "/usr/share/doc/git-doc"
8
+ else
9
+ echo "ERROR: git was called, but wasn't supposed to:" git $*
10
+ exit 1
11
+ fi
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ echo "ERROR: open was called, but wasn't supposed to:" open $*
3
+ exit 1
@@ -0,0 +1,111 @@
1
+ require 'test/unit'
2
+ require 'hub'
3
+ require 'hub/standalone'
4
+
5
+ # We're checking for `open` in our tests
6
+ ENV['BROWSER'] = 'open'
7
+
8
+ # Setup path with fake executables in case a test hits them
9
+ fakebin_dir = File.expand_path('../fakebin', __FILE__)
10
+ ENV['PATH'] = "#{fakebin_dir}:#{ENV['PATH']}"
11
+
12
+ class Test::Unit::TestCase
13
+ # Shortcut for creating a `Hub` instance. Pass it what you would
14
+ # normally pass `hub` on the command line, e.g.
15
+ #
16
+ # shell: hub clone rtomayko/tilt
17
+ # test: Hub("clone rtomayko/tilt")
18
+ def Hub(args)
19
+ Hub::Runner.new(*args.split(' '))
20
+ end
21
+
22
+ # Shortcut for running the `hub` command in a subprocess. Returns
23
+ # STDOUT as a string. Pass it what you would normally pass `hub` on
24
+ # the command line, e.g.
25
+ #
26
+ # shell: hub clone rtomayko/tilt
27
+ # test: hub("clone rtomayko/tilt")
28
+ #
29
+ # If a block is given it will be run in the child process before
30
+ # execution begins. You can use this to monkeypatch or fudge the
31
+ # environment before running hub.
32
+ def hub(args, input = nil)
33
+ parent_read, child_write = IO.pipe
34
+ child_read, parent_write = IO.pipe if input
35
+
36
+ fork do
37
+ yield if block_given?
38
+ $stdin.reopen(child_read) if input
39
+ $stdout.reopen(child_write)
40
+ $stderr.reopen(child_write)
41
+ Hub(args).execute
42
+ end
43
+
44
+ if input
45
+ parent_write.write input
46
+ parent_write.close
47
+ end
48
+ child_write.close
49
+ parent_read.read
50
+ end
51
+
52
+ # Asserts that `hub` will run a specific git command based on
53
+ # certain input.
54
+ #
55
+ # e.g.
56
+ # assert_command "clone git/hub", "git clone git://github.com/git/hub.git"
57
+ #
58
+ # Here we are saying that this:
59
+ # $ hub clone git/hub
60
+ # Should in turn execute this:
61
+ # $ git clone git://github.com/git/hub.git
62
+ def assert_command(input, expected)
63
+ assert_equal expected, Hub(input).command, "$ git #{input}"
64
+ end
65
+
66
+ def assert_commands(*expected)
67
+ input = expected.pop
68
+ assert_equal expected, Hub(input).commands
69
+ end
70
+
71
+ # Asserts that the command will be forwarded to git without changes
72
+ def assert_forwarded(input)
73
+ cmd = Hub(input)
74
+ assert !cmd.args.changed?, "arguments were not supposed to change: #{cmd.args.inspect}"
75
+ end
76
+
77
+ # Asserts that `hub` will show a specific alias command for a
78
+ # specific shell.
79
+ #
80
+ # e.g.
81
+ # assert_alias_command "sh", "alias git=hub"
82
+ #
83
+ # Here we are saying that this:
84
+ # $ hub alias sh
85
+ # Should display this:
86
+ # Run this in your shell to start using `hub` as `git`:
87
+ # alias git=hub
88
+ def assert_alias_command(shell, command)
89
+ expected = "Run this in your shell to start using `hub` as `git`:\n %s\n"
90
+ assert_equal(expected % command, hub("alias #{shell}"))
91
+ end
92
+
93
+ # Asserts that `haystack` includes `needle`.
94
+ def assert_includes(needle, haystack)
95
+ assert haystack.include?(needle),
96
+ "expected #{needle.inspect} in #{haystack.inspect}"
97
+ end
98
+
99
+ # Asserts that `haystack` does not include `needle`.
100
+ def assert_not_includes(needle, haystack)
101
+ assert !haystack.include?(needle),
102
+ "didn't expect #{needle.inspect} in #{haystack.inspect}"
103
+ end
104
+
105
+ # Version of assert_equal tailored for big output
106
+ def assert_output(expected, command)
107
+ output = hub(command) { ENV['GIT'] = 'echo' }
108
+ assert expected == output,
109
+ "expected:\n#{expected}\ngot:\n#{output}"
110
+ end
111
+ end
@@ -0,0 +1,1224 @@
1
+ require 'helper'
2
+ require 'webmock/test_unit'
3
+ require 'rbconfig'
4
+ require 'yaml'
5
+ require 'forwardable'
6
+ require 'json'
7
+
8
+ WebMock::BodyPattern.class_eval do
9
+ undef normalize_hash
10
+ # override normalizing hash since it otherwise requires JSON
11
+ def normalize_hash(hash) hash end
12
+ end
13
+
14
+ class HubTest < Test::Unit::TestCase
15
+ extend Forwardable
16
+
17
+ if defined? WebMock::API
18
+ include WebMock::API
19
+ else
20
+ include WebMock
21
+ end
22
+
23
+ COMMANDS = []
24
+
25
+ Hub::Context.class_eval do
26
+ remove_method :which
27
+ define_method :which do |name|
28
+ COMMANDS.include?(name) ? "/usr/bin/#{name}" : nil
29
+ end
30
+ end
31
+
32
+ attr_reader :git_reader
33
+ include Hub::Context::GitReaderMethods
34
+ def_delegators :git_reader, :stub_config_value, :stub_command_output
35
+
36
+ def setup
37
+ super
38
+ COMMANDS.replace %w[open groff]
39
+ Hub::Context::PWD.replace '/path/to/hub'
40
+
41
+ @git_reader = Hub::Context::GitReader.new 'git' do |cache, cmd|
42
+ unless cmd.index('config --get alias.') == 0
43
+ raise ArgumentError, "`git #{cmd}` not stubbed"
44
+ end
45
+ end
46
+
47
+ Hub::Commands.instance_variable_set :@git_reader, @git_reader
48
+ Hub::Commands.instance_variable_set :@local_repo, nil
49
+
50
+ @git_reader.stub! \
51
+ 'remote' => "mislav\norigin",
52
+ 'symbolic-ref -q HEAD' => 'refs/heads/master',
53
+ 'config --get github.user' => 'tpw',
54
+ 'config --get github.token' => 'abc123',
55
+ 'config --get-all remote.origin.url' => 'git://github.com/defunkt/hub.git',
56
+ 'config --get-all remote.mislav.url' => 'git://github.com/mislav/hub.git',
57
+ 'rev-parse --symbolic-full-name master@{upstream}' => 'refs/remotes/origin/master',
58
+ 'config --get --bool hub.http-clone' => 'false',
59
+ 'config --get hub.protocol' => nil,
60
+ 'rev-parse -q --git-dir' => '.git'
61
+ end
62
+
63
+ def test_private_clone
64
+ input = "clone -p rtomayko/ronn"
65
+ command = "git clone git@github.com:rtomayko/ronn.git"
66
+ assert_command input, command
67
+ end
68
+
69
+ def test_private_clone_noop
70
+ input = "--noop clone -p rtomayko/ronn"
71
+ command = "git clone git@github.com:rtomayko/ronn.git\n"
72
+ assert_output command, hub(input)
73
+ end
74
+
75
+ def test_https_clone
76
+ stub_https_is_preferred
77
+ input = "clone rtomayko/ronn"
78
+ command = "git clone https://github.com/rtomayko/ronn.git"
79
+ assert_command input, command
80
+ end
81
+
82
+ def test_public_clone
83
+ input = "clone rtomayko/ronn"
84
+ command = "git clone git://github.com/rtomayko/ronn.git"
85
+ assert_command input, command
86
+ end
87
+
88
+ def test_your_private_clone
89
+ input = "clone -p resque"
90
+ command = "git clone git@github.com:tpw/resque.git"
91
+ assert_command input, command
92
+ end
93
+
94
+ def test_your_clone_is_always_private
95
+ input = "clone resque"
96
+ command = "git clone git@github.com:tpw/resque.git"
97
+ assert_command input, command
98
+ end
99
+
100
+ def test_clone_repo_with_period
101
+ input = "clone hookio/hook.js"
102
+ command = "git clone git://github.com/hookio/hook.js.git"
103
+ assert_command input, command
104
+ end
105
+
106
+ def test_clone_with_arguments
107
+ input = "clone --bare -o master resque"
108
+ command = "git clone --bare -o master git@github.com:tpw/resque.git"
109
+ assert_command input, command
110
+ end
111
+
112
+ def test_clone_with_arguments_and_destination
113
+ assert_forwarded "clone --template=one/two git://github.com/tpw/resque.git --origin master resquetastic"
114
+ end
115
+
116
+ def test_your_private_clone_fails_without_config
117
+ out = hub("clone -p mustache") do
118
+ stub_github_user(nil)
119
+ end
120
+
121
+ assert_equal "** No GitHub user set. See http://help.github.com/set-your-user-name-email-and-github-token/\n", out
122
+ end
123
+
124
+ def test_your_public_clone_fails_without_config
125
+ out = hub("clone mustache") do
126
+ stub_github_user(nil)
127
+ end
128
+
129
+ assert_equal "** No GitHub user set. See http://help.github.com/set-your-user-name-email-and-github-token/\n", out
130
+ end
131
+
132
+ def test_private_clone_left_alone
133
+ assert_forwarded "clone git@github.com:rtomayko/ronn.git"
134
+ end
135
+
136
+ def test_public_clone_left_alone
137
+ assert_forwarded "clone git://github.com/rtomayko/ronn.git"
138
+ end
139
+
140
+ def test_normal_public_clone_with_path
141
+ assert_forwarded "clone git://github.com/rtomayko/ronn.git ronn-dev"
142
+ end
143
+
144
+ def test_normal_clone_from_path
145
+ assert_forwarded "clone ./test"
146
+ end
147
+
148
+ def test_clone_with_host_alias
149
+ assert_forwarded "clone server:git/repo.git"
150
+ end
151
+
152
+ def test_alias_expand
153
+ stub_alias 'c', 'clone --bare'
154
+ input = "c rtomayko/ronn"
155
+ command = "git clone --bare git://github.com/rtomayko/ronn.git"
156
+ assert_command input, command
157
+ end
158
+
159
+ def test_alias_expand_advanced
160
+ stub_alias 'c', 'clone --template="white space"'
161
+ input = "c rtomayko/ronn"
162
+ command = "git clone '--template=white space' git://github.com/rtomayko/ronn.git"
163
+ assert_command input, command
164
+ end
165
+
166
+ def test_alias_doesnt_expand_for_unknown_commands
167
+ stub_alias 'c', 'compute --fast'
168
+ assert_forwarded "c rtomayko/ronn"
169
+ end
170
+
171
+ def test_remote_origin
172
+ input = "remote add origin"
173
+ command = "git remote add origin git://github.com/tpw/hub.git"
174
+ assert_command input, command
175
+ end
176
+
177
+ def test_remote_add_with_name
178
+ input = "remote add another hookio/hub.js"
179
+ command = "git remote add another git://github.com/hookio/hub.js.git"
180
+ assert_command input, command
181
+ end
182
+
183
+ def test_private_remote_origin
184
+ input = "remote add -p origin"
185
+ command = "git remote add origin git@github.com:tpw/hub.git"
186
+ assert_command input, command
187
+ end
188
+
189
+ def test_public_remote_origin_as_normal
190
+ input = "remote add origin http://github.com/defunkt/resque.git"
191
+ command = "git remote add origin http://github.com/defunkt/resque.git"
192
+ assert_command input, command
193
+ end
194
+
195
+ def test_remote_from_rel_path
196
+ assert_forwarded "remote add origin ./path"
197
+ end
198
+
199
+ def test_remote_from_abs_path
200
+ assert_forwarded "remote add origin /path"
201
+ end
202
+
203
+ def test_remote_with_host_alias
204
+ assert_forwarded "remote add origin server:/git/repo.git"
205
+ end
206
+
207
+ def test_private_remote_origin_as_normal
208
+ assert_forwarded "remote add origin git@github.com:defunkt/resque.git"
209
+ end
210
+
211
+ def test_public_submodule
212
+ input = "submodule add wycats/bundler vendor/bundler"
213
+ command = "git submodule add git://github.com/wycats/bundler.git vendor/bundler"
214
+ assert_command input, command
215
+ end
216
+
217
+ def test_private_submodule
218
+ input = "submodule add -p grit vendor/grit"
219
+ command = "git submodule add git@github.com:tpw/grit.git vendor/grit"
220
+ assert_command input, command
221
+ end
222
+
223
+ def test_submodule_branch
224
+ input = "submodule add -b ryppl ryppl/pip vendor/pip"
225
+ command = "git submodule add -b ryppl git://github.com/ryppl/pip.git vendor/pip"
226
+ assert_command input, command
227
+ end
228
+
229
+ def test_submodule_with_args
230
+ input = "submodule -q add --bare -- grit grit"
231
+ command = "git submodule -q add --bare -- git://github.com/tpw/grit.git grit"
232
+ assert_command input, command
233
+ end
234
+
235
+ def test_private_remote
236
+ input = "remote add -p rtomayko"
237
+ command = "git remote add rtomayko git@github.com:rtomayko/hub.git"
238
+ assert_command input, command
239
+ end
240
+
241
+ def test_https_protocol_remote
242
+ stub_https_is_preferred
243
+ input = "remote add rtomayko"
244
+ command = "git remote add rtomayko https://github.com/rtomayko/hub.git"
245
+ assert_command input, command
246
+ end
247
+
248
+ def test_public_remote
249
+ input = "remote add rtomayko"
250
+ command = "git remote add rtomayko git://github.com/rtomayko/hub.git"
251
+ assert_command input, command
252
+ end
253
+
254
+ def test_public_remote_f
255
+ input = "remote add -f rtomayko"
256
+ command = "git remote add -f rtomayko git://github.com/rtomayko/hub.git"
257
+ assert_command input, command
258
+ end
259
+
260
+ def test_named_public_remote
261
+ input = "remote add origin rtomayko"
262
+ command = "git remote add origin git://github.com/rtomayko/hub.git"
263
+ assert_command input, command
264
+ end
265
+
266
+ def test_named_public_remote_f
267
+ input = "remote add -f origin rtomayko"
268
+ command = "git remote add -f origin git://github.com/rtomayko/hub.git"
269
+ assert_command input, command
270
+ end
271
+
272
+ def test_private_remote_with_repo
273
+ input = "remote add -p jashkenas/coffee-script"
274
+ command = "git remote add jashkenas git@github.com:jashkenas/coffee-script.git"
275
+ assert_command input, command
276
+ end
277
+
278
+ def test_public_remote_with_repo
279
+ input = "remote add jashkenas/coffee-script"
280
+ command = "git remote add jashkenas git://github.com/jashkenas/coffee-script.git"
281
+ assert_command input, command
282
+ end
283
+
284
+ def test_public_remote_f_with_repo
285
+ input = "remote add -f jashkenas/coffee-script"
286
+ command = "git remote add -f jashkenas git://github.com/jashkenas/coffee-script.git"
287
+ assert_command input, command
288
+ end
289
+
290
+ def test_named_private_remote_with_repo
291
+ input = "remote add -p origin jashkenas/coffee-script"
292
+ command = "git remote add origin git@github.com:jashkenas/coffee-script.git"
293
+ assert_command input, command
294
+ end
295
+
296
+ def test_fetch_existing_remote
297
+ assert_forwarded "fetch mislav"
298
+ end
299
+
300
+ def test_fetch_new_remote
301
+ stub_remotes_group('xoebus', nil)
302
+ stub_existing_fork('xoebus')
303
+
304
+ assert_commands "git remote add xoebus git://github.com/xoebus/hub.git",
305
+ "git fetch xoebus",
306
+ "fetch xoebus"
307
+ end
308
+
309
+ def test_fetch_new_remote_with_options
310
+ stub_remotes_group('xoebus', nil)
311
+ stub_existing_fork('xoebus')
312
+
313
+ assert_commands "git remote add xoebus git://github.com/xoebus/hub.git",
314
+ "git fetch --depth=1 --prune xoebus",
315
+ "fetch --depth=1 --prune xoebus"
316
+ end
317
+
318
+ def test_fetch_multiple_new_remotes
319
+ stub_remotes_group('xoebus', nil)
320
+ stub_remotes_group('rtomayko', nil)
321
+ stub_existing_fork('xoebus')
322
+ stub_existing_fork('rtomayko')
323
+
324
+ assert_commands "git remote add xoebus git://github.com/xoebus/hub.git",
325
+ "git remote add rtomayko git://github.com/rtomayko/hub.git",
326
+ "git fetch --multiple xoebus rtomayko",
327
+ "fetch --multiple xoebus rtomayko"
328
+ end
329
+
330
+ def test_fetch_multiple_comma_separated_remotes
331
+ stub_remotes_group('xoebus', nil)
332
+ stub_remotes_group('rtomayko', nil)
333
+ stub_existing_fork('xoebus')
334
+ stub_existing_fork('rtomayko')
335
+
336
+ assert_commands "git remote add xoebus git://github.com/xoebus/hub.git",
337
+ "git remote add rtomayko git://github.com/rtomayko/hub.git",
338
+ "git fetch --multiple xoebus rtomayko",
339
+ "fetch xoebus,rtomayko"
340
+ end
341
+
342
+ def test_fetch_multiple_new_remotes_with_filtering
343
+ stub_remotes_group('xoebus', nil)
344
+ stub_remotes_group('mygrp', 'one two')
345
+ stub_remotes_group('typo', nil)
346
+ stub_existing_fork('xoebus')
347
+ stub_nonexisting_fork('typo')
348
+
349
+ # mislav: existing remote; skipped
350
+ # xoebus: new remote, fork exists; added
351
+ # mygrp: a remotes group; skipped
352
+ # URL: can't be a username; skipped
353
+ # typo: fork doesn't exist; skipped
354
+ assert_commands "git remote add xoebus git://github.com/xoebus/hub.git",
355
+ "git fetch --multiple mislav xoebus mygrp git://example.com typo",
356
+ "fetch --multiple mislav xoebus mygrp git://example.com typo"
357
+ end
358
+
359
+ def test_cherry_pick
360
+ assert_forwarded "cherry-pick a319d88"
361
+ end
362
+
363
+ def test_cherry_pick_url
364
+ url = 'http://github.com/mislav/hub/commit/a319d88'
365
+ assert_commands "git fetch mislav", "git cherry-pick a319d88", "cherry-pick #{url}"
366
+ end
367
+
368
+ def test_cherry_pick_url_with_fragment
369
+ url = 'http://github.com/mislav/hub/commit/abcdef0123456789#comments'
370
+ assert_commands "git fetch mislav", "git cherry-pick abcdef0123456789", "cherry-pick #{url}"
371
+ end
372
+
373
+ def test_cherry_pick_url_with_remote_add
374
+ url = 'https://github.com/xoebus/hub/commit/a319d88'
375
+ assert_commands "git remote add -f xoebus git://github.com/xoebus/hub.git",
376
+ "git cherry-pick a319d88",
377
+ "cherry-pick #{url}"
378
+ end
379
+
380
+ def test_cherry_pick_origin_url
381
+ url = 'https://github.com/defunkt/hub/commit/a319d88'
382
+ assert_commands "git fetch origin", "git cherry-pick a319d88", "cherry-pick #{url}"
383
+ end
384
+
385
+ def test_cherry_pick_github_user_notation
386
+ assert_commands "git fetch mislav", "git cherry-pick 368af20", "cherry-pick mislav@368af20"
387
+ end
388
+
389
+ def test_cherry_pick_github_user_repo_notation
390
+ # not supported
391
+ assert_forwarded "cherry-pick mislav/hubbub@a319d88"
392
+ end
393
+
394
+ def test_cherry_pick_github_notation_too_short
395
+ assert_forwarded "cherry-pick mislav@a319"
396
+ end
397
+
398
+ def test_cherry_pick_github_notation_with_remote_add
399
+ assert_commands "git remote add -f xoebus git://github.com/xoebus/hub.git",
400
+ "git cherry-pick a319d88",
401
+ "cherry-pick xoebus@a319d88"
402
+ end
403
+
404
+ def test_am_untouched
405
+ assert_forwarded "am some.patch"
406
+ end
407
+
408
+ def test_am_pull_request
409
+ with_tmpdir('/tmp/') do
410
+ assert_commands "curl -#LA 'hub #{Hub::Version}' https://github.com/defunkt/hub/pull/55.patch -o /tmp/55.patch",
411
+ "git am --signoff /tmp/55.patch -p2",
412
+ "am --signoff https://github.com/defunkt/hub/pull/55 -p2"
413
+
414
+ cmd = Hub("am https://github.com/defunkt/hub/pull/55/files").command
415
+ assert_includes '/pull/55.patch', cmd
416
+ end
417
+ end
418
+
419
+ def test_am_no_tmpdir
420
+ with_tmpdir(nil) do
421
+ cmd = Hub("am https://github.com/defunkt/hub/pull/55").command
422
+ assert_includes '/tmp/55.patch', cmd
423
+ end
424
+ end
425
+
426
+ def test_am_commit_url
427
+ with_tmpdir('/tmp/') do
428
+ url = 'https://github.com/davidbalbert/hub/commit/fdb9921'
429
+
430
+ assert_commands "curl -#LA 'hub #{Hub::Version}' #{url}.patch -o /tmp/fdb9921.patch",
431
+ "git am --signoff /tmp/fdb9921.patch -p2",
432
+ "am --signoff #{url} -p2"
433
+ end
434
+ end
435
+
436
+ def test_am_gist
437
+ with_tmpdir('/tmp/') do
438
+ url = 'https://gist.github.com/8da7fb575debd88c54cf'
439
+
440
+ assert_commands "curl -#LA 'hub #{Hub::Version}' #{url}.txt -o /tmp/gist-8da7fb575debd88c54cf.txt",
441
+ "git am --signoff /tmp/gist-8da7fb575debd88c54cf.txt -p2",
442
+ "am --signoff #{url} -p2"
443
+ end
444
+ end
445
+
446
+ def test_apply_untouched
447
+ assert_forwarded "apply some.patch"
448
+ end
449
+
450
+ def test_apply_pull_request
451
+ with_tmpdir('/tmp/') do
452
+ assert_commands "curl -#LA 'hub #{Hub::Version}' https://github.com/defunkt/hub/pull/55.patch -o /tmp/55.patch",
453
+ "git apply /tmp/55.patch -p2",
454
+ "apply https://github.com/defunkt/hub/pull/55 -p2"
455
+
456
+ cmd = Hub("apply https://github.com/defunkt/hub/pull/55/files").command
457
+ assert_includes '/pull/55.patch', cmd
458
+ end
459
+ end
460
+
461
+ def test_apply_commit_url
462
+ with_tmpdir('/tmp/') do
463
+ url = 'https://github.com/davidbalbert/hub/commit/fdb9921'
464
+
465
+ assert_commands "curl -#LA 'hub #{Hub::Version}' #{url}.patch -o /tmp/fdb9921.patch",
466
+ "git apply /tmp/fdb9921.patch -p2",
467
+ "apply #{url} -p2"
468
+ end
469
+ end
470
+
471
+ def test_apply_gist
472
+ with_tmpdir('/tmp/') do
473
+ url = 'https://gist.github.com/8da7fb575debd88c54cf'
474
+
475
+ assert_commands "curl -#LA 'hub #{Hub::Version}' #{url}.txt -o /tmp/gist-8da7fb575debd88c54cf.txt",
476
+ "git apply /tmp/gist-8da7fb575debd88c54cf.txt -p2",
477
+ "apply #{url} -p2"
478
+ end
479
+ end
480
+
481
+ def test_init
482
+ assert_commands "git init", "git remote add origin git@github.com:tpw/hub.git", "init -g"
483
+ end
484
+
485
+ def test_init_no_login
486
+ out = hub("init -g") do
487
+ stub_github_user(nil)
488
+ end
489
+
490
+ assert_equal "** No GitHub user set. See http://help.github.com/set-your-user-name-email-and-github-token/\n", out
491
+ end
492
+
493
+ def test_push_untouched
494
+ assert_forwarded "push"
495
+ end
496
+
497
+ def test_push_two
498
+ assert_commands "git push origin cool-feature", "git push staging cool-feature",
499
+ "push origin,staging cool-feature"
500
+ end
501
+
502
+ def test_push_current_branch
503
+ stub_branch('refs/heads/cool-feature')
504
+ assert_commands "git push origin cool-feature", "git push staging cool-feature",
505
+ "push origin,staging"
506
+ end
507
+
508
+ def test_push_more
509
+ assert_commands "git push origin cool-feature",
510
+ "git push staging cool-feature",
511
+ "git push qa cool-feature",
512
+ "push origin,staging,qa cool-feature"
513
+ end
514
+
515
+ def test_create
516
+ stub_no_remotes
517
+ stub_nonexisting_fork('tpw')
518
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/repos/create").
519
+ with(:body => { 'name' => 'hub' })
520
+
521
+ expected = "remote add -f origin git@github.com:tpw/hub.git\n"
522
+ expected << "created repository: tpw/hub\n"
523
+ assert_equal expected, hub("create") { ENV['GIT'] = 'echo' }
524
+ end
525
+
526
+ def test_create_custom_name
527
+ stub_no_remotes
528
+ stub_nonexisting_fork('tpw', 'hubbub')
529
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/repos/create").
530
+ with(:body => { 'name' => 'hubbub' })
531
+
532
+ expected = "remote add -f origin git@github.com:tpw/hubbub.git\n"
533
+ expected << "created repository: tpw/hubbub\n"
534
+ assert_equal expected, hub("create hubbub") { ENV['GIT'] = 'echo' }
535
+ end
536
+
537
+ def test_create_in_organization
538
+ stub_no_remotes
539
+ stub_nonexisting_fork('acme', 'hubbub')
540
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/repos/create").
541
+ with(:body => { 'name' => 'acme/hubbub' })
542
+
543
+ expected = "remote add -f origin git@github.com:acme/hubbub.git\n"
544
+ expected << "created repository: acme/hubbub\n"
545
+ assert_equal expected, hub("create acme/hubbub") { ENV['GIT'] = 'echo' }
546
+ end
547
+
548
+ def test_create_no_openssl
549
+ stub_no_remotes
550
+ stub_nonexisting_fork('tpw')
551
+ stub_request(:post, "http://#{auth}github.com/api/v2/yaml/repos/create").
552
+ with(:body => { 'name' => 'hub' })
553
+
554
+ expected = "remote add -f origin git@github.com:tpw/hub.git\n"
555
+ expected << "created repository: tpw/hub\n"
556
+
557
+ assert_equal expected, hub("create") {
558
+ ENV['GIT'] = 'echo'
559
+ require 'net/https'
560
+ Object.send :remove_const, :OpenSSL
561
+ }
562
+ end
563
+
564
+ def test_create_failed
565
+ stub_no_remotes
566
+ stub_nonexisting_fork('tpw')
567
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/repos/create").
568
+ to_return(:status => [401, "Your token is fail"])
569
+
570
+ expected = "Error creating repository: Your token is fail (HTTP 401)\n"
571
+ expected << "Check your token configuration (`git config github.token`)\n"
572
+ assert_equal expected, hub("create") { ENV['GIT'] = 'echo' }
573
+ end
574
+
575
+ def test_create_with_env_authentication
576
+ stub_no_remotes
577
+ stub_nonexisting_fork('mojombo')
578
+
579
+ old_user = ENV['GITHUB_USER']
580
+ old_token = ENV['GITHUB_TOKEN']
581
+ ENV['GITHUB_USER'] = 'mojombo'
582
+ ENV['GITHUB_TOKEN'] = '123abc'
583
+
584
+ stub_request(:post, "https://#{auth('mojombo', '123abc')}github.com/api/v2/yaml/repos/create").
585
+ with(:body => { 'name' => 'hub' })
586
+
587
+ expected = "remote add -f origin git@github.com:mojombo/hub.git\n"
588
+ expected << "created repository: mojombo/hub\n"
589
+ assert_equal expected, hub("create") { ENV['GIT'] = 'echo' }
590
+
591
+ ensure
592
+ ENV['GITHUB_USER'] = old_user
593
+ ENV['GITHUB_TOKEN'] = old_token
594
+ end
595
+
596
+ def test_create_private_repository
597
+ stub_no_remotes
598
+ stub_nonexisting_fork('tpw')
599
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/repos/create").
600
+ with(:body => { 'name' => 'hub', 'public' => '0' })
601
+
602
+ expected = "remote add -f origin git@github.com:tpw/hub.git\n"
603
+ expected << "created repository: tpw/hub\n"
604
+ assert_equal expected, hub("create -p") { ENV['GIT'] = 'echo' }
605
+ end
606
+
607
+ def test_create_with_description_and_homepage
608
+ stub_no_remotes
609
+ stub_nonexisting_fork('tpw')
610
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/repos/create").with(:body => {
611
+ 'name' => 'hub', 'description' => 'toyproject', 'homepage' => 'http://example.com'
612
+ })
613
+
614
+ expected = "remote add -f origin git@github.com:tpw/hub.git\n"
615
+ expected << "created repository: tpw/hub\n"
616
+ assert_equal expected, hub("create -d toyproject -h http://example.com") { ENV['GIT'] = 'echo' }
617
+ end
618
+
619
+ def test_create_with_invalid_arguments
620
+ assert_equal "invalid argument: -a\n", hub("create -a blah") { ENV['GIT'] = 'echo' }
621
+ assert_equal "invalid argument: bleh\n", hub("create blah bleh") { ENV['GIT'] = 'echo' }
622
+ end
623
+
624
+ def test_create_with_existing_repository
625
+ stub_no_remotes
626
+ stub_existing_fork('tpw')
627
+
628
+ expected = "tpw/hub already exists on GitHub\n"
629
+ expected << "remote add -f origin git@github.com:tpw/hub.git\n"
630
+ expected << "set remote origin: tpw/hub\n"
631
+ assert_equal expected, hub("create") { ENV['GIT'] = 'echo' }
632
+ end
633
+
634
+ def test_create_https_protocol
635
+ stub_no_remotes
636
+ stub_existing_fork('tpw')
637
+ stub_https_is_preferred
638
+
639
+ expected = "tpw/hub already exists on GitHub\n"
640
+ expected << "remote add -f origin https://github.com/tpw/hub.git\n"
641
+ expected << "set remote origin: tpw/hub\n"
642
+ assert_equal expected, hub("create") { ENV['GIT'] = 'echo' }
643
+ end
644
+
645
+ def test_create_no_user
646
+ stub_no_remotes
647
+ out = hub("create") do
648
+ stub_github_token(nil)
649
+ end
650
+ assert_equal "** No GitHub token set. See http://help.github.com/set-your-user-name-email-and-github-token/\n", out
651
+ end
652
+
653
+ def test_create_outside_git_repo
654
+ stub_no_git_repo
655
+ assert_equal "'create' must be run from inside a git repository\n", hub("create")
656
+ end
657
+
658
+ def test_create_origin_already_exists
659
+ stub_nonexisting_fork('tpw')
660
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/repos/create").
661
+ with(:body => { 'name' => 'hub' })
662
+
663
+ expected = "remote -v\ncreated repository: tpw/hub\n"
664
+ assert_equal expected, hub("create") { ENV['GIT'] = 'echo' }
665
+ end
666
+
667
+ def test_fork
668
+ stub_nonexisting_fork('tpw')
669
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/repos/fork/defunkt/hub")
670
+
671
+ expected = "remote add -f tpw git@github.com:tpw/hub.git\n"
672
+ expected << "new remote: tpw\n"
673
+ assert_equal expected, hub("fork") { ENV['GIT'] = 'echo' }
674
+ end
675
+
676
+ def test_fork_failed
677
+ stub_nonexisting_fork('tpw')
678
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/repos/fork/defunkt/hub").
679
+ to_return(:status => [500, "Your fork is fail"])
680
+
681
+ expected = "Error creating fork: Your fork is fail (HTTP 500)\n"
682
+ assert_equal expected, hub("fork") { ENV['GIT'] = 'echo' }
683
+ end
684
+
685
+ def test_fork_no_remote
686
+ stub_nonexisting_fork('tpw')
687
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/repos/fork/defunkt/hub")
688
+
689
+ assert_equal "", hub("fork --no-remote") { ENV['GIT'] = 'echo' }
690
+ end
691
+
692
+ def test_fork_already_exists
693
+ stub_existing_fork('tpw')
694
+
695
+ expected = "tpw/hub already exists on GitHub\n"
696
+ expected << "remote add -f tpw git@github.com:tpw/hub.git\n"
697
+ expected << "new remote: tpw\n"
698
+ assert_equal expected, hub("fork") { ENV['GIT'] = 'echo' }
699
+ end
700
+
701
+ def test_fork_https_protocol
702
+ stub_existing_fork('tpw')
703
+ stub_https_is_preferred
704
+
705
+ expected = "tpw/hub already exists on GitHub\n"
706
+ expected << "remote add -f tpw https://github.com/tpw/hub.git\n"
707
+ expected << "new remote: tpw\n"
708
+ assert_equal expected, hub("fork") { ENV['GIT'] = 'echo' }
709
+ end
710
+
711
+ def test_pullrequest
712
+ expected = "Aborted: head branch is the same as base (\"master\")\n" <<
713
+ "(use `-h <branch>` to specify an explicit pull request head)\n"
714
+ assert_output expected, "pull-request hereyougo"
715
+ end
716
+
717
+ def test_pullrequest_with_unpushed_commits
718
+ stub_tracking('master', 'mislav', 'master')
719
+ stub_command_output "rev-list --cherry mislav/master...", "+abcd1234\n+bcde2345"
720
+
721
+ expected = "Aborted: 2 commits are not yet pushed to mislav/master\n" <<
722
+ "(use `-f` to force submit a pull request anyway)\n"
723
+ assert_output expected, "pull-request hereyougo"
724
+ end
725
+
726
+ def test_pullrequest_from_branch
727
+ stub_branch('refs/heads/feature')
728
+ stub_tracking_nothing('feature')
729
+
730
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/pulls/defunkt/hub").
731
+ with(:body => { 'pull' => {'base' => "master", 'head' => "tpw:feature", 'title' => "hereyougo"} }).
732
+ to_return(:body => mock_pullreq_response(1))
733
+
734
+ expected = "https://github.com/defunkt/hub/pull/1\n"
735
+ assert_output expected, "pull-request hereyougo -f"
736
+ end
737
+
738
+ def test_pullrequest_from_tracking_branch
739
+ stub_branch('refs/heads/feature')
740
+ stub_tracking('feature', 'mislav', 'yay-feature')
741
+ stub_command_output "rev-list --cherry mislav/master...", nil
742
+
743
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/pulls/defunkt/hub").
744
+ with(:body => { 'pull' => {'base' => "master", 'head' => "mislav:yay-feature", 'title' => "hereyougo"} }).
745
+ to_return(:body => mock_pullreq_response(1))
746
+
747
+ expected = "https://github.com/defunkt/hub/pull/1\n"
748
+ assert_output expected, "pull-request hereyougo -f"
749
+ end
750
+
751
+ def test_pullrequest_explicit_head
752
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/pulls/defunkt/hub").
753
+ with(:body => { 'pull' => {'base' => "master", 'head' => "tpw:yay-feature", 'title' => "hereyougo"} }).
754
+ to_return(:body => mock_pullreq_response(1))
755
+
756
+ expected = "https://github.com/defunkt/hub/pull/1\n"
757
+ assert_output expected, "pull-request hereyougo -h yay-feature -f"
758
+ end
759
+
760
+ def test_pullrequest_explicit_head_with_owner
761
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/pulls/defunkt/hub").
762
+ with(:body => { 'pull' => {'base' => "master", 'head' => "mojombo:feature", 'title' => "hereyougo"} }).
763
+ to_return(:body => mock_pullreq_response(1))
764
+
765
+ expected = "https://github.com/defunkt/hub/pull/1\n"
766
+ assert_output expected, "pull-request hereyougo -h mojombo:feature -f"
767
+ end
768
+
769
+ def test_pullrequest_explicit_base
770
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/pulls/defunkt/hub").
771
+ with(:body => { 'pull' => {'base' => "feature", 'head' => "defunkt:master", 'title' => "hereyougo"} }).
772
+ to_return(:body => mock_pullreq_response(1))
773
+
774
+ expected = "https://github.com/defunkt/hub/pull/1\n"
775
+ assert_output expected, "pull-request hereyougo -b feature -f"
776
+ end
777
+
778
+ def test_pullrequest_explicit_base_with_owner
779
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/pulls/mojombo/hub").
780
+ with(:body => { 'pull' => {'base' => "feature", 'head' => "defunkt:master", 'title' => "hereyougo"} }).
781
+ to_return(:body => mock_pullreq_response(1))
782
+
783
+ expected = "https://github.com/defunkt/hub/pull/1\n"
784
+ assert_output expected, "pull-request hereyougo -b mojombo:feature -f"
785
+ end
786
+
787
+ def test_pullrequest_explicit_base_with_repo
788
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/pulls/mojombo/hubbub").
789
+ with(:body => { 'pull' => {'base' => "feature", 'head' => "defunkt:master", 'title' => "hereyougo"} }).
790
+ to_return(:body => mock_pullreq_response(1))
791
+
792
+ expected = "https://github.com/defunkt/hub/pull/1\n"
793
+ assert_output expected, "pull-request hereyougo -b mojombo/hubbub:feature -f"
794
+ end
795
+
796
+ def test_pullrequest_existing_issue
797
+ stub_branch('refs/heads/myfix')
798
+ stub_tracking('myfix', 'mislav', 'awesomefix')
799
+ stub_command_output "rev-list --cherry mislav/awesomefix...", nil
800
+
801
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/pulls/defunkt/hub").
802
+ with(:body => { 'pull' => {'base' => "master", 'head' => "mislav:awesomefix", 'issue' => '92'} }).
803
+ to_return(:body => mock_pullreq_response(92))
804
+
805
+ expected = "https://github.com/defunkt/hub/pull/92\n"
806
+ assert_output expected, "pull-request -i 92"
807
+ end
808
+
809
+ def test_pullrequest_existing_issue_url
810
+ stub_branch('refs/heads/myfix')
811
+ stub_tracking('myfix', 'mislav', 'awesomefix')
812
+ stub_command_output "rev-list --cherry mislav/awesomefix...", nil
813
+
814
+ stub_request(:post, "https://#{auth}github.com/api/v2/yaml/pulls/mojombo/hub").
815
+ with(:body => { 'pull' => {'base' => "master", 'head' => "mislav:awesomefix", 'issue' => '92'} }).
816
+ to_return(:body => mock_pullreq_response(92, 'mojombo/hub'))
817
+
818
+ expected = "https://github.com/mojombo/hub/pull/92\n"
819
+ assert_output expected, "pull-request https://github.com/mojombo/hub/issues/92#comment_4"
820
+ end
821
+
822
+ def test_checkout_no_changes
823
+ assert_forwarded "checkout master"
824
+ end
825
+
826
+ def test_checkout_pullrequest
827
+ stub_request(:get, "http://github.com/api/v2/json/pulls/defunkt/hub/73").
828
+ to_return(:body => mock_pull_response('blueyed:feature'))
829
+
830
+ assert_commands 'git remote add -f -t feature blueyed git://github.com/blueyed/hub.git',
831
+ 'git checkout -b blueyed-feature blueyed/feature',
832
+ "checkout https://github.com/defunkt/hub/pull/73/files"
833
+ end
834
+
835
+ def test_checkout_pullrequest_custom_branch
836
+ stub_request(:get, "http://github.com/api/v2/json/pulls/defunkt/hub/73").
837
+ to_return(:body => mock_pull_response('blueyed:feature'))
838
+
839
+ assert_commands 'git remote add -f -t feature blueyed git://github.com/blueyed/hub.git',
840
+ 'git checkout -b review blueyed/feature',
841
+ "checkout https://github.com/defunkt/hub/pull/73/files review"
842
+ end
843
+
844
+ def test_checkout_pullrequest_existing_remote
845
+ stub_command_output 'remote', "origin\nblueyed"
846
+
847
+ stub_request(:get, "http://github.com/api/v2/json/pulls/defunkt/hub/73").
848
+ to_return(:body => mock_pull_response('blueyed:feature'))
849
+
850
+ assert_commands 'git remote set-branches --add blueyed feature',
851
+ 'git fetch blueyed +refs/heads/feature:refs/remotes/blueyed/feature',
852
+ 'git checkout -b blueyed-feature blueyed/feature',
853
+ "checkout https://github.com/defunkt/hub/pull/73/files"
854
+ end
855
+
856
+ def test_version
857
+ out = hub('--version')
858
+ assert_includes "git version 1.7.0.4", out
859
+ assert_includes "hub version #{Hub::Version}", out
860
+ end
861
+
862
+ def test_exec_path
863
+ out = hub('--exec-path')
864
+ assert_equal "/usr/lib/git-core\n", out
865
+ end
866
+
867
+ def test_exec_path_arg
868
+ out = hub('--exec-path=/home/wombat/share/my-l33t-git-core')
869
+ assert_equal improved_help_text, out
870
+ end
871
+
872
+ def test_html_path
873
+ out = hub('--html-path')
874
+ assert_equal "/usr/share/doc/git-doc\n", out
875
+ end
876
+
877
+ def test_help
878
+ assert_equal improved_help_text, hub("help")
879
+ end
880
+
881
+ def test_help_by_default
882
+ assert_equal improved_help_text, hub("")
883
+ end
884
+
885
+ def test_help_with_pager
886
+ assert_equal improved_help_text, hub("-p")
887
+ end
888
+
889
+ def test_help_hub
890
+ help_manpage = hub("help hub")
891
+ assert_includes "git + hub = github", help_manpage
892
+ assert_includes <<-config, help_manpage
893
+ Use git-config(1) to display the currently configured GitHub username:
894
+ config
895
+ end
896
+
897
+ def test_help_hub_no_groff
898
+ stub_available_commands()
899
+ assert_equal "** Can't find groff(1)\n", hub("help hub")
900
+ end
901
+
902
+ def test_hub_standalone
903
+ help_standalone = hub("hub standalone")
904
+ assert_equal Hub::Standalone.build, help_standalone
905
+ end
906
+
907
+ def test_hub_compare
908
+ assert_command "compare refactor",
909
+ "open https://github.com/defunkt/hub/compare/refactor"
910
+ end
911
+
912
+ def test_hub_compare_nothing
913
+ expected = "Usage: hub compare [USER] [<START>...]<END>\n"
914
+ assert_equal expected, hub("compare")
915
+ end
916
+
917
+ def test_hub_compare_tracking_nothing
918
+ stub_tracking_nothing
919
+ expected = "Usage: hub compare [USER] [<START>...]<END>\n"
920
+ assert_equal expected, hub("compare")
921
+ end
922
+
923
+ def test_hub_compare_tracking_branch
924
+ stub_branch('refs/heads/feature')
925
+ stub_tracking('feature', 'mislav', 'experimental')
926
+
927
+ assert_command "compare",
928
+ "open https://github.com/mislav/hub/compare/experimental"
929
+ end
930
+
931
+ def test_hub_compare_range
932
+ assert_command "compare 1.0...fix",
933
+ "open https://github.com/defunkt/hub/compare/1.0...fix"
934
+ end
935
+
936
+ def test_hub_compare_range_fixes_two_dots_for_tags
937
+ assert_command "compare 1.0..fix",
938
+ "open https://github.com/defunkt/hub/compare/1.0...fix"
939
+ end
940
+
941
+ def test_hub_compare_range_fixes_two_dots_for_shas
942
+ assert_command "compare 1234abc..3456cde",
943
+ "open https://github.com/defunkt/hub/compare/1234abc...3456cde"
944
+ end
945
+
946
+ def test_hub_compare_range_ignores_two_dots_for_complex_ranges
947
+ assert_command "compare @{a..b}..@{c..d}",
948
+ "open https://github.com/defunkt/hub/compare/@{a..b}..@{c..d}"
949
+ end
950
+
951
+ def test_hub_compare_on_wiki
952
+ stub_repo_url 'git://github.com/defunkt/hub.wiki.git'
953
+ assert_command "compare 1.0...fix",
954
+ "open https://github.com/defunkt/hub/wiki/_compare/1.0...fix"
955
+ end
956
+
957
+ def test_hub_compare_fork
958
+ assert_command "compare myfork feature",
959
+ "open https://github.com/myfork/hub/compare/feature"
960
+ end
961
+
962
+ def test_hub_compare_url
963
+ assert_command "compare -u 1.0...1.1",
964
+ "echo https://github.com/defunkt/hub/compare/1.0...1.1"
965
+ end
966
+
967
+ def test_hub_browse
968
+ assert_command "browse mojombo/bert", "open https://github.com/mojombo/bert"
969
+ end
970
+
971
+ def test_hub_browse_commit
972
+ assert_command "browse mojombo/bert commit/5d5582", "open https://github.com/mojombo/bert/commit/5d5582"
973
+ end
974
+
975
+ def test_hub_browse_tracking_nothing
976
+ stub_tracking_nothing
977
+ assert_command "browse mojombo/bert", "open https://github.com/mojombo/bert"
978
+ end
979
+
980
+ def test_hub_browse_url
981
+ assert_command "browse -u mojombo/bert", "echo https://github.com/mojombo/bert"
982
+ end
983
+
984
+ def test_hub_browse_self
985
+ assert_command "browse resque", "open https://github.com/tpw/resque"
986
+ end
987
+
988
+ def test_hub_browse_subpage
989
+ assert_command "browse resque commits",
990
+ "open https://github.com/tpw/resque/commits/master"
991
+ assert_command "browse resque issues",
992
+ "open https://github.com/tpw/resque/issues"
993
+ assert_command "browse resque wiki",
994
+ "open https://github.com/tpw/resque/wiki"
995
+ end
996
+
997
+ def test_hub_browse_on_branch
998
+ stub_branch('refs/heads/feature')
999
+ stub_tracking('feature', 'mislav', 'experimental')
1000
+
1001
+ assert_command "browse resque", "open https://github.com/tpw/resque"
1002
+ assert_command "browse resque commits",
1003
+ "open https://github.com/tpw/resque/commits/master"
1004
+
1005
+ assert_command "browse",
1006
+ "open https://github.com/mislav/hub/tree/experimental"
1007
+ assert_command "browse -- tree",
1008
+ "open https://github.com/mislav/hub/tree/experimental"
1009
+ assert_command "browse -- commits",
1010
+ "open https://github.com/mislav/hub/commits/experimental"
1011
+ end
1012
+
1013
+ def test_hub_browse_current
1014
+ assert_command "browse", "open https://github.com/defunkt/hub"
1015
+ assert_command "browse --", "open https://github.com/defunkt/hub"
1016
+ end
1017
+
1018
+ def test_hub_browse_commit_from_current
1019
+ assert_command "browse -- commit/6616e4", "open https://github.com/defunkt/hub/commit/6616e4"
1020
+ end
1021
+
1022
+ def test_hub_browse_no_tracking
1023
+ stub_tracking_nothing
1024
+ assert_command "browse", "open https://github.com/defunkt/hub"
1025
+ end
1026
+
1027
+ def test_hub_browse_no_tracking_on_branch
1028
+ stub_branch('refs/heads/feature')
1029
+ stub_tracking_nothing('feature')
1030
+ assert_command "browse", "open https://github.com/defunkt/hub"
1031
+ end
1032
+
1033
+ def test_hub_browse_current_wiki
1034
+ stub_repo_url 'git://github.com/defunkt/hub.wiki.git'
1035
+
1036
+ assert_command "browse", "open https://github.com/defunkt/hub/wiki"
1037
+ assert_command "browse -- wiki", "open https://github.com/defunkt/hub/wiki"
1038
+ assert_command "browse -- commits", "open https://github.com/defunkt/hub/wiki/_history"
1039
+ assert_command "browse -- pages", "open https://github.com/defunkt/hub/wiki/_pages"
1040
+ end
1041
+
1042
+ def test_hub_browse_current_subpage
1043
+ assert_command "browse -- network",
1044
+ "open https://github.com/defunkt/hub/network"
1045
+ assert_command "browse -- anything/everything",
1046
+ "open https://github.com/defunkt/hub/anything/everything"
1047
+ end
1048
+
1049
+ def test_hub_browse_deprecated_private
1050
+ with_browser_env('echo') do
1051
+ assert_includes "Warning: the `-p` flag has no effect anymore\n", hub("browse -p defunkt/hub")
1052
+ end
1053
+ end
1054
+
1055
+ def test_hub_browse_no_repo
1056
+ stub_repo_url(nil)
1057
+ assert_equal "Usage: hub browse [<USER>/]<REPOSITORY>\n", hub("browse")
1058
+ end
1059
+
1060
+ def test_custom_browser
1061
+ with_browser_env("custom") do
1062
+ assert_browser("custom")
1063
+ end
1064
+ end
1065
+
1066
+ def test_linux_browser
1067
+ stub_available_commands "open", "xdg-open", "cygstart"
1068
+ with_browser_env(nil) do
1069
+ with_host_os("i686-linux") do
1070
+ assert_browser("xdg-open")
1071
+ end
1072
+ end
1073
+ end
1074
+
1075
+ def test_cygwin_browser
1076
+ stub_available_commands "open", "cygstart"
1077
+ with_browser_env(nil) do
1078
+ with_host_os("i686-linux") do
1079
+ assert_browser("cygstart")
1080
+ end
1081
+ end
1082
+ end
1083
+
1084
+ def test_no_browser
1085
+ stub_available_commands()
1086
+ expected = "Please set $BROWSER to a web launcher to use this command.\n"
1087
+ with_browser_env(nil) do
1088
+ with_host_os("i686-linux") do
1089
+ assert_equal expected, hub("browse")
1090
+ end
1091
+ end
1092
+ end
1093
+
1094
+ def test_context_method_doesnt_hijack_git_command
1095
+ assert_forwarded 'remotes'
1096
+ end
1097
+
1098
+ def test_not_choking_on_ruby_methods
1099
+ assert_forwarded 'id'
1100
+ assert_forwarded 'name'
1101
+ end
1102
+
1103
+ def test_multiple_remote_urls
1104
+ stub_repo_url("git://example.com/other.git\ngit://github.com/my/repo.git")
1105
+ assert_command "browse", "open https://github.com/my/repo"
1106
+ end
1107
+
1108
+ def test_global_flags_preserved
1109
+ cmd = '--no-pager --bare -c core.awesome=true -c name=value --git-dir=/srv/www perform'
1110
+ assert_command cmd, 'git --bare -c core.awesome=true -c name=value --git-dir=/srv/www --no-pager perform'
1111
+ assert_equal %w[git --bare -c core.awesome=true -c name=value --git-dir=/srv/www], git_reader.executable
1112
+ end
1113
+
1114
+ protected
1115
+
1116
+ def stub_github_user(name)
1117
+ stub_config_value 'github.user', name
1118
+ end
1119
+
1120
+ def stub_github_token(token)
1121
+ stub_config_value 'github.token', token
1122
+ end
1123
+
1124
+ def stub_repo_url(value, remote_name = 'origin')
1125
+ stub_config_value "remote.#{remote_name}.url", value, '--get-all'
1126
+ end
1127
+
1128
+ def stub_branch(value)
1129
+ stub_command_output 'symbolic-ref -q HEAD', value
1130
+ end
1131
+
1132
+ def stub_tracking(from, remote_name, remote_branch)
1133
+ stub_command_output "rev-parse --symbolic-full-name #{from}@{upstream}",
1134
+ remote_branch ? "refs/remotes/#{remote_name}/#{remote_branch}" : nil
1135
+ end
1136
+
1137
+ def stub_tracking_nothing(from = 'master')
1138
+ stub_tracking(from, nil, nil)
1139
+ end
1140
+
1141
+ def stub_remotes_group(name, value)
1142
+ stub_config_value "remotes.#{name}", value
1143
+ end
1144
+
1145
+ def stub_no_remotes
1146
+ stub_command_output 'remote', nil
1147
+ end
1148
+
1149
+ def stub_no_git_repo
1150
+ stub_command_output 'rev-parse -q --git-dir', nil
1151
+ end
1152
+
1153
+ def stub_alias(name, value)
1154
+ stub_config_value "alias.#{name}", value
1155
+ end
1156
+
1157
+ def stub_existing_fork(user, repo = 'hub')
1158
+ stub_fork(user, repo, 200)
1159
+ end
1160
+
1161
+ def stub_nonexisting_fork(user, repo = 'hub')
1162
+ stub_fork(user, repo, 404)
1163
+ end
1164
+
1165
+ def stub_fork(user, repo, status)
1166
+ stub_request(:get, "github.com/api/v2/yaml/repos/show/#{user}/#{repo}").
1167
+ to_return(:status => status)
1168
+ end
1169
+
1170
+ def stub_available_commands(*names)
1171
+ COMMANDS.replace names
1172
+ end
1173
+
1174
+ def stub_https_is_preferred
1175
+ stub_config_value 'hub.protocol', 'https'
1176
+ end
1177
+
1178
+ def with_browser_env(value)
1179
+ browser, ENV['BROWSER'] = ENV['BROWSER'], value
1180
+ yield
1181
+ ensure
1182
+ ENV['BROWSER'] = browser
1183
+ end
1184
+
1185
+ def with_tmpdir(value)
1186
+ dir, ENV['TMPDIR'] = ENV['TMPDIR'], value
1187
+ yield
1188
+ ensure
1189
+ ENV['TMPDIR'] = dir
1190
+ end
1191
+
1192
+ def assert_browser(browser)
1193
+ assert_command "browse", "#{browser} https://github.com/defunkt/hub"
1194
+ end
1195
+
1196
+ def with_host_os(value)
1197
+ host_os = RbConfig::CONFIG['host_os']
1198
+ RbConfig::CONFIG['host_os'] = value
1199
+ begin
1200
+ yield
1201
+ ensure
1202
+ RbConfig::CONFIG['host_os'] = host_os
1203
+ end
1204
+ end
1205
+
1206
+ def auth(user = git_config('github.user'), password = git_config('github.token'))
1207
+ "#{user}%2Ftoken:#{password}@"
1208
+ end
1209
+
1210
+ def mock_pullreq_response(id, name_with_owner = 'defunkt/hub')
1211
+ YAML.dump('pull' => {
1212
+ 'html_url' => "https://github.com/#{name_with_owner}/pull/#{id}"
1213
+ })
1214
+ end
1215
+
1216
+ def mock_pull_response(label)
1217
+ JSON.generate('pull' => { 'head' => {'label' => label} })
1218
+ end
1219
+
1220
+ def improved_help_text
1221
+ Hub::Commands.send :improved_help_text
1222
+ end
1223
+
1224
+ end