Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge remote-tracking branch 'upstream/master' into get-pull-request

  • Loading branch information...
commit 6cb932ce46e308ba8805cb06e635ba07cb6d39ae 2 parents db76936 + 6976010
@jianli authored
Showing with 1,318 additions and 460 deletions.
  1. +0 −1  .gitignore
  2. +3 −2 Gemfile
  3. +84 −0 Gemfile.lock
  4. +36 −0 HISTORY.md
  5. +55 −11 README.md
  6. +4 −4 Rakefile
  7. +40 −3 features/authentication.feature
  8. +8 −2 features/browse.feature
  9. +80 −0 features/checkout.feature
  10. +53 −0 features/ci_status.feature
  11. +66 −0 features/compare.feature
  12. +41 −12 features/create.feature
  13. +10 −0 features/fetch.feature
  14. +16 −9 features/fork.feature
  15. +1 −1  features/merge.feature
  16. +366 −16 features/pull_request.feature
  17. +57 −0 features/steps.rb
  18. +6 −2 features/submodule_add.feature
  19. +28 −13 features/support/env.rb
  20. +16 −0 features/support/local_server.rb
  21. +1 −0  hub.gemspec
  22. +1 −1  lib/hub.rb
  23. +0 −1  lib/hub/args.rb
  24. +119 −32 lib/hub/commands.rb
  25. +10 −2 lib/hub/context.rb
  26. +67 −31 lib/hub/github_api.rb
  27. +16 −4 lib/hub/json.rb
  28. +11 −7 lib/hub/runner.rb
  29. +1 −1  lib/hub/version.rb
  30. +52 −20 man/hub.1
  31. +44 −19 man/hub.1.html
  32. +14 −4 man/hub.1.ronn
  33. +2 −1  test/helper.rb
  34. +10 −261 test/hub_test.rb
View
1  .gitignore
@@ -1,5 +1,4 @@
*.swp
*~
/hub
-Gemfile.lock
tmp/
View
5 Gemfile
@@ -1,10 +1,11 @@
source 'https://rubygems.org'
-gem 'ronn'
+gem 'ronn', :platform => :mri
gem 'aruba'
gem 'cucumber'
gem 'sinatra'
-gem 'thin'
+gem 'thin', :platform => :mri
gem 'json'
+gem 'jruby-openssl', :platform => :jruby
gemspec
View
84 Gemfile.lock
@@ -0,0 +1,84 @@
+PATH
+ remote: .
+ specs:
+ hub (1.10.6)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ addressable (2.3.2)
+ aruba (0.4.11)
+ childprocess (>= 0.2.3)
+ cucumber (>= 1.1.1)
+ ffi (>= 1.0.11)
+ rspec (>= 2.7.0)
+ bouncy-castle-java (1.5.0146.1)
+ builder (3.0.0)
+ childprocess (0.3.3)
+ ffi (~> 1.0.6)
+ crack (0.3.1)
+ cucumber (1.2.1)
+ builder (>= 2.1.2)
+ diff-lcs (>= 1.1.3)
+ gherkin (~> 2.11.0)
+ json (>= 1.4.6)
+ daemons (1.1.8)
+ diff-lcs (1.1.3)
+ eventmachine (0.12.10)
+ ffi (1.0.11)
+ ffi (1.0.11-java)
+ gherkin (2.11.1)
+ json (>= 1.4.6)
+ gherkin (2.11.1-java)
+ json (>= 1.4.6)
+ hpricot (0.8.4)
+ jruby-openssl (0.7.7)
+ bouncy-castle-java (>= 1.5.0146.1)
+ json (1.7.3)
+ json (1.7.3-java)
+ mustache (0.99.4)
+ rack (1.4.1)
+ rack-protection (1.2.0)
+ rack
+ rake (0.9.2.2)
+ rdiscount (1.6.8)
+ ronn (0.7.3)
+ hpricot (>= 0.8.2)
+ mustache (>= 0.7.0)
+ rdiscount (>= 1.5.8)
+ rspec (2.11.0)
+ rspec-core (~> 2.11.0)
+ rspec-expectations (~> 2.11.0)
+ rspec-mocks (~> 2.11.0)
+ rspec-core (2.11.0)
+ rspec-expectations (2.11.1)
+ diff-lcs (~> 1.1.3)
+ rspec-mocks (2.11.1)
+ sinatra (1.3.2)
+ rack (~> 1.3, >= 1.3.6)
+ rack-protection (~> 1.2)
+ tilt (~> 1.3, >= 1.3.3)
+ thin (1.3.1)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
+ tilt (1.3.3)
+ webmock (1.9.0)
+ addressable (>= 2.2.7)
+ crack (>= 0.1.7)
+
+PLATFORMS
+ java
+ ruby
+
+DEPENDENCIES
+ aruba
+ cucumber
+ hub!
+ jruby-openssl
+ json
+ rake
+ ronn
+ sinatra
+ thin
+ webmock
View
36 HISTORY.md
@@ -1,3 +1,39 @@
+## master
+
+* change `pull-request` interface to allow passing title & body
+* avoid hard breaks in `pull-request` message authored in Vim
+* save and reuse `pull-request` message if creating it failed
+* new `ci-status` command for checking GitHub Status API
+
+## 1.10.6 (2013-04-25)
+
+* compensate for new GitHub API enforcement
+* fix asking for password on Ruby <= 1.8.6
+* fix `fetch` for forks where username contains a dash
+
+## 1.10.5 (2013-03-02)
+
+* helpful `pull-request` error message when base project is invalid
+* fix `compare` for ranges containing "owner:branch" notation
+* enable `--name` argument for `submodule add`
+
+## 1.10.4 (2012-12-29)
+
+* fixes for Windows
+* display more validation errors on GitHub API failures
+* persist correctly capitalized GitHub login name
+
+## 1.10.3 (2012-11-22)
+
+* fix `browse` on Windows
+* fix hub on JRuby
+* try fixing Ruby 1.8.7 error with API requests
+* fix various edge cases around `create` command
+* have `fork` set up a remote even if own fork already exists
+* fix `pull-request` with Unicode chars on Ruby 1.8
+* respect $GITHUB_USER & $GITHUB_PASSWORD
+* improve reading git remotes configuration
+
## 1.10.2 (2012-07-24)
* fix pushing multiple refs to multiple remotes
View
66 README.md
@@ -48,6 +48,15 @@ git version 1.7.6
hub version 1.8.3
~~~
+#### On Windows
+
+If you have mysysgit, open "Git Bash" and follow the steps above but put the
+`hub` executable in `/bin` instead of `~/bin`.
+
+Avoid aliasing hub as `git` due to the fact that mysysgit automatically
+configures your prompt to include git information, and you want to avoid slowing
+that down. See [Is your shell prompt slow?](#is-your-shell-prompt-slow)
+
### RubyGems
Though not recommended, hub can also be installed as a RubyGem:
@@ -79,20 +88,49 @@ $ cd hub
$ rake install prefix=/usr/local
~~~
-### Help! It's Slow!
+### Help! It's slow!
+
+#### Is `hub` noticeably slower than plain git?
+
+That is inconvenient, especially if you want to alias hub as `git`. Few things
+you can try:
+
+* Find out which ruby is used for the hub executable:
+
+ ``` sh
+ head -1 `which hub`
+ ```
-Is your prompt slow? It may be hub.
+* That ruby should be speedy. Time it with:
-1. Check that it's **not** installed using RubyGems.
-2. Check that RUBYOPT isn't loading anything shady:
+ ``` sh
+ time /usr/bin/ruby -e0
+ #=> it should be below 0.01 s total
+ ```
- $ echo $RUBYOPT
+* Check that Ruby isn't loading something shady:
-3. Check that your system Ruby is speedy:
+ ``` sh
+ echo $RUBYOPT
+ ```
- $ time /usr/bin/env ruby -e0
+* Check your [GC settings][gc]
-If #3 is slow, it may be your [GC settings][gc].
+General recommendation: you should change hub's shebang line to run with system
+ruby (usually `/usr/bin/ruby`) instead of currently active ruby (`/usr/bin/env
+ruby`). Also, Ruby 1.8 is speedier than 1.9.
+
+#### Is your shell prompt slow?
+
+Does your prompt show git information? Hub may be slowing down your prompt.
+
+This can happen if you've aliased hub as `git`. This is fine when you use `git`
+manually, but may be unacceptable for your prompt, which doesn't need hub
+features anyway!
+
+The solution is to identify which shell functions are calling `git`, and replace
+each occurrence of that with `command git`. This is a shell feature that enables
+you to call a command directly and skip aliases and functions wrapping it.
Aliasing
@@ -200,7 +238,7 @@ superpowers:
[ opened pull request on GitHub for "YOUR_USER:feature" ]
# explicit title, pull base & head:
- $ git pull-request "I've implemented feature X" -b defunkt:master -h mislav:feature
+ $ git pull-request -m "Implemented feature X" -b defunkt:master -h mislav:feature
$ git pull-request -i 123
[ attached pull request to issue #123 ]
@@ -294,8 +332,14 @@ superpowers:
$ hub submodule add -p wycats/bundler vendor/bundler
> git submodule add git@github.com:wycats/bundler.git vendor/bundler
- $ hub submodule add -b ryppl ryppl/pip vendor/pip
- > git submodule add -b ryppl git://github.com/ryppl/pip.git vendor/pip
+ $ hub submodule add -b ryppl --name pip ryppl/pip vendor/pip
+ > git submodule add -b ryppl --name pip git://github.com/ryppl/pip.git vendor/pip
+
+### git ci-status
+
+ $ hub ci-status [commit]
+ > (prints CI state of commit and exits with appropriate code)
+ > One of: success (0), error (1), failure (1), pending (2), no status (3)
### git help
View
8 Rakefile
@@ -152,17 +152,17 @@ task :homebrew do
sh 'git pull -q origin master'
formula_file = 'Library/Formula/hub.rb'
- md5 = `curl -#L https://github.com/defunkt/hub/tarball/v#{Hub::VERSION} | md5`.chomp
- abort unless $?.success? and md5.length == 32
+ sha = `curl -fsSL https://github.com/defunkt/hub/tarball/v#{Hub::VERSION} | shasum`.split(/\s+/).first
+ abort unless $?.success? and sha.length == 40
formula = File.read formula_file
formula.sub! /\bv\d+(\.\d+)*/, "v#{Hub::VERSION}"
- formula.sub! /\b[0-9a-f]{32}\b/, md5
+ formula.sub! /\b[0-9a-f]{40}\b/, sha
File.open(formula_file, 'w') {|f| f << formula }
branch = "hub-v#{Hub::VERSION}"
sh "git checkout -q -B #{branch}"
- sh "git commit -m 'upgrade hub to v#{Hub::VERSION}' -- #{formula_file}"
+ sh "git commit -m 'hub v#{Hub::VERSION}' -- #{formula_file}"
sh "git push -u mislav #{branch}"
sh "hub pull-request 'upgrade hub to v#{Hub::VERSION}'"
View
43 features/authentication.feature
@@ -10,10 +10,17 @@ Feature: OAuth authentication
post('/authorizations') {
auth = Rack::Auth::Basic::Request.new(env)
halt 401 unless auth.credentials == %w[mislav kitty]
- halt 400 unless params[:scopes] == ['repo']
+ assert :scopes => ['repo']
json :token => 'OTOKEN'
}
- post('/user/repos') { status 200 }
+ get('/user') {
+ halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'
+ json :login => 'MiSlAv'
+ }
+ post('/user/repos') {
+ halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'
+ json :full_name => 'mislav/dotfiles'
+ }
"""
When I run `hub create` interactively
When I type "mislav"
@@ -21,6 +28,7 @@ Feature: OAuth authentication
Then the output should contain "github.com username:"
And the output should contain "github.com password for mislav (never stored):"
And the exit status should be 0
+ And the file "../home/.config/hub" should contain "user: MiSlAv"
And the file "../home/.config/hub" should contain "oauth_token: OTOKEN"
And the file "../home/.config/hub" should have mode "0600"
@@ -36,7 +44,12 @@ Feature: OAuth authentication
{:token => 'OTOKEN', :app => {:url => 'http://defunkt.io/hub/'}}
]
}
- post('/user/repos') { status 200 }
+ get('/user') {
+ json :login => 'mislav'
+ }
+ post('/user/repos') {
+ json :full_name => 'mislav/dotfiles'
+ }
"""
When I run `hub create` interactively
When I type "mislav"
@@ -45,6 +58,30 @@ Feature: OAuth authentication
And the exit status should be 0
And the file "../home/.config/hub" should contain "oauth_token: OTOKEN"
+ Scenario: Credentials from GITHUB_USER & GITHUB_PASSWORD
+ Given the GitHub API server:
+ """
+ require 'rack/auth/basic'
+ get('/authorizations') {
+ auth = Rack::Auth::Basic::Request.new(env)
+ halt 401 unless auth.credentials == %w[mislav kitty]
+ json [
+ {:token => 'OTOKEN', :app => {:url => 'http://defunkt.io/hub/'}}
+ ]
+ }
+ get('/user') {
+ json :login => 'mislav'
+ }
+ post('/user/repos') {
+ json :full_name => 'mislav/dotfiles'
+ }
+ """
+ Given $GITHUB_USER is "mislav"
+ And $GITHUB_PASSWORD is "kitty"
+ When I successfully run `hub create`
+ Then the output should not contain "github.com password for mislav"
+ And the file "../home/.config/hub" should contain "oauth_token: OTOKEN"
+
Scenario: Wrong password
Given the GitHub API server:
"""
View
10 features/browse.feature
@@ -59,6 +59,12 @@ Feature: hub browse
When I successfully run `hub browse`
Then "open https://github.com/mislav/dotfiles" should be run
+ Scenario: Current branch with special chars
+ Given I am in "git://github.com/mislav/dotfiles.git" git repo
+ And I am on the "fix-bug-#123" branch with upstream "origin/fix-bug-#123"
+ When I successfully run `hub browse`
+ Then "open https://github.com/mislav/dotfiles/tree/fix-bug-%23123" should be run
+
Scenario: Commits on current branch
Given I am in "git://github.com/mislav/dotfiles.git" git repo
And I am on the "feature" branch with upstream "origin/experimental"
@@ -67,9 +73,9 @@ Feature: hub browse
Scenario: Complex branch
Given I am in "git://github.com/mislav/dotfiles.git" git repo
- And I am on the "foo/bar" branch with upstream "origin/baz/qux"
+ And I am on the "foo/bar" branch with upstream "origin/baz/qux/moo"
When I successfully run `hub browse`
- Then "open https://github.com/mislav/dotfiles/tree/baz/qux" should be run
+ Then "open https://github.com/mislav/dotfiles/tree/baz/qux/moo" should be run
Scenario: Wiki repo
Given I am in "git://github.com/defunkt/hub.wiki.git" git repo
View
80 features/checkout.feature
@@ -0,0 +1,80 @@
+Feature: hub checkout <PULLREQ-URL>
+ Background:
+ Given I am in "git://github.com/mojombo/jekyll.git" git repo
+ And I am "mislav" on github.com with OAuth token "OTOKEN"
+
+ Scenario: Unchanged command
+ When I run `hub checkout master`
+ Then "git checkout master" should be run
+
+ Scenario: Checkout a pull request
+ Given the GitHub API server:
+ """
+ get('/repos/mojombo/jekyll/pulls/77') {
+ json :head => {
+ :label => 'mislav:fixes',
+ :repo => { :private => false }
+ }
+ }
+ """
+ When I run `hub checkout -f https://github.com/mojombo/jekyll/pull/77 -q`
+ Then "git remote add -f -t fixes mislav git://github.com/mislav/jekyll.git" should be run
+ And "git checkout -f --track -B mislav-fixes mislav/fixes -q" should be run
+
+ Scenario: Custom name for new branch
+ Given the GitHub API server:
+ """
+ get('/repos/mojombo/jekyll/pulls/77') {
+ json :head => {
+ :label => 'mislav:fixes',
+ :repo => { :private => false }
+ }
+ }
+ """
+ When I run `hub checkout https://github.com/mojombo/jekyll/pull/77 fixes-from-mislav`
+ Then "git remote add -f -t fixes mislav git://github.com/mislav/jekyll.git" should be run
+ And "git checkout --track -B fixes-from-mislav mislav/fixes" should be run
+
+ Scenario: Private pull request
+ Given the GitHub API server:
+ """
+ get('/repos/mojombo/jekyll/pulls/77') {
+ json :head => {
+ :label => 'mislav:fixes',
+ :repo => { :private => true }
+ }
+ }
+ """
+ When I run `hub checkout -f https://github.com/mojombo/jekyll/pull/77 -q`
+ Then "git remote add -f -t fixes mislav git@github.com:mislav/jekyll.git" should be run
+ And "git checkout -f --track -B mislav-fixes mislav/fixes -q" should be run
+
+ Scenario: Custom name for new branch
+ Given the GitHub API server:
+ """
+ get('/repos/mojombo/jekyll/pulls/77') {
+ json :head => {
+ :label => 'mislav:fixes',
+ :repo => { :private => false }
+ }
+ }
+ """
+ When I run `hub checkout https://github.com/mojombo/jekyll/pull/77 fixes-from-mislav`
+ Then "git remote add -f -t fixes mislav git://github.com/mislav/jekyll.git" should be run
+ And "git checkout --track -B fixes-from-mislav mislav/fixes" should be run
+
+ Scenario: Remote for user already exists
+ Given the GitHub API server:
+ """
+ get('/repos/mojombo/jekyll/pulls/77') {
+ json :head => {
+ :label => 'mislav:fixes',
+ :repo => { :private => false }
+ }
+ }
+ """
+ And the "mislav" remote has url "git://github.com/mislav/jekyll.git"
+ When I run `hub checkout https://github.com/mojombo/jekyll/pull/77`
+ Then "git remote set-branches --add mislav fixes" should be run
+ And "git fetch mislav +refs/heads/fixes:refs/remotes/mislav/fixes" should be run
+ And "git checkout --track -B mislav-fixes mislav/fixes" should be run
View
53 features/ci_status.feature
@@ -0,0 +1,53 @@
+Feature: hub ci-status
+
+ Background:
+ Given I am in "git://github.com/michiels/pencilbox.git" git repo
+ And I am "michiels" on github.com with OAuth token "OTOKEN"
+
+ Scenario: Fetch commit SHA
+ Given there is a commit named "the_sha"
+ Given the remote commit state of "michiels/pencilbox" "the_sha" is "success"
+ When I run `hub ci-status the_sha`
+ Then the output should contain exactly "success\n"
+ And the exit status should be 0
+
+ Scenario: Multiple statuses, latest is passing
+ Given there is a commit named "the_sha"
+ Given the remote commit states of "michiels/pencilbox" "the_sha" are:
+ """
+ [ { :state => 'success' },
+ { :state => 'pending' } ]
+ """
+ When I run `hub ci-status the_sha`
+ Then the output should contain exactly "success\n"
+ And the exit status should be 0
+
+ Scenario: Exit status 1 for 'error' and 'failure'
+ Given the remote commit state of "michiels/pencilbox" "HEAD" is "error"
+ When I run `hub ci-status`
+ Then the exit status should be 1
+ And the output should contain exactly "error\n"
+
+ Scenario: Use HEAD when no sha given
+ Given the remote commit state of "michiels/pencilbox" "HEAD" is "pending"
+ When I run `hub ci-status`
+ Then the exit status should be 2
+ And the output should contain exactly "pending\n"
+
+ Scenario: Exit status 3 for no statuses available
+ Given there is a commit named "the_sha"
+ Given the remote commit state of "michiels/pencilbox" "the_sha" is nil
+ When I run `hub ci-status the_sha`
+ Then the output should contain exactly "no status\n"
+ And the exit status should be 3
+
+ Scenario: Abort with message when invalid ref given
+ When I run `hub ci-status this-is-an-invalid-ref`
+ Then the exit status should be 1
+ And the output should contain exactly "Aborted: no revision could be determined from 'this-is-an-invalid-ref'\n"
+
+ Scenario: Non-GitHub repo
+ Given the "origin" remote has url "mygh:Manganeez/repo.git"
+ When I run `hub ci-status`
+ Then the stderr should contain "Aborted: the origin remote doesn't point to a GitHub repository.\n"
+ And the exit status should be 1
View
66 features/compare.feature
@@ -0,0 +1,66 @@
+Feature: hub browse
+ Background:
+ Given I am in "git://github.com/mislav/dotfiles.git" git repo
+
+ Scenario: Compare branch
+ When I successfully run `hub compare refactor`
+ Then there should be no output
+ And "open https://github.com/mislav/dotfiles/compare/refactor" should be run
+
+ Scenario: No args, no upstream
+ When I run `hub compare`
+ Then the exit status should be 1
+ And the stderr should contain:
+ """
+ hub compare [USER] [<START>...]<END>
+ """
+
+ Scenario: No args, has upstream branch
+ Given I am on the "feature" branch with upstream "origin/experimental"
+ When I successfully run `hub compare`
+ Then there should be no output
+ And "open https://github.com/mislav/dotfiles/compare/experimental" should be run
+
+ Scenario: Compare range
+ When I successfully run `hub compare 1.0...fix`
+ Then there should be no output
+ And "open https://github.com/mislav/dotfiles/compare/1.0...fix" should be run
+
+ Scenario: Output URL without opening the browser
+ When I successfully run `hub compare -u 1.0...fix`
+ Then "open https://github.com/mislav/dotfiles/compare/1.0...fix" should not be run
+ And the stdout should contain exactly:
+ """
+ https://github.com/mislav/dotfiles/compare/1.0...fix\n
+ """
+
+ Scenario: Compare 2-dots range for tags
+ When I successfully run `hub compare 1.0..fix`
+ Then there should be no output
+ And "open https://github.com/mislav/dotfiles/compare/1.0...fix" should be run
+
+ Scenario: Compare 2-dots range for SHAs
+ When I successfully run `hub compare 1234abc..3456cde`
+ Then there should be no output
+ And "open https://github.com/mislav/dotfiles/compare/1234abc...3456cde" should be run
+
+ Scenario: Compare 2-dots range with "user:repo" notation
+ When I successfully run `hub compare henrahmagix:master..2b10927`
+ Then there should be no output
+ And "open https://github.com/mislav/dotfiles/compare/henrahmagix:master...2b10927" should be run
+
+ Scenario: Complex range is unchanged
+ When I successfully run `hub compare @{a..b}..@{c..d}`
+ Then there should be no output
+ And "open https://github.com/mislav/dotfiles/compare/@{a..b}..@{c..d}" should be run
+
+ Scenario: Compare wiki
+ Given the "origin" remote has url "git://github.com/mislav/dotfiles.wiki.git"
+ When I successfully run `hub compare 1.0..fix`
+ Then there should be no output
+ And "open https://github.com/mislav/dotfiles/wiki/_compare/1.0...fix" should be run
+
+ Scenario: Compare fork
+ When I successfully run `hub compare anotheruser feature`
+ Then there should be no output
+ And "open https://github.com/anotheruser/dotfiles/compare/feature" should be run
View
53 features/create.feature
@@ -7,8 +7,8 @@ Feature: hub create
Given the GitHub API server:
"""
post('/user/repos') {
- halt 400 if params[:private]
- status 200
+ assert :private => false
+ json :full_name => 'mislav/dotfiles'
}
"""
When I successfully run `hub create`
@@ -19,8 +19,8 @@ Feature: hub create
Given the GitHub API server:
"""
post('/user/repos') {
- halt 400 unless params[:private]
- status 200
+ assert :private => true
+ json :full_name => 'mislav/dotfiles'
}
"""
When I successfully run `hub create -p`
@@ -29,7 +29,9 @@ Feature: hub create
Scenario: HTTPS is preferred
Given the GitHub API server:
"""
- post('/user/repos') { status 200 }
+ post('/user/repos') {
+ json :full_name => 'mislav/dotfiles'
+ }
"""
And HTTPS is preferred
When I successfully run `hub create`
@@ -38,7 +40,9 @@ Feature: hub create
Scenario: Create in organization
Given the GitHub API server:
"""
- post('/orgs/acme/repos') { status 200 }
+ post('/orgs/acme/repos') {
+ json :full_name => 'acme/dotfiles'
+ }
"""
When I successfully run `hub create acme/dotfiles`
Then the url for "origin" should be "git@github.com:acme/dotfiles.git"
@@ -58,8 +62,8 @@ Feature: hub create
Given the GitHub API server:
"""
post('/user/repos') {
- halt 400 unless params[:name] == 'myconfig'
- status 200
+ assert :name => 'myconfig'
+ json :full_name => 'mislav/myconfig'
}
"""
When I successfully run `hub create myconfig`
@@ -69,9 +73,9 @@ Feature: hub create
Given the GitHub API server:
"""
post('/user/repos') {
- halt 400 unless params[:description] == 'mydesc' and
- params[:homepage] == 'http://example.com'
- status 200
+ assert :description => 'mydesc',
+ :homepage => 'http://example.com'
+ json :full_name => 'mislav/dotfiles'
}
"""
When I successfully run `hub create -d mydesc -h http://example.com`
@@ -86,7 +90,9 @@ Feature: hub create
Scenario: Origin remote already exists
Given the GitHub API server:
"""
- post('/user/repos') { status 200 }
+ post('/user/repos') {
+ json :full_name => 'mislav/dotfiles'
+ }
"""
And the "origin" remote has url "git://github.com/mislav/dotfiles.git"
When I successfully run `hub create`
@@ -100,3 +106,26 @@ Feature: hub create
When I successfully run `hub create`
Then the output should contain "mislav/dotfiles already exists on github.com\n"
And the url for "origin" should be "git@github.com:mislav/dotfiles.git"
+
+ Scenario: API response changes the clone URL
+ Given the GitHub API server:
+ """
+ post('/user/repos') {
+ json :full_name => 'Mooslav/myconfig'
+ }
+ """
+ When I successfully run `hub create`
+ Then the url for "origin" should be "git@github.com:Mooslav/myconfig.git"
+ And the output should contain exactly "created repository: Mooslav/myconfig\n"
+
+ Scenario: Current directory contains spaces
+ Given I am in "my dot files" git repo
+ Given the GitHub API server:
+ """
+ post('/user/repos') {
+ assert :name => 'my-dot-files'
+ json :full_name => 'mislav/my-dot-files'
+ }
+ """
+ When I successfully run `hub create`
+ Then the url for "origin" should be "git@github.com:mislav/my-dot-files.git"
View
10 features/fetch.feature
@@ -19,6 +19,16 @@ Feature: hub fetch
And the url for "mislav" should be "git://github.com/mislav/dotfiles.git"
And there should be no output
+ Scenario: Owner name with dash
+ Given the GitHub API server:
+ """
+ get('/repos/ankit-maverick/dotfiles') { json :private => false }
+ """
+ When I successfully run `hub fetch ankit-maverick`
+ Then "git fetch ankit-maverick" should be run
+ And the url for "ankit-maverick" should be "git://github.com/ankit-maverick/dotfiles.git"
+ And there should be no output
+
Scenario: HTTPS is preferred
Given the GitHub API server:
"""
View
25 features/fork.feature
@@ -8,7 +8,7 @@ Feature: hub fork
Given the GitHub API server:
"""
before { halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' }
- get('/repos/evilchelu/dotfiles', :host_name => 'api.github.com') { '' }
+ get('/repos/mislav/dotfiles', :host_name => 'api.github.com') { 404 }
post('/repos/evilchelu/dotfiles/forks', :host_name => 'api.github.com') { '' }
"""
When I successfully run `hub fork`
@@ -19,7 +19,6 @@ Feature: hub fork
Scenario: --no-remote
Given the GitHub API server:
"""
- get('/repos/evilchelu/dotfiles') { '' }
post('/repos/evilchelu/dotfiles/forks') { '' }
"""
When I successfully run `hub fork --no-remote`
@@ -29,7 +28,6 @@ Feature: hub fork
Scenario: Fork failed
Given the GitHub API server:
"""
- get('/repos/evilchelu/dotfiles') { '' }
post('/repos/evilchelu/dotfiles/forks') { halt 500 }
"""
When I run `hub fork`
@@ -40,11 +38,12 @@ Feature: hub fork
"""
And there should be no "mislav" remote
- Scenario: Fork already exists
+ Scenario: Unrelated fork already exists
Given the GitHub API server:
"""
- get('/repos/evilchelu/dotfiles') { '' }
- get('/repos/mislav/dotfiles') { '' }
+ get('/repos/mislav/dotfiles') {
+ json :parent => { :html_url => 'https://github.com/unrelated/dotfiles' }
+ }
"""
When I run `hub fork`
Then the exit status should be 1
@@ -54,11 +53,21 @@ Feature: hub fork
"""
And there should be no "mislav" remote
+Scenario: Related fork already exists
+ Given the GitHub API server:
+ """
+ get('/repos/mislav/dotfiles') {
+ json :parent => { :html_url => 'https://github.com/evilchelu/dotfiles' }
+ }
+ """
+ When I run `hub fork`
+ Then the exit status should be 0
+ And the url for "mislav" should be "git@github.com:mislav/dotfiles.git"
+
Scenario: Invalid OAuth token
Given the GitHub API server:
"""
before { halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' }
- get('/repos/evilchelu/dotfiles') { '' }
"""
And I am "mislav" on github.com with OAuth token "WRONGTOKEN"
When I run `hub fork`
@@ -71,7 +80,6 @@ Feature: hub fork
Scenario: HTTPS is preferred
Given the GitHub API server:
"""
- get('/repos/evilchelu/dotfiles') { '' }
post('/repos/evilchelu/dotfiles/forks') { '' }
"""
And HTTPS is preferred
@@ -98,7 +106,6 @@ Feature: hub fork
Given the GitHub API server:
"""
before { halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN' }
- get('/api/v3/repos/evilchelu/dotfiles', :host_name => 'git.my.org') { '' }
post('/api/v3/repos/evilchelu/dotfiles/forks', :host_name => 'git.my.org') { '' }
"""
And the "origin" remote has url "git@git.my.org:evilchelu/dotfiles.git"
View
2  features/merge.feature
@@ -27,7 +27,7 @@ Feature: hub merge
Add `hub merge` command
"""
- Scenario: Merge pull request
+ Scenario: Merge private pull request
Given the GitHub API server:
"""
require 'json'
View
382 features/pull_request.feature
@@ -1,7 +1,14 @@
Feature: hub pull-request
Background:
- Given I am in "dotfiles" git repo
+ Given I am in "git://github.com/mislav/coral.git" git repo
And I am "mislav" on github.com with OAuth token "OTOKEN"
+ And the git commit editor is "vim"
+
+ Scenario: Detached HEAD
+ Given I am in detached HEAD
+ When I run `hub pull-request`
+ Then the stderr should contain "Aborted: not currently on any branch.\n"
+ And the exit status should be 1
Scenario: Non-GitHub repo
Given the "origin" remote has url "mygh:Manganeez/repo.git"
@@ -15,22 +22,365 @@ Feature: hub pull-request
Given the GitHub API server:
"""
post('/repos/Manganeez/repo/pulls') {
- { :base => 'master',
- :head => 'mislav:master',
- :title => 'hereyougo'
- }.each do |param, value|
- if params[param] != value
- halt 422, json(
- :message => "expected %s to be %s; got %s" % [
- param.inspect,
- value.inspect,
- params[param].inspect
- ]
- )
- end
- end
+ assert :base => 'master',
+ :head => 'mislav:master',
+ :title => 'here we go'
json :html_url => "https://github.com/Manganeez/repo/pull/12"
}
"""
- When I successfully run `hub pull-request hereyougo`
+ When I successfully run `hub pull-request -m "here we go"`
Then the output should contain exactly "https://github.com/Manganeez/repo/pull/12\n"
+
+ Scenario: With Unicode characters
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ halt 400 if request.content_charset != 'utf-8'
+ assert :title => 'ăéñøü'
+ json :html_url => "the://url"
+ }
+ """
+ When I successfully run `hub pull-request -m ăéñøü`
+ Then the output should contain exactly "the://url\n"
+
+ Scenario: Deprecated title argument
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ halt 422 if params[:title] != 'mytitle'
+ json :html_url => "the://url"
+ }
+ """
+ When I successfully run `hub pull-request mytitle`
+ Then the stderr should contain exactly:
+ """
+ hub: Specifying pull request title without a flag is deprecated.
+ Please use one of `-m' or `-F' options.\n
+ """
+
+ Scenario: Non-existing base
+ Given the GitHub API server:
+ """
+ post('/repos/origin/coral/pulls') { 404 }
+ """
+ When I run `hub pull-request -b origin:master -m here`
+ Then the exit status should be 1
+ Then the stderr should contain:
+ """
+ Error creating pull request: Not Found (HTTP 404)
+ Are you sure that github.com/origin/coral exists?
+ """
+
+ Scenario: Supplies User-Agent string to API calls
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ halt 400 unless request.user_agent.include?('Hub')
+ json :html_url => "the://url"
+ }
+ """
+ When I successfully run `hub pull-request -m useragent`
+ Then the output should contain exactly "the://url\n"
+
+ Scenario: Text editor adds title and body
+ Given the text editor adds:
+ """
+ This title comes from vim!
+
+ This body as well.
+ """
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :title => 'This title comes from vim!',
+ :body => 'This body as well.'
+ json :html_url => "https://github.com/mislav/coral/pull/12"
+ }
+ """
+ When I successfully run `hub pull-request`
+ Then the output should contain exactly "https://github.com/mislav/coral/pull/12\n"
+ And the file ".git/PULLREQ_EDITMSG" should not exist
+
+ Scenario: Failed pull request preserves previous message
+ Given the text editor adds:
+ """
+ This title will fail
+ """
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ halt 422 if params[:title].include?("fail")
+ assert :body => "This title will fail",
+ :title => "But this title will prevail"
+ json :html_url => "https://github.com/mislav/coral/pull/12"
+ }
+ """
+ When I run `hub pull-request`
+ Then the exit status should be 1
+ And the stderr should contain exactly:
+ """
+ Error creating pull request: Unprocessable Entity (HTTP 422)\n
+ """
+ Given the text editor adds:
+ """
+ But this title will prevail
+ """
+ When I successfully run `hub pull-request`
+ Then the file ".git/PULLREQ_EDITMSG" should not exist
+
+ Scenario: Text editor fails
+ Given the text editor exits with error status
+ And an empty file named ".git/PULLREQ_EDITMSG"
+ When I run `hub pull-request`
+ Then the stderr should contain "error using text editor for pull request message"
+ And the exit status should be 1
+ And the file ".git/PULLREQ_EDITMSG" should not exist
+
+ Scenario: Title and body from file
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :title => 'Title from file',
+ :body => "Body from file as well.\n\nMultiline, even!"
+ json :html_url => "https://github.com/mislav/coral/pull/12"
+ }
+ """
+ And a file named "pullreq-msg" with:
+ """
+ Title from file
+
+ Body from file as well.
+
+ Multiline, even!
+ """
+ When I successfully run `hub pull-request -F pullreq-msg`
+ Then the output should contain exactly "https://github.com/mislav/coral/pull/12\n"
+ And the file ".git/PULLREQ_EDITMSG" should not exist
+
+ Scenario: Title and body from stdin
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :title => 'Unix piping is great',
+ :body => 'Just look at this'
+ json :html_url => "https://github.com/mislav/coral/pull/12"
+ }
+ """
+ When I run `hub pull-request -F -` interactively
+ And I pass in:
+ """
+ Unix piping is great
+
+ Just look at this
+ """
+ Then the output should contain exactly "https://github.com/mislav/coral/pull/12\n"
+ And the exit status should be 0
+ And the file ".git/PULLREQ_EDITMSG" should not exist
+
+ Scenario: Title and body from command-line argument
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :title => 'I am just a pull',
+ :body => 'A little pull'
+ json :html_url => "https://github.com/mislav/coral/pull/12"
+ }
+ """
+ When I successfully run `hub pull-request -m "I am just a pull\n\nA little pull"`
+ Then the output should contain exactly "https://github.com/mislav/coral/pull/12\n"
+ And the file ".git/PULLREQ_EDITMSG" should not exist
+
+ Scenario: Error when implicit head is the same as base
+ Given I am on the "master" branch with upstream "origin/master"
+ When I run `hub pull-request`
+ Then the stderr should contain exactly:
+ """
+ Aborted: head branch is the same as base ("master")
+ (use `-h <branch>` to specify an explicit pull request head)\n
+ """
+
+ Scenario: Explicit head
+ Given I am on the "master" branch
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :head => 'mislav:feature'
+ json :html_url => "the://url"
+ }
+ """
+ When I successfully run `hub pull-request -h feature -m message`
+ Then the output should contain exactly "the://url\n"
+
+ Scenario: Explicit head with owner
+ Given I am on the "master" branch
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :head => 'mojombo:feature'
+ json :html_url => "the://url"
+ }
+ """
+ When I successfully run `hub pull-request -h mojombo:feature -m message`
+ Then the output should contain exactly "the://url\n"
+
+ Scenario: Explicit base
+ Given I am on the "feature" branch
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :base => 'develop'
+ json :html_url => "the://url"
+ }
+ """
+ When I successfully run `hub pull-request -b develop -m message`
+ Then the output should contain exactly "the://url\n"
+
+ Scenario: Explicit base with owner
+ Given I am on the "master" branch
+ Given the GitHub API server:
+ """
+ post('/repos/mojombo/coral/pulls') {
+ assert :base => 'develop'
+ json :html_url => "the://url"
+ }
+ """
+ When I successfully run `hub pull-request -b mojombo:develop -m message`
+ Then the output should contain exactly "the://url\n"
+
+ Scenario: Explicit base with owner and repo name
+ Given I am on the "master" branch
+ Given the GitHub API server:
+ """
+ post('/repos/mojombo/coralify/pulls') {
+ assert :base => 'develop'
+ json :html_url => "the://url"
+ }
+ """
+ When I successfully run `hub pull-request -b mojombo/coralify:develop -m message`
+ Then the output should contain exactly "the://url\n"
+
+ Scenario: Error when there are unpushed commits
+ Given I am on the "feature" branch with upstream "origin/feature"
+ When I make 2 commits
+ And I run `hub pull-request`
+ Then the stderr should contain exactly:
+ """
+ Aborted: 2 commits are not yet pushed to origin/feature
+ (use `-f` to force submit a pull request anyway)\n
+ """
+
+ Scenario: Ignore unpushed commits with `-f`
+ Given I am on the "feature" branch with upstream "origin/feature"
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :head => 'mislav:feature'
+ json :html_url => "the://url"
+ }
+ """
+ When I make 2 commits
+ And I successfully run `hub pull-request -f -m message`
+ Then the output should contain exactly "the://url\n"
+
+ Scenario: Pull request fails on the server
+ Given I am on the "feature" branch with upstream "origin/feature"
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ status 422
+ json(:message => "I haz fail!")
+ }
+ """
+ When I run `hub pull-request -m message`
+ Then the stderr should contain exactly:
+ """
+ Error creating pull request: Unprocessable Entity (HTTP 422)
+ I haz fail!\n
+ """
+
+ Scenario: Convert issue to pull request
+ Given I am on the "feature" branch with upstream "origin/feature"
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :issue => '92'
+ json :html_url => "https://github.com/mislav/coral/pull/92"
+ }
+ """
+ When I successfully run `hub pull-request -i 92`
+ Then the output should contain exactly "https://github.com/mislav/coral/pull/92\n"
+
+ Scenario: Convert issue URL to pull request
+ Given I am on the "feature" branch with upstream "origin/feature"
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :issue => '92'
+ json :html_url => "https://github.com/mislav/coral/pull/92"
+ }
+ """
+ When I successfully run `hub pull-request https://github.com/mislav/coral/issues/92`
+ Then the output should contain exactly "https://github.com/mislav/coral/pull/92\n"
+
+ Scenario: Error when there are unpushed commits
+ Given I am on the "feature" branch with upstream "origin/feature"
+ When I make 2 commits
+ And I run `hub pull-request`
+ Then the stderr should contain exactly:
+ """
+ Aborted: 2 commits are not yet pushed to origin/feature
+ (use `-f` to force submit a pull request anyway)\n
+ """
+
+ Scenario: Ignore unpushed commits with `-f`
+ Given I am on the "feature" branch with upstream "origin/feature"
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :head => 'mislav:feature'
+ json :html_url => "the://url"
+ }
+ """
+ When I make 2 commits
+ And I successfully run `hub pull-request -f -m message`
+ Then the output should contain exactly "the://url\n"
+
+ Scenario: Pull request fails on the server
+ Given I am on the "feature" branch with upstream "origin/feature"
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ status 422
+ json(:message => "I haz fail!")
+ }
+ """
+ When I run `hub pull-request -m message`
+ Then the stderr should contain exactly:
+ """
+ Error creating pull request: Unprocessable Entity (HTTP 422)
+ I haz fail!\n
+ """
+
+ Scenario: Convert issue to pull request
+ Given I am on the "feature" branch with upstream "origin/feature"
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :issue => '92'
+ json :html_url => "https://github.com/mislav/coral/pull/92"
+ }
+ """
+ When I successfully run `hub pull-request -i 92`
+ Then the output should contain exactly "https://github.com/mislav/coral/pull/92\n"
+
+ Scenario: Convert issue URL to pull request
+ Given I am on the "feature" branch with upstream "origin/feature"
+ Given the GitHub API server:
+ """
+ post('/repos/mislav/coral/pulls') {
+ assert :issue => '92'
+ json :html_url => "https://github.com/mislav/coral/pull/92"
+ }
+ """
+ When I successfully run `hub pull-request https://github.com/mislav/coral/issues/92`
+ Then the output should contain exactly "https://github.com/mislav/coral/pull/92\n"
View
57 features/steps.rb
@@ -57,6 +57,11 @@
run_silent %(git reset --quiet --hard HEAD^)
end
+When /^I make (a|\d+) commits?$/ do |num|
+ num = num == 'a' ? 1 : num.to_i
+ num.times { empty_commit }
+end
+
Given /^I am on the "([^"]+)" branch(?: with upstream "([^"]+)")?$/ do |name, upstream|
empty_commit
if upstream
@@ -69,6 +74,12 @@
run_silent %(git checkout --quiet -B #{name} --track #{upstream})
end
+Given /^I am in detached HEAD$/ do
+ empty_commit
+ empty_commit
+ run_silent %(git checkout HEAD^)
+end
+
Given /^the current dir is not a repo$/ do
in_current_dir do
FileUtils.rm_rf '.git'
@@ -131,3 +142,49 @@
mode.to_s(8).should =~ /#{expected_mode}$/
end
end
+
+Given /^the remote commit states of "(.*?)" "(.*?)" are:$/ do |proj, ref, json_value|
+ if ref == 'HEAD'
+ empty_commit
+ end
+ rev = run_silent %(git rev-parse #{ref})
+
+ status_endpoint = <<-EOS
+ get('/repos/#{proj}/statuses/#{rev}') {
+ json #{json_value}
+ }
+ EOS
+ step %{the GitHub API server:}, status_endpoint
+end
+
+Given /^the remote commit state of "(.*?)" "(.*?)" is "(.*?)"$/ do |proj, ref, status|
+ step %{the remote commit states of "#{proj}" "#{ref}" are:}, "[ { :state => \"#{status}\" } ]"
+end
+
+Given /^the remote commit state of "(.*?)" "(.*?)" is nil$/ do |proj, ref|
+ step %{the remote commit states of "#{proj}" "#{ref}" are:}, "[ ]"
+end
+
+Given /^the text editor exits with error status$/ do
+ text_editor_script "exit 1"
+end
+
+Given /^the text editor adds:$/ do |text|
+ text_editor_script <<-BASH
+ file="$3"
+ contents="$(cat "$file" 2>/dev/null || true)"
+ { echo "#{text}"
+ echo
+ echo "$contents"
+ } > "$file"
+ BASH
+end
+
+When /^I pass in:$/ do |input|
+ type(input)
+ @interactive.stdin.close
+end
+
+Given /^the git commit editor is "([^"]+)"$/ do |cmd|
+ set_env('GIT_EDITOR', cmd)
+end
View
8 features/submodule_add.feature
@@ -17,5 +17,9 @@ Feature: hub submodule add
Then the "vendor/grit" submodule url should be "git@github.com:mojombo/grit.git"
Scenario: Add submodule with arguments
- When I successfully run `hub submodule add -b foo mojombo/grit vendor/grit`
- Then "git submodule add -b foo git://github.com/mojombo/grit.git vendor/grit" should be run
+ When I successfully run `hub submodule add -b foo --name grit mojombo/grit vendor/grit`
+ Then "git submodule add -b foo --name grit git://github.com/mojombo/grit.git vendor/grit" should be run
+
+ Scenario: Add submodule with branch
+ When I successfully run `hub submodule add --branch foo mojombo/grit vendor/grit`
+ Then "git submodule add --branch foo git://github.com/mojombo/grit.git vendor/grit" should be run
View
41 features/support/env.rb
@@ -1,6 +1,5 @@
require 'aruba/cucumber'
require 'fileutils'
-require 'hub/context'
require 'forwardable'
# needed to avoid "Too many open files" on 1.8.7
@@ -11,14 +10,7 @@ def close_streams
end
end
-if [:macosx, :linux].include? ChildProcess.platform
- ChildProcess.posix_spawn = true # experimental suppport
-end
-
-unless system_git = Hub::Context.which('git')
- abort "Error: `git` not found in PATH"
-end
-
+system_git = `which git 2>/dev/null`.chomp
lib_dir = File.expand_path('../../../lib', __FILE__)
bin_dir = File.expand_path('../fakebin', __FILE__)
@@ -41,15 +33,32 @@ def close_streams
set_env 'HUB_SYSTEM_GIT', system_git
# ensure that api.github.com is actually never hit in tests
set_env 'HUB_TEST_HOST', '127.0.0.1:0'
+ # ensure we use fakebin `open` to test browsing
+ set_env 'BROWSER', 'open'
+ # sabotage opening a commit message editor interactively
+ set_env 'GIT_EDITOR', 'false'
+
+ author_name = "Hub"
+ author_email = "hub@test.local"
+ set_env 'GIT_AUTHOR_NAME', author_name
+ set_env 'GIT_COMMITTER_NAME', author_name
+ set_env 'GIT_AUTHOR_EMAIL', author_email
+ set_env 'GIT_COMMITTER_EMAIL', author_email
FileUtils.mkdir_p ENV['HOME']
- @aruba_io_wait_seconds = 0.02
+ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby'
+ @aruba_timeout_seconds = 5
+ @aruba_io_wait_seconds = 0.1
+ else
+ @aruba_io_wait_seconds = 0.02
+ end
end
After do
@server.stop if defined? @server and @server
processes.each {|_, p| p.close_streams }
+ FileUtils.rm_f("#{bin_dir}/vim")
end
RSpec::Matchers.define :be_successful_command do
@@ -118,6 +127,14 @@ def edit_hub_config
File.open(config, 'w') { |cfg| cfg << YAML.dump(data) }
end
+ define_method(:text_editor_script) do |bash_code|
+ File.open("#{bin_dir}/vim", 'w', 0755) do |exe|
+ exe.puts "#!/bin/bash"
+ exe.puts "set -e"
+ exe.puts bash_code
+ end
+ end
+
def run_silent cmd
in_current_dir do
command = SimpleCommand.run(cmd)
@@ -127,9 +144,7 @@ def run_silent cmd
end
def empty_commit
- run_silent "git commit --quiet -m ''" <<
- " --allow-empty --allow-empty-message" <<
- " --author 'Hub <hub@test.local>'"
+ run_silent "git commit --quiet -m empty --allow-empty"
end
# Aruba unnecessarily creates new Announcer instance on each invocation
View
16 features/support/local_server.rb
@@ -21,9 +21,11 @@ def self.run_handler(app, port, &block)
begin
require 'rack/handler/thin'
Thin::Logging.silent = true
+ Thin::HTTP_STATUS_CODES[422] = "Unprocessable Entity"
Rack::Handler::Thin.run(app, :Port => port, &block)
rescue LoadError
require 'rack/handler/webrick'
+ WEBrick::HTTPStatus::StatusMessage[422] = "Unprocessable Entity"
Rack::Handler::WEBrick.run(app, :Port => port, :AccessLog => [], :Logger => WEBrick::Log::new(nil, 0), &block)
end
end
@@ -61,6 +63,20 @@ def json(value)
content_type :json
JSON.generate value
end
+
+ def assert(expected)
+ expected.each do |key, value|
+ if params[key] != value
+ halt 422, json(
+ :message => "expected %s to be %s; got %s" % [
+ key.inspect,
+ value.inspect,
+ params[key].inspect
+ ]
+ )
+ end
+ end
+ end
end
new(klass.new).start
View
1  hub.gemspec
@@ -8,6 +8,7 @@ Gem::Specification.new do |s|
s.homepage = "http://defunkt.io/hub/"
s.email = "mislav.marohnic@gmail.com"
s.authors = [ "Chris Wanstrath", "Mislav Marohnić" ]
+ s.license = "MIT"
s.add_development_dependency 'rake'
s.add_development_dependency 'webmock'
View
2  lib/hub.rb
@@ -1,4 +1,4 @@
-require 'hub/version'
+require 'hub/version' unless defined?(Hub::VERSION)
require 'hub/args'
require 'hub/ssh_config'
require 'hub/github_api'
View
1  lib/hub/args.rb
@@ -11,7 +11,6 @@ class Args < Array
def initialize(*args)
super
@executable = ENV["GIT"] || "git"
- @after = nil
@skip = @noop = false
@original_args = args.first
@chain = [nil]
View
151 lib/hub/commands.rb
@@ -35,10 +35,10 @@ module Commands
extend Context
NAME_RE = /[\w.][\w.-]*/
- OWNER_RE = /[a-zA-Z0-9-]+/
+ OWNER_RE = /[a-zA-Z0-9][a-zA-Z0-9-]*/
NAME_WITH_OWNER_RE = /^(?:#{NAME_RE}|#{OWNER_RE}\/#{NAME_RE})$/
- CUSTOM_COMMANDS = %w[alias create browse compare fork pull-request]
+ CUSTOM_COMMANDS = %w[alias create browse compare fork pull-request ci-status]
def run(args)
slurp_global_flags(args)
@@ -70,6 +70,38 @@ def run(args)
abort "fatal: #{err.message}"
end
+
+ # $ hub ci-status
+ # $ hub ci-status 6f6d9797f9d6e56c3da623a97cfc3f45daf9ae5f
+ # $ hub ci-status master
+ # $ hub ci-status origin/master
+ def ci_status(args)
+ args.shift
+ ref = args.words.first || 'HEAD'
+
+ unless head_project = local_repo.current_project
+ abort "Aborted: the origin remote doesn't point to a GitHub repository."
+ end
+
+ unless sha = local_repo.git_command("rev-parse -q #{ref}")
+ abort "Aborted: no revision could be determined from '#{ref}'"
+ end
+
+ statuses = api_client.statuses(head_project, sha)
+ status = statuses.first
+ ref_state = status ? status['state'] : 'no status'
+
+ exit_code = case ref_state
+ when 'success' then 0
+ when 'failure', 'error' then 1
+ when 'pending' then 2
+ else 3
+ end
+
+ $stdout.puts ref_state
+ exit exit_code
+ end
+
# $ hub pull-request
# $ hub pull-request "My humble contribution"
# $ hub pull-request -i 92
@@ -81,6 +113,10 @@ def pull_request(args)
base_project = local_repo.main_project
head_project = local_repo.current_project
+ unless current_branch
+ abort "Aborted: not currently on any branch."
+ end
+
unless base_project
abort "Aborted: the origin remote doesn't point to a GitHub repository."
end
@@ -104,6 +140,13 @@ def pull_request(args)
exit
when '-f'
force = true
+ when '-F', '--file'
+ file = args.shift
+ text = file == '-' ? $stdin.read : File.read(file)
+ options[:title], options[:body] = read_msg(text)
+ when '-m', '--message'
+ text = args.shift
+ options[:title], options[:body] = read_msg(text)
when '-b'
base_project, options[:base] = from_github_ref.call(args.shift, base_project)
when '-h'
@@ -116,7 +159,10 @@ def pull_request(args)
if url = resolve_github_url(arg) and url.project_path =~ /^issues\/(\d+)/
options[:issue] = $1
base_project = url.project
- elsif !options[:title] then options[:title] = arg
+ elsif !options[:title]
+ options[:title] = arg
+ warn "hub: Specifying pull request title without a flag is deprecated."
+ warn "Please use one of `-m' or `-F' options."
else
abort "invalid argument: #{arg}"
end
@@ -177,8 +223,9 @@ def pull_request(args)
[format, base_branch, remote_branch]
end
- options[:title], options[:body] = pullrequest_editmsg(commit_summary) { |msg|
- msg.puts default_message if default_message
+ options[:title], options[:body] = pullrequest_editmsg(commit_summary) { |msg, initial_message|
+ initial_message ||= default_message
+ msg.puts initial_message if initial_message
msg.puts ""
msg.puts "# Requesting a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}"
msg.puts "#"
@@ -192,8 +239,15 @@ def pull_request(args)
args.executable = 'echo'
args.replace [pull['html_url']]
rescue GitHubAPI::Exceptions
- display_api_exception("creating pull request", $!.response)
+ response = $!.response
+ display_api_exception("creating pull request", response)
+ if 404 == response.status
+ base_url = base_project.web_url.split('://', 2).last
+ warn "Are you sure that #{base_url} exists?"
+ end
exit 1
+ else
+ delete_editmsg
end
# $ hub clone rtomayko/tilt
@@ -209,7 +263,7 @@ def pull_request(args)
# > git clone git@github.com:YOUR_LOGIN/hemingway.git
def clone(args)
ssh = args.delete('-p')
- has_values = /^(--(upload-pack|template|depth|origin|branch|reference)|-[ubo])$/
+ has_values = /^(--(upload-pack|template|depth|origin|branch|reference|name)|-[ubo])$/
idx = 1
while idx < args.length
@@ -238,23 +292,13 @@ def clone(args)
# $ hub submodule add -p wycats/bundler vendor/bundler
# > git submodule add git@github.com:wycats/bundler.git vendor/bundler
#
- # $ hub submodule add -b ryppl ryppl/pip vendor/bundler
- # > git submodule add -b ryppl git://github.com/ryppl/pip.git vendor/pip
+ # $ hub submodule add -b ryppl --name pip ryppl/pip vendor/pip
+ # > git submodule add -b ryppl --name pip git://github.com/ryppl/pip.git vendor/pip
def submodule(args)
return unless index = args.index('add')
args.delete_at index
- branch = args.index('-b') || args.index('--branch')
- if branch
- args.delete_at branch
- branch_name = args.delete_at branch
- end
-
clone(args)
-
- if branch_name
- args.insert branch, '-b', branch_name
- end
args.insert index, 'add'
end
@@ -324,7 +368,7 @@ def fetch(args)
end
projects = names.map { |name|
- unless name =~ /\W/ or remotes.include?(name) or remotes_group(name)
+ unless name !~ /^#{OWNER_RE}$/ or remotes.include?(name) or remotes_group(name)
project = github_project(nil, name)
repo_info = api_client.repo_info(project)
if repo_info.success?
@@ -441,7 +485,8 @@ def am(args)
url = url.sub(%r{(/pull/\d+)/\w*$}, '\1') unless gist
ext = gist ? '.txt' : '.patch'
url += ext unless File.extname(url) == ext
- patch_file = File.join(ENV['TMPDIR'] || '/tmp', "#{gist ? 'gist-' : ''}#{File.basename(url)}")
+ patch_file = File.join(tmp_dir, "#{gist ? 'gist-' : ''}#{File.basename(url)}")
+ # TODO: remove dependency on curl
args.before 'curl', ['-#LA', "hub #{Hub::Version}", url, '-o', patch_file]
args[idx] = patch_file
end
@@ -472,9 +517,14 @@ def fork(args)
end
forked_project = project.owned_by(github_user(project.host))
- if api_client.repo_exists?(forked_project)
- abort "Error creating fork: %s already exists on %s" %
- [ forked_project.name_with_owner, forked_project.host ]
+ existing_repo = api_client.repo_info(forked_project)
+ if existing_repo.success?
+ parent_data = existing_repo.data['parent']
+ parent_url = parent_data && resolve_github_url(parent_data['html_url'])
+ if !parent_url or parent_url.project != project
+ abort "Error creating fork: %s already exists on %s" %
+ [ forked_project.name_with_owner, forked_project.host ]
+ end
else
api_client.fork_repo(project) unless args.noop?
end
@@ -527,7 +577,10 @@ def create(args)
action = "set remote origin"
else
action = "created repository"
- api_client.create_repo(new_project, options) unless args.noop?
+ unless args.noop?
+ repo_data = api_client.create_repo(new_project, options)
+ new_project = github_project(repo_data['full_name'])
+ end
end
url = new_project.git_url(:private => true, :https => https_protocol?)
@@ -599,12 +652,13 @@ def browse(args)
abort "Usage: hub browse [<USER>/]<REPOSITORY>" unless project
+ require 'CGI'
# $ hub browse -- wiki
path = case subpage = args.shift
when 'commits'
- "/commits/#{branch.short_name}"
+ "/commits/#{branch_in_url(branch)}"
when 'tree', NilClass
- "/tree/#{branch.short_name}" if branch and !branch.master?
+ "/tree/#{branch_in_url(branch)}" if branch and !branch.master?
else
"/#{subpage}"
end
@@ -633,7 +687,7 @@ def compare(args)
abort "Usage: hub compare [USER] [<START>...]<END>"
end
else
- sha_or_tag = /(\w{1,2}|\w[\w.-]+\w)/
+ sha_or_tag = /((?:#{OWNER_RE}:)?\w[\w.-]+\w)/
# replaces two dots with three: "sha1...sha2"
range = args.pop.sub(/^#{sha_or_tag}\.\.#{sha_or_tag}$/, '\1...\2')
project = if owner = args.pop then github_project(nil, owner)
@@ -731,6 +785,11 @@ def help(args)
# from the command line.
#
+ def branch_in_url(branch)
+ require 'CGI'
+ CGI.escape(branch.short_name).gsub("%2F", "/")
+ end
+
def api_client
@api_client ||= begin
config_file = ENV['HUB_CONFIG'] || '~/.config/hub'
@@ -817,6 +876,7 @@ def improved_help_text
create Create this repository on GitHub and add GitHub as origin
browse Open a GitHub page in the default browser
compare Open a compare page on GitHub
+ ci-status Show the CI status of a commit
See 'git help <command>' for more information on a specific command.
help
@@ -956,27 +1016,50 @@ def page_stdout
read.close
write.close
end
+ rescue NotImplementedError
+ # fork might not available, such as in JRuby
end
def pullrequest_editmsg(changes)
- message_file = File.join(git_dir, 'PULLREQ_EDITMSG')
+ message_file = pullrequest_editmsg_file
+
+ if File.exists?(message_file)
+ title, body = read_editmsg(message_file)
+ previous_message = [title, body].compact.join("\n\n") if title
+ end
+
File.open(message_file, 'w') { |msg|
- yield msg
+ yield msg, previous_message
if changes
msg.puts "#\n# Changes:\n#"
msg.puts changes.gsub(/^/, '# ').gsub(/ +$/, '')
end
}
+
edit_cmd = Array(git_editor).dup
- edit_cmd << '-c' << 'set ft=gitcommit' if edit_cmd[0] =~ /^[mg]?vim$/
+ edit_cmd << '-c' << 'set ft=gitcommit tw=0 wrap lbr' if edit_cmd[0] =~ /^[mg]?vim$/
edit_cmd << message_file
system(*edit_cmd)
- abort "can't open text editor for pull request message" unless $?.success?
+
+ unless $?.success?
+ # writing was cancelled, or the editor never opened in the first place
+ delete_editmsg(message_file)
+ abort "error using text editor for pull request message"
+ end
+
title, body = read_editmsg(message_file)
abort "Aborting due to empty pull request title" unless title
[title, body]
end
+ def read_msg(message)
+ message.split("\n\n", 2).each {|s| s.strip! }.reject {|s| s.empty? }
+ end
+
+ def pullrequest_editmsg_file
+ File.join(git_dir, 'PULLREQ_EDITMSG')
+ end
+
def read_editmsg(file)
title, body = '', ''
File.open(file, 'r') { |msg|
@@ -992,6 +1075,10 @@ def read_editmsg(file)
[title =~ /\S/ ? title : nil, body =~ /\S/ ? body : nil]
end
+ def delete_editmsg(file = pullrequest_editmsg_file)
+ File.delete(file) if File.exist?(file)
+ end
+
def expand_alias(cmd)
if expanded = git_alias_for(cmd)
if expanded.index('!') != 0
View
12 lib/hub/context.rb
@@ -220,6 +220,7 @@ def self.from_url(url, local_repo)
def initialize(*args)
super
+ self.name = self.name.tr(' ', '-')
self.host ||= (local_repo || LocalRepo).default_host
self.host = host.sub(/^ssh\./i, '') if 'ssh.github.com' == host.downcase
end
@@ -432,7 +433,10 @@ def git_editor
editor = git_command 'var GIT_EDITOR'
editor = ENV[$1] if editor =~ /^\$(\w+)$/
editor = File.expand_path editor if (editor =~ /^[~.]/ or editor.index('/')) and editor !~ /["']/
- editor.shellsplit
+ # avoid shellsplitting "C:\Program Files"
+ if File.exist? editor then [editor]
+ else editor.shellsplit
+ end
end
module System
@@ -441,7 +445,7 @@ module System
# Returns an array, e.g.: ['open']
def browser_launcher
browser = ENV['BROWSER'] || (
- osx? ? 'open' : windows? ? 'start' :
+ osx? ? 'open' : windows? ? %w[cmd /c start] :
%w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm }
)
@@ -481,6 +485,10 @@ def which(cmd)
def command?(name)
!which(name).nil?
end
+
+ def tmp_dir
+ ENV['TMPDIR'] || ENV['TEMP'] || '/tmp'
+ end
end
include System
View
98 lib/hub/github_api.rb
@@ -66,7 +66,7 @@ def fork_repo project
# Public: Create a new project.
def create_repo project, options = {}
- is_org = project.owner != config.username(api_host(project.host))
+ is_org = project.owner.downcase != config.username(api_host(project.host)).downcase
params = { :name => project.name, :private => !!options[:private] }
params[:description] = options[:description] if options[:description]
params[:homepage] = options[:homepage] if options[:homepage]
@@ -77,6 +77,7 @@ def create_repo project, options = {}
res = post "https://%s/user/repos" % api_host(project.host), params
end
res.error! unless res.success?
+ res.data
end
# Public: Fetch info about a pull request.
@@ -109,29 +110,21 @@ def create_pullrequest options
res.data
end
- # Return the pull request corresponding to the current branch
- def get_pullrequest project, branch_name
- for state in ['open', 'closed']
- page = 1
- res = nil
- while page == 1 or res.data.length > 0
- res = get "https://%s/repos/%s/%s/pulls?state=%s&page=%s" %
- [api_host(project.host), project.owner, project.name, state, page]
- res.error! unless res.success?
- res.data.each { |x|
- if branch_name == x['head']['label'].split(':', 0)[1]
- return x['html_url']
- end
- }
- page += 1
- end
- end
- nil
+ def statuses project, sha
+ res = get "https://%s/repos/%s/%s/statuses/%s" %
+ [api_host(project.host), project.owner, project.name, sha]
+
+ res.error! unless res.success?
+ res.data
end
# Methods for performing HTTP requests
#
- # Requires access to a `config` object that implements `proxy_uri(with_ssl)`
+ # Requires access to a `config` object that implements:
+ # - proxy_uri(with_ssl)
+ # - username(host)
+ # - update_username(host, old_username, new_username)
+ # - password(host, user)
module HttpMethods
# Decorator for Net::HTTPResponse
module ResponseMethods
@@ -145,7 +138,12 @@ def error_sentences
data['errors'].map do |err|
case err['code']
when 'custom' then err['message']
- when 'missing_field' then "field '%s' is missing" % err['field']
+ when 'missing_field'
+ %(Missing field: "%s") % err['field']
+ when 'invalid'
+ %(Invalid value for "%s": "%s") % [ err['field'], err['value'] ]
+ when 'unauthorized'
+ %(Not allowed to change field "%s") % err['field']
end
end.compact if data['errors']
end
@@ -159,10 +157,17 @@ def post url, params = nil
perform_request url, :Post do |req|
if params
req.body = JSON.dump params
- req['Content-Type'] = 'application/json'
+ req['Content-Type'] = 'application/json;charset=utf-8'
end
yield req if block_given?
- req['Content-Length'] = req.body ? req.body.length : 0
+ req['Content-Length'] = byte_size req.body
+ end
+ end
+
+ def byte_size str
+ if str.respond_to? :bytesize then str.bytesize
+ elsif str.respond_to? :length then str.length
+ else 0
end
end
@@ -180,13 +185,17 @@ def perform_request url, type
create_connection host_url
end
+ req['User-Agent'] = "Hub #{Hub::VERSION}"
apply_authentication(req, url)
yield req if block_given?
- res = http.start { http.request(req) }
- res.extend ResponseMethods
- res
- rescue SocketError => err
- raise Context::FatalError, "error with #{type.to_s.upcase} #{url} (#{err.message})"
+
+ begin
+ res = http.start { http.request(req) }
+ res.extend ResponseMethods
+ return res
+ rescue SocketError => err
+ raise Context::FatalError, "error with #{type.to_s.upcase} #{url} (#{err.message})"
+ end
end
def request_uri url
@@ -240,10 +249,18 @@ def apply_authentication req, url
if (req.path =~ /\/authorizations$/)
super
else
+ refresh = false
user = url.user || config.username(url.host)
token = config.oauth_token(url.host, user) {
+ refresh = true
obtain_oauth_token url.host, user
}
+ if refresh
+ # get current user info user to persist correctly capitalized login name
+ res = get "https://#{url.host}/user"
+ res.error! unless res.success?
+ config.update_username(url.host, user, res.data['login'])
+ end
req['Authorization'] = "token #{token}"
end
end
@@ -336,6 +353,7 @@ def normalize_host host
end
def username host
+ return ENV['GITHUB_USER'] unless ENV['GITHUB_USER'].to_s.empty?
host = normalize_host host
@data.fetch_user host do
if block_given? then yield
@@ -344,6 +362,12 @@ def username host
end
end
+ def update_username host, old_username, new_username
+ entry = @data.entry_for_user(normalize_host(host), old_username)
+ entry['user'] = new_username
+ @data.save
+ end
+
def api_token host, user
host = normalize_host host
@data.fetch_value host, user, :api_token do
@@ -354,6 +378,7 @@ def api_token host, user
end
def password host, user
+ return ENV['GITHUB_PASSWORD'] unless ENV['GITHUB_PASSWORD'].to_s.empty?
host = normalize_host host
@password_cache["#{user}@#{host}"] ||= prompt_password host, user
end
@@ -380,12 +405,14 @@ def prompt_password host, user
end
end
- # FIXME: probably not cross-platform
+ NULL = defined?(File::NULL) ? File::NULL :
+ File.exist?('/dev/null') ? '/dev/null' : 'NUL'
+
def askpass
- tty_state = `stty -g`
+ tty_state = `stty -g 2>#{NULL}`
system 'stty raw -echo -icanon isig' if $?.success?
pass = ''
- while char = $stdin.getbyte and !(char == 13 or char == 10)
+ while char = getbyte($stdin) and !(char == 13 or char == 10)
if char == 127 or char == 8
pass[-1,1] = '' unless pass.empty?
else
@@ -397,6 +424,15 @@ def askpass
system "stty #{tty_state}" unless tty_state.empty?
end
+ def getbyte(io)
+ if io.respond_to?(:getbyte)
+ io.getbyte
+ else
+ # In Ruby <= 1.8.6, getc behaved the same
+ io.getc
+ end
+ end
+
def proxy_uri(with_ssl)
env_name = "HTTP#{with_ssl ? 'S' : ''}_PROXY"
if proxy = ENV[env_name] || ENV[env_name.downcase] and !proxy.empty?
View
20 lib/hub/json.rb
@@ -111,10 +111,22 @@ def generate_type(obj)
end
end
- def generate_String(str) str.inspect end
- alias generate_Numeric generate_String
- alias generate_TrueClass generate_String
- alias generate_FalseClass generate_String
+ ESC_MAP = Hash.new {|h,k| k }.update \
+ "\r" => 'r',
+ "\n" => 'n',
+ "\f" => 'f',
+ "\t" => 't',
+ "\b" => 'b'
+
+ def generate_String(str)
+ escaped = str.gsub(/[\r\n\f\t\b"\\]/) { "\\#{ESC_MAP[$&]}"}
+ %("#{escaped}")
+ end
+
+ def generate_simple(obj) obj.inspect end
+ alias generate_Numeric generate_simple
+ alias generate_TrueClass generate_simple
+ alias generate_FalseClass generate_simple
def generate_Symbol(sym) generate_String(sym.to_s) end
View
18 lib/hub/runner.rb
@@ -48,17 +48,12 @@ def execute
if args.noop?
puts commands
elsif not args.skip?
- if args.chained?
- execute_command_chain
- else
- exec(*args.to_exec)
- end
+ execute_command_chain args.commands
end
end
# Runs multiple commands in succession; exits at first failure.
- def execute_command_chain
- commands = args.commands
+ def execute_command_chain commands
commands.each_with_index do |cmd, i|
if cmd.respond_to?(:call) then cmd.call
elsif i == commands.length - 1
@@ -69,5 +64,14 @@ def execute_command_chain
end
end
end
+
+ # Special-case `echo` for Windows
+ def exec *args
+ if args.first == 'echo' && Context::windows?
+ puts args[1..-1].join(' ')
+ else
+ super
+ end
+ end
end
end
View
2  lib/hub/version.rb
@@ -1,3 +1,3 @@
module Hub
- Version = VERSION = '1.10.2'
+ Version = VERSION = '1.10.6'
end
View
72 man/hub.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "HUB" "1" "May 2012" "DEFUNKT" "Git Manual"
+.TH "HUB" "1" "May 2013" "DEFUNKT" "Git Manual"
.
.SH "NAME"
\fBhub\fR \- git + hub = github
@@ -61,7 +61,10 @@
\fBgit fork\fR [\fB\-\-no\-remote\fR]
.
.br
-\fBgit pull\-request\fR [\fB\-f\fR] [\fITITLE\fR|\fB\-i\fR \fIISSUE\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR]
+\fBgit pull\-request\fR [\fB\-f\fR] [\fB\-m\fR \fIMESSAGE\fR|\fB\-F\fR \fIFILE\fR|\fB\-i\fR \fIISSUE\fR|\fIISSUE\-URL\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR]
+.
+.br
+\fBgit ci\-status\fR [\fICOMMIT\fR]
.
.SH "DESCRIPTION"
hub enhances various git commands to ease most common workflows with GitHub\.
@@ -125,36 +128,55 @@ Display enhanced git\-help(1)\.
.P
hub also adds some custom commands that are otherwise not present in git:
.
-.TP
-\fBgit create\fR [\fINAME\fR] [\fB\-p\fR] [\fB\-d\fR \fIDESCRIPTION\fR] [\fB\-h\fR \fIHOMEPAGE\fR]
-Create a new public GitHub repository from the current git repository and add remote \fBorigin\fR at "git@github\.com:\fIUSER\fR/\fIREPOSITORY\fR\.git"; \fIUSER\fR is your GitHub username and \fIREPOSITORY\fR is the current working directory name\. To explicitly name the new repository, pass in \fINAME\fR, optionally in \fIORGANIZATION\fR/\fINAME\fR form to create under an organization you\'re a member of\. With \fB\-p\fR, create a private repository, and with \fB\-d\fR and \fB\-h\fR set the repository\'s description and homepage URL, respectively\.
+.IP "\(bu" 4
+\fBgit create\fR [\fINAME\fR] [\fB\-p\fR] [\fB\-d\fR \fIDESCRIPTION\fR] [\fB\-h\fR \fIHOMEPAGE\fR]: Create a new public GitHub repository from the curr