Skip to content

Commit

Permalink
hub fork forks a project on GitHub and adds the new remote
Browse files Browse the repository at this point in the history
Usage example:

  $ git fork
  (forks "defunkt/hub" on GitHub)
  (git remote add mislav git@github.com:mislav/hub.git)
  new remote: mislav

Use `git fork --no-remote` to skip adding the new remote.

Tests use Webmock for stubbing out HTTP requests:

  $ gem install webmock
  • Loading branch information
mislav authored and defunkt committed Apr 7, 2010
1 parent 782897f commit 6e444c7
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 6 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ superpowers:
$ git remote add origin
> git remote add origin git://github.com/YOUR_USER/CURRENT_REPO.git

### git fork

$ git fork
... hardcore forking action ...
> git remote add YOUR_USER git@github.com:YOUR_USER/CURRENT_REPO.git

Forks the original repo on GitHub and adds the new remote under your username.
It requires your GitHub token to be present; see "GitHub login" below for details.

### git init

$ git init -g
Expand Down Expand Up @@ -177,7 +186,8 @@ If you see nothing, you need to set the config setting:

$ git config --global github.user YOUR_USER

See <http://github.com/guides/local-github-config> for more information.
For commands that require write access to GitHub (such as `fork`), you'll want to
setup "github.token" as well. See [local GitHub config guide][2] for more information.


Configuration
Expand Down Expand Up @@ -243,3 +253,4 @@ Chris Wanstrath :: chris@ozmm.org :: @defunkt
[0]: http://help.github.com/forking/
[1]: http://github.com/defunkt/hub/issues
[speed]: http://gist.github.com/284823
[2]: http://github.com/guides/local-github-config
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ task :default => :test

Rake::TestTask.new do |t|
t.libs << 'lib'
t.ruby_opts << '-rubygems'
t.pattern = 'test/**/*_test.rb'
t.verbose = false
end
Expand Down
42 changes: 42 additions & 0 deletions lib/hub/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ module Commands

# Templates and useful information.
USER = `git config --global github.user`.chomp
TOKEN = `git config --global github.token`.chomp
ORIGIN = `git config remote.origin.url`.chomp
HTTP_CLONE = `git config --global hub.http-clone`.chomp == 'yes'
PUBLIC = (HTTP_CLONE ? 'http' : 'git') + '://github.com/%s/%s.git'
PRIVATE = 'git@github.com:%s/%s.git'
LGHCONF = "http://github.com/guides/local-github-config"
API_REPO = 'http://github.com/api/v2/yaml/repos/show/%s/%s'
API_FORK = 'http://github.com/api/v2/yaml/repos/fork/%s/%s'

# Set the repo name based on the current origin or, as a fallback,
# the cwd.
Expand Down Expand Up @@ -146,6 +149,27 @@ def init(args)
args.after "git remote add origin #{url}"
end
end

def fork(args)
require 'net/http'

# can't do anything without token and original owner name
if github_user and github_token and !OWNER.empty?
if own_repo_exists?
puts "#{github_user}/#{REPO} already exists on GitHub"
else
fork_repo
end

if args.include?('--no-remote')
exit
else
url = PRIVATE % [ github_user, REPO ]
args.replace %W"remote add #{github_user} #{url}"
args.after { puts "new remote: #{github_user}" }
end
end
end

# $ hub push origin,staging cool-feature
# > git push origin cool-feature
Expand Down Expand Up @@ -349,6 +373,14 @@ def github_user
USER
end
end

def github_token
if TOKEN.empty?
abort "** No GitHub token set. See #{LGHCONF}"
else
TOKEN
end
end

# Returns the terminal-formatted manpage, ready to be printed to
# the screen.
Expand Down Expand Up @@ -433,5 +465,15 @@ def page_stdout
write.close
end
end

def own_repo_exists?
url = API_REPO % [USER, REPO]
Net::HTTPSuccess === Net::HTTP.get_response(URI(url))
end

def fork_repo
url = API_FORK % [OWNER, REPO]
Net::HTTP.post_form(URI(url), 'login' => USER, 'token' => TOKEN)
end
end
end
18 changes: 17 additions & 1 deletion man/hub.1
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ your GitHub login. With \fB\-p\fR, use private remote
"git@github.com:\fIUSER\fR/\fIREPOSITORY\fR.git".
.
.TP
\fBgit fork\fR [\fB\-\-no\-remote\fR]
Forks the original project (as specified in "origin" remote) on GitHub
and adds a new remote named \fIUSER\fR referencing the newly created repo.
Requires \fBgithub.token\fR to be set (see CONFIGURATION).
.
.TP
\fBgit help\fR
Display enhanced git\-help(1).
.
Expand All @@ -95,12 +101,13 @@ $ git config \-\-global github.user
.IP "" 0
.
.P
Or, set the GitHub username with:
Or, set the GitHub username and token with:
.
.IP "" 4
.
.nf
$ git config \-\-global github.user <username>
$ git config \-\-global github.token <token>
.
.fi
.
Expand Down Expand Up @@ -151,6 +158,15 @@ $ git remote add origin
.
.fi
.
.SS "git fork"
.
.nf
$ git fork
... hardcore forking action ...
> git remote add YOUR_USER git@github.com:YOUR_USER/CURRENT_REPO.git
.
.fi
.
.SS "git init"
.
.nf
Expand Down
13 changes: 12 additions & 1 deletion man/hub.1.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion man/hub.1.ron
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ After configuring the alias, the following commands have superpowers:
your GitHub login. With `-p`, use private remote
"git@github.com:<USER>/<REPOSITORY>.git".

* `git fork` [`--no-remote`]:
Forks the original project (as specified in "origin" remote) on GitHub
and adds a new remote named <USER> referencing the newly created repo.
Requires `github.token` to be set (see CONFIGURATION).

* `git help`:
Display enhanced git-help(1).

Expand All @@ -68,9 +73,10 @@ Use git-config(1) to display the currently configured GitHub username:

$ git config --global github.user

Or, set the GitHub username with:
Or, set the GitHub username and token with:

$ git config --global github.user <username>
$ git config --global github.token <token>

See <http://github.com/guides/local-github-config> for more
information.
Expand Down Expand Up @@ -107,6 +113,12 @@ cloning:
$ git remote add origin
> git remote add origin git://github.com/YOUR_USER/CURRENT_REPO.git

### git fork

$ git fork
... hardcore forking action ...
> git remote add YOUR_USER git@github.com:YOUR_USER/CURRENT_REPO.git

### git init

$ git init -g
Expand Down
10 changes: 8 additions & 2 deletions test/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,22 @@ def Hub(args)
# If a block is given it will be run in the child process before
# execution begins. You can use this to monkeypatch or fudge the
# environment before running hub.
def hub(args)
def hub(args, input = nil)
parent_read, child_write = IO.pipe
child_read, parent_write = IO.pipe if input

fork do
yield if block_given?
$stdin.reopen(child_read) if input
$stdout.reopen(child_write)
$stderr.reopen(child_write)
Hub(args).execute
end


if input
parent_write.write input
parent_write.close
end
child_write.close
parent_read.read
end
Expand Down
32 changes: 32 additions & 0 deletions test/hub_test.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
$LOAD_PATH.unshift File.dirname(__FILE__)
require 'helper'
require 'webmock/test_unit'

class HubTest < Test::Unit::TestCase
include WebMock

def setup
Hub::Commands::REPO.replace("hub")
Hub::Commands::USER.replace("tpw")
Hub::Commands::TOKEN.replace("abc123")
Hub::Commands::OWNER.replace("defunkt")
end

Expand Down Expand Up @@ -196,6 +200,34 @@ def test_push_more
assert_equal "git push origin cool-feature", h.command
assert_equal "git push staging cool-feature; git push qa cool-feature", h.after
end

def test_fork
stub_request(:get, "github.com/api/v2/yaml/repos/show/tpw/hub").to_return(:status => 404)
stub_request(:post, "github.com/api/v2/yaml/repos/fork/defunkt/hub").with { |req|
params = Hash[*req.body.split(/[&=]/)]
params == { 'login'=>'tpw', 'token'=>'abc123' }
}

expected = "remote add tpw git@github.com:tpw/hub.git\n"
expected << "new remote: tpw\n"
assert_equal expected, hub("fork") { ENV['GIT'] = 'echo' }
end

def test_fork_no_remote
stub_request(:get, "github.com/api/v2/yaml/repos/show/tpw/hub").to_return(:status => 404)
stub_request(:post, "github.com/api/v2/yaml/repos/fork/defunkt/hub")

assert_equal "", hub("fork --no-remote") { ENV['GIT'] = 'echo' }
end

def test_fork_already_exists
stub_request(:get, "github.com/api/v2/yaml/repos/show/tpw/hub").to_return(:status => 200)

expected = "tpw/hub already exists on GitHub\n"
expected << "remote add tpw git@github.com:tpw/hub.git\n"
expected << "new remote: tpw\n"
assert_equal expected, hub("fork") { ENV['GIT'] = 'echo' }
end

def test_version
out = hub('--version')
Expand Down

0 comments on commit 6e444c7

Please sign in to comment.