hubflow 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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