hub 1.10.6 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of hub might be problematic. Click here for more details.
- data/README.md +30 -62
- data/Rakefile +24 -15
- data/bin/bench +37 -0
- data/lib/hub.rb +1 -0
- data/lib/hub/commands.rb +178 -57
- data/lib/hub/context.rb +63 -20
- data/lib/hub/github_api.rb +193 -71
- data/lib/hub/speedy_stdlib.rb +107 -0
- data/lib/hub/ssh_config.rb +1 -1
- data/lib/hub/standalone.rb +31 -3
- data/lib/hub/version.rb +1 -1
- data/man/hub.1 +46 -23
- data/man/hub.1.html +46 -29
- data/man/hub.1.ronn +30 -15
- data/script/cached-bundle +46 -0
- data/script/s3-put +71 -0
- data/script/test +41 -0
- data/script/test_each +9 -0
- data/test/context_test.rb +79 -0
- data/test/fakebin/git +1 -1
- data/test/fakebin/open +2 -2
- data/test/github_api_test.rb +79 -0
- data/test/helper.rb +2 -2
- data/test/hub_test.rb +85 -197
- data/test/standalone_test.rb +6 -2
- metadata +22 -15
- data/HISTORY.md +0 -244
data/man/hub.1.ronn
CHANGED
@@ -25,9 +25,10 @@ hub(1) -- git + hub = github
|
|
25
25
|
|
26
26
|
`git create` [<NAME>] [`-p`] [`-d` <DESCRIPTION>] [`-h` <HOMEPAGE>]
|
27
27
|
`git browse` [`-u`] [[<USER>`/`]<REPOSITORY>] [SUBPAGE]
|
28
|
-
`git compare` [`-u`] [<USER>] [<START>...]<END>
|
28
|
+
`git compare` [`-u`] [<USER>] [[<START>...]<END>]
|
29
29
|
`git fork` [`--no-remote`]
|
30
|
-
`git pull-request` [`-f`] [<
|
30
|
+
`git pull-request` [`-f`] [`-m` <MESSAGE>|`-F` <FILE>|`-i` <ISSUE>|<ISSUE-URL>] [`-b` <BASE>] [`-h` <HEAD>]
|
31
|
+
`git ci-status` [`-v`] [<COMMIT>]
|
31
32
|
|
32
33
|
## DESCRIPTION
|
33
34
|
|
@@ -50,8 +51,12 @@ hub enhances various git commands to ease most common workflows with GitHub.
|
|
50
51
|
* `git clone` [`-p`] <OPTIONS> [<USER>`/`]<REPOSITORY> <DIRECTORY>:
|
51
52
|
Clone repository "git://github.com/<USER>/<REPOSITORY>.git" into
|
52
53
|
<DIRECTORY> as with git-clone(1). When <USER>/ is omitted, assumes
|
53
|
-
your GitHub login.
|
54
|
-
|
54
|
+
your GitHub login.
|
55
|
+
|
56
|
+
If the repository is private or the current user has push access to the
|
57
|
+
repository, hub will use the ssh protocol for cloning. Use `-p` to select
|
58
|
+
the ssh protocol unconditionally. HTTPS protocol can be used instead by
|
59
|
+
setting "hub.protocol" (see <CONFIGURATION>).
|
55
60
|
|
56
61
|
* `git remote add` [`-p`] <OPTIONS> <USER>[`/`<REPOSITORY>]:
|
57
62
|
Add remote "git://github.com/<USER>/<REPOSITORY>.git" as with
|
@@ -122,20 +127,22 @@ hub also adds some custom commands that are otherwise not present in git:
|
|
122
127
|
specified, `browse` opens the page of the repository found in the current
|
123
128
|
directory. If SUBPAGE is specified, the browser will open on the specified
|
124
129
|
subpage: one of "wiki", "commits", "issues" or other (the default is
|
125
|
-
"tree").
|
130
|
+
"tree"). With `-u`, outputs the URL rather than opening the browser.
|
126
131
|
|
127
|
-
* `git compare` [`-u`] [<USER>] [<START>...]<END
|
132
|
+
* `git compare` [`-u`] [<USER>] [[<START>...]<END>]:
|
128
133
|
Open a GitHub compare view page in the system's default web browser.
|
129
134
|
<START> to <END> are branch names, tag names, or commit SHA1s specifying
|
130
135
|
the range of history to compare. If a range with two dots (`a..b`) is given,
|
131
136
|
it will be transformed into one with three dots. If <START> is omitted,
|
132
137
|
GitHub will compare against the base branch (the default is "master").
|
138
|
+
If <END> is omitted, GitHub compare view is opened for the current branch.
|
139
|
+
With `-u`, outputs the URL rather than opening the browser.
|
133
140
|
|
134
141
|
* `git fork` [`--no-remote`]:
|
135
142
|
Forks the original project (referenced by "origin" remote) on GitHub and
|
136
143
|
adds a new remote for it under your username.
|
137
144
|
|
138
|
-
* `git pull-request` [`-f`] [<
|
145
|
+
* `git pull-request` [`-f`] [`-m` <MESSAGE>|`-F` <FILE>|`-i` <ISSUE>|<ISSUE-URL>] [`-b` <BASE>] [`-h` <HEAD>]:
|
139
146
|
Opens a pull request on GitHub for the project that the "origin" remote
|
140
147
|
points to. The default head of the pull request is the current branch.
|
141
148
|
Both base and head of the pull request can be explicitly given in one of
|
@@ -144,12 +151,20 @@ hub also adds some custom commands that are otherwise not present in git:
|
|
144
151
|
branch has local commits that are not yet pushed to its upstream branch
|
145
152
|
on the remote. To skip this check, use `-f`.
|
146
153
|
|
147
|
-
|
148
|
-
the pull request can be entered in the same manner as git commit message.
|
154
|
+
Without <MESSAGE> or <FILE>, a text editor will open in which title and body
|
155
|
+
of the pull request can be entered in the same manner as git commit message.
|
156
|
+
Pull request message can also be passed via stdin with `-F -`.
|
157
|
+
|
158
|
+
Issue to pull request conversion via `-i <ISSUE>` or <ISSUE-URL>
|
159
|
+
arguments is deprecated and will likely be removed from the future versions
|
160
|
+
of both hub and GitHub API.
|
161
|
+
|
162
|
+
* `git ci-status` [`-v`] [<COMMIT>]:
|
163
|
+
Looks up the SHA for <COMMIT> in GitHub Status API and displays the latest
|
164
|
+
status. Exits with one of:
|
165
|
+
success (0), error (1), failure (1), pending (2), no status (3)
|
149
166
|
|
150
|
-
If
|
151
|
-
request will be attached to an existing GitHub issue. Alternatively, instead
|
152
|
-
of title you can paste a full URL to an issue on GitHub.
|
167
|
+
If `-v` is given, additionally print the URL to CI build results.
|
153
168
|
|
154
169
|
## CONFIGURATION
|
155
170
|
|
@@ -186,14 +201,14 @@ variable:
|
|
186
201
|
|
187
202
|
## BUGS
|
188
203
|
|
189
|
-
<https://github.com/
|
204
|
+
<https://github.com/github/hub/issues>
|
190
205
|
|
191
206
|
## AUTHORS
|
192
207
|
|
193
|
-
<https://github.com/
|
208
|
+
<https://github.com/github/hub/contributors>
|
194
209
|
|
195
210
|
## SEE ALSO
|
196
211
|
|
197
212
|
git(1), git-clone(1), git-remote(1), git-init(1),
|
198
213
|
<http://github.com>,
|
199
|
-
<https://github.com/
|
214
|
+
<https://github.com/github/hub>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
# Usage: cached-bundle install --deployment
|
3
|
+
#
|
4
|
+
# After running `bundle`, caches the `vendor/bundle` directory to S3.
|
5
|
+
# On the next run, restores the cached directory before running `bundle`.
|
6
|
+
# When `Gemfile.lock` changes, the cache gets rebuilt.
|
7
|
+
#
|
8
|
+
# Requirements:
|
9
|
+
# - Gemfile.lock
|
10
|
+
# - TRAVIS_REPO_SLUG
|
11
|
+
# - TRAVIS_RUBY_VERSION
|
12
|
+
# - AMAZON_S3_BUCKET
|
13
|
+
# - script/s3-put
|
14
|
+
# - bundle
|
15
|
+
# - curl
|
16
|
+
#
|
17
|
+
# Author: Mislav Marohnić
|
18
|
+
|
19
|
+
set -e
|
20
|
+
|
21
|
+
compute_md5() {
|
22
|
+
local output="$(openssl md5)"
|
23
|
+
echo "${output##* }"
|
24
|
+
}
|
25
|
+
|
26
|
+
download() {
|
27
|
+
curl --tcp-nodelay -qsfL "$1" -o "$2"
|
28
|
+
}
|
29
|
+
|
30
|
+
bundle_path="vendor/bundle"
|
31
|
+
gemfile_hash="$(compute_md5 <"${BUNDLE_GEMFILE:-Gemfile}.lock")"
|
32
|
+
cache_name="${TRAVIS_RUBY_VERSION}-${gemfile_hash}.tgz"
|
33
|
+
fetch_url="http://${AMAZON_S3_BUCKET}.s3.amazonaws.com/${TRAVIS_REPO_SLUG}/${cache_name}"
|
34
|
+
|
35
|
+
if download "$fetch_url" "$cache_name"; then
|
36
|
+
echo "Reusing cached bundle ${cache_name}"
|
37
|
+
tar xzf "$cache_name"
|
38
|
+
fi
|
39
|
+
|
40
|
+
bundle "$@"
|
41
|
+
|
42
|
+
if [ ! -f "$cache_name" ]; then
|
43
|
+
echo "Caching \`${bundle_path}' to S3"
|
44
|
+
tar czf "$cache_name" "$bundle_path"
|
45
|
+
script/s3-put "$cache_name" "${AMAZON_S3_BUCKET}:${TRAVIS_REPO_SLUG}/${cache_name}"
|
46
|
+
fi
|
data/script/s3-put
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
# Usage: s3-put <FILE> <S3_BUCKET>[:<PATH>] [<CONTENT_TYPE>]
|
3
|
+
#
|
4
|
+
# Uploads a file to the Amazon S3 service.
|
5
|
+
# Outputs the URL for the newly uploaded file.
|
6
|
+
#
|
7
|
+
# Requirements:
|
8
|
+
# - AMAZON_ACCESS_KEY_ID
|
9
|
+
# - AMAZON_SECRET_ACCESS_KEY
|
10
|
+
# - openssl
|
11
|
+
# - curl
|
12
|
+
#
|
13
|
+
# Author: Mislav Marohnić
|
14
|
+
|
15
|
+
set -e
|
16
|
+
|
17
|
+
authorization() {
|
18
|
+
local signature="$(string_to_sign | hmac_sha1 | base64)"
|
19
|
+
echo "AWS ${AMAZON_ACCESS_KEY_ID?}:${signature}"
|
20
|
+
}
|
21
|
+
|
22
|
+
hmac_sha1() {
|
23
|
+
openssl dgst -binary -sha1 -hmac "${AMAZON_SECRET_ACCESS_KEY?}"
|
24
|
+
}
|
25
|
+
|
26
|
+
base64() {
|
27
|
+
openssl enc -base64
|
28
|
+
}
|
29
|
+
|
30
|
+
bin_md5() {
|
31
|
+
openssl dgst -binary -md5
|
32
|
+
}
|
33
|
+
|
34
|
+
string_to_sign() {
|
35
|
+
echo "$http_method"
|
36
|
+
echo "$content_md5"
|
37
|
+
echo "$content_type"
|
38
|
+
echo "$date"
|
39
|
+
echo "x-amz-acl:$acl"
|
40
|
+
printf "/$bucket/$remote_path"
|
41
|
+
}
|
42
|
+
|
43
|
+
date_string() {
|
44
|
+
LC_TIME=C date "+%a, %d %h %Y %T %z"
|
45
|
+
}
|
46
|
+
|
47
|
+
file="$1"
|
48
|
+
bucket="${2%%:*}"
|
49
|
+
remote_path="${2#*:}"
|
50
|
+
content_type="$3"
|
51
|
+
|
52
|
+
if [ -z "$remote_path" ] || [ "$remote_path" = "$bucket" ]; then
|
53
|
+
remote_path="${file##*/}"
|
54
|
+
fi
|
55
|
+
|
56
|
+
http_method=PUT
|
57
|
+
acl="public-read"
|
58
|
+
content_md5="$(bin_md5 < "$file" | base64)"
|
59
|
+
date="$(date_string)"
|
60
|
+
|
61
|
+
url="https://$bucket.s3.amazonaws.com/$remote_path"
|
62
|
+
|
63
|
+
curl -qsSf -T "$file" \
|
64
|
+
-H "Authorization: $(authorization)" \
|
65
|
+
-H "x-amz-acl: $acl" \
|
66
|
+
-H "Date: $date" \
|
67
|
+
-H "Content-MD5: $content_md5" \
|
68
|
+
-H "Content-Type: $content_type" \
|
69
|
+
"$url"
|
70
|
+
|
71
|
+
echo "$url"
|
data/script/test
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
set -e
|
3
|
+
|
4
|
+
STATUS=0
|
5
|
+
warnings="${TMPDIR:-/tmp}/hub-warnings.$$"
|
6
|
+
|
7
|
+
run() {
|
8
|
+
# Save warnings on stderr to a separate file
|
9
|
+
RUBYOPT="$RUBYOPT -w" bundle exec "$@" \
|
10
|
+
2> >(tee >(grep 'warning:' >>"$warnings") | grep -v 'warning:') || STATUS=$?
|
11
|
+
}
|
12
|
+
|
13
|
+
check_warnings() {
|
14
|
+
# Display Ruby warnings from this project's source files. Abort if any were found.
|
15
|
+
num="$(grep -F "$PWD" "$warnings" | grep -v "${PWD}/vendor/bundle" | sort | uniq -c | sort -rn | tee /dev/stderr | wc -l)"
|
16
|
+
rm -f "$warnings"
|
17
|
+
if [ "$num" -gt 0 ]; then
|
18
|
+
echo "FAILED: this test suite doesn't tolerate Ruby syntax warnings!" >&2
|
19
|
+
exit 1
|
20
|
+
fi
|
21
|
+
}
|
22
|
+
|
23
|
+
if tmux -V; then
|
24
|
+
if [ -n "$CI" ]; then
|
25
|
+
git --version
|
26
|
+
bash --version | head -1
|
27
|
+
zsh --version
|
28
|
+
echo
|
29
|
+
fi
|
30
|
+
profile="all"
|
31
|
+
else
|
32
|
+
echo "warning: skipping shell completion tests (install tmux to enable)" >&2
|
33
|
+
profile="default"
|
34
|
+
fi
|
35
|
+
|
36
|
+
run rake test
|
37
|
+
run cucumber -p "$profile"
|
38
|
+
|
39
|
+
check_warnings
|
40
|
+
|
41
|
+
exit $STATUS
|
data/script/test_each
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ContextTest < Minitest::Test
|
4
|
+
class Context
|
5
|
+
include Hub::Context
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
@git_reader = Hub::Context::GitReader.new('git', &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
public :git_editor
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :context
|
15
|
+
|
16
|
+
def setup
|
17
|
+
super
|
18
|
+
@stubs = {}
|
19
|
+
@context = Context.new do |_, cmd|
|
20
|
+
@stubs.fetch(cmd)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_editor
|
25
|
+
stub_command_output 'var GIT_EDITOR', 'vim'
|
26
|
+
assert_equal %w'vim', context.git_editor
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_editor_with_argument
|
30
|
+
stub_command_output 'var GIT_EDITOR', 'subl -w'
|
31
|
+
assert_equal %w'subl -w', context.git_editor
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_editor_with_spaces
|
35
|
+
stub_command_output 'var GIT_EDITOR', '"my editor" -w arg2'
|
36
|
+
assert_equal %w'my\ editor -w arg2', context.git_editor
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_editor_with_tilde
|
40
|
+
stub_command_output 'var GIT_EDITOR', '~/bin/vi'
|
41
|
+
with_env('HOME', '/home/mislav') do
|
42
|
+
assert_equal %w'/home/mislav/bin/vi', context.git_editor
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_editor_with_env_variable
|
47
|
+
stub_command_output 'var GIT_EDITOR', '$EDITOR'
|
48
|
+
with_env('EDITOR', 'subl -w') do
|
49
|
+
assert_equal %w'subl -w', context.git_editor
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_editor_with_embedded_env_variable
|
54
|
+
stub_command_output 'var GIT_EDITOR', '$EDITOR -w'
|
55
|
+
with_env('EDITOR', 'subl') do
|
56
|
+
assert_equal %w'subl -w', context.git_editor
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_editor_with_curly_brackets_embedded_env_variable
|
61
|
+
stub_command_output 'var GIT_EDITOR', 'my${EDITOR}2 -w'
|
62
|
+
with_env('EDITOR', 'subl') do
|
63
|
+
assert_equal %w'mysubl2 -w', context.git_editor
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def stub_command_output(cmd, value)
|
70
|
+
@stubs[cmd] = value.nil? ? nil : value.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def with_env(name, value)
|
74
|
+
dir, ENV[name] = ENV[name], value
|
75
|
+
yield
|
76
|
+
ensure
|
77
|
+
ENV[name] = dir
|
78
|
+
end
|
79
|
+
end
|
data/test/fakebin/git
CHANGED
data/test/fakebin/open
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
#!/bin/sh
|
2
|
-
echo "ERROR: open was called, but wasn't supposed to:" open $*
|
3
|
-
exit 1
|
2
|
+
echo "ERROR: open was called, but wasn't supposed to:" open $* >&2
|
3
|
+
exit 1
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'hub/github_api'
|
3
|
+
require 'forwardable'
|
4
|
+
require 'delegate'
|
5
|
+
|
6
|
+
class FileStoreTest < Minitest::Test
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@store, :yaml_dump, :yaml_load
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@store = Hub::GitHubAPI::FileStore.new('')
|
12
|
+
end
|
13
|
+
|
14
|
+
class OrderedHash < DelegateClass(::Hash)
|
15
|
+
def self.[](*args)
|
16
|
+
hash = new
|
17
|
+
while args.any?
|
18
|
+
key, value = args.shift, args.shift
|
19
|
+
hash[key] = value
|
20
|
+
end
|
21
|
+
hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(hash = {})
|
25
|
+
@keys = hash.keys
|
26
|
+
super(hash)
|
27
|
+
end
|
28
|
+
|
29
|
+
def []=(key, value) @keys << key; super end
|
30
|
+
|
31
|
+
def each
|
32
|
+
@keys.each { |key| yield(key, self[key]) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_yaml_dump
|
37
|
+
output = yaml_dump("github.com" => [
|
38
|
+
OrderedHash['user', 'mislav', 'oauth_token', 'OTOKEN'],
|
39
|
+
OrderedHash['user', 'tpw', 'oauth_token', 'POKEN'],
|
40
|
+
])
|
41
|
+
|
42
|
+
assert_equal <<-YAML.chomp, output
|
43
|
+
---
|
44
|
+
github.com:
|
45
|
+
- user: mislav
|
46
|
+
oauth_token: OTOKEN
|
47
|
+
- user: tpw
|
48
|
+
oauth_token: POKEN
|
49
|
+
YAML
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_yaml_load
|
53
|
+
data = yaml_load <<-YAML
|
54
|
+
---
|
55
|
+
github.com:
|
56
|
+
- user: mislav
|
57
|
+
oauth_token: OTOKEN
|
58
|
+
- user: tpw
|
59
|
+
oauth_token: POKEN
|
60
|
+
YAML
|
61
|
+
|
62
|
+
assert_equal 'mislav', data['github.com'][0]['user']
|
63
|
+
assert_equal 'OTOKEN', data['github.com'][0]['oauth_token']
|
64
|
+
assert_equal 'tpw', data['github.com'][1]['user']
|
65
|
+
assert_equal 'POKEN', data['github.com'][1]['oauth_token']
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_yaml_load_quoted
|
69
|
+
data = yaml_load <<-YAML
|
70
|
+
---
|
71
|
+
github.com:
|
72
|
+
- user: 'true'
|
73
|
+
oauth_token: '1234'
|
74
|
+
YAML
|
75
|
+
|
76
|
+
assert_equal 'true', data['github.com'][0]['user']
|
77
|
+
assert_equal '1234', data['github.com'][0]['oauth_token']
|
78
|
+
end
|
79
|
+
end
|
data/test/helper.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'minitest/autorun'
|
2
2
|
require 'hub'
|
3
3
|
|
4
4
|
# We're checking for `open` in our tests
|
@@ -28,7 +28,7 @@ Hub::Commands.extend Module.new {
|
|
28
28
|
end
|
29
29
|
}
|
30
30
|
|
31
|
-
class Test
|
31
|
+
class Minitest::Test
|
32
32
|
# Shortcut for creating a `Hub` instance. Pass it what you would
|
33
33
|
# normally pass `hub` on the command line, e.g.
|
34
34
|
#
|