зеркало из https://github.com/mislav/hub.git
Automatically add the `upstream` remote when cloning a fork
Example: $ hub clone my-fork > origin: git://github.com/MYUSER/my-fork.git > upstream: git://github.com/OWNER/original-project.git $ hub clone --origin=upstream my-fork > (no origin remote) > upstream: git://github.com/OWNER/original-project.git
This commit is contained in:
Родитель
90523e4da6
Коммит
7dd2e9617f
|
@ -5,7 +5,9 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/github/hub/cmd"
|
||||
"github.com/github/hub/github"
|
||||
"github.com/github/hub/ui"
|
||||
"github.com/github/hub/utils"
|
||||
)
|
||||
|
||||
|
@ -72,17 +74,96 @@ func transformCloneArgs(args *Args) {
|
|||
p.RegisterValue("--shallow-since")
|
||||
p.RegisterValue("--template")
|
||||
p.RegisterValue("--upload-pack", "-u")
|
||||
p.RegisterBool("--quiet", "-q")
|
||||
}
|
||||
p.Parse(args.Params)
|
||||
|
||||
upstreamName := "upstream"
|
||||
originName := p.Value("--origin")
|
||||
quiet := p.Bool("--quiet")
|
||||
targetDir := ""
|
||||
|
||||
nameWithOwnerRegexp := regexp.MustCompile(NameWithOwnerRe)
|
||||
for _, i := range p.PositionalIndices {
|
||||
a := args.Params[i]
|
||||
if nameWithOwnerRegexp.MatchString(a) && !isCloneable(a) {
|
||||
url := getCloneUrl(a, isSSH, args.Command != "submodule")
|
||||
args.ReplaceParam(i, url)
|
||||
for n, i := range p.PositionalIndices {
|
||||
switch n {
|
||||
case 0:
|
||||
repo := args.Params[i]
|
||||
if nameWithOwnerRegexp.MatchString(repo) && !isCloneable(repo) {
|
||||
name := repo
|
||||
owner := ""
|
||||
if strings.Contains(name, "/") {
|
||||
split := strings.SplitN(name, "/", 2)
|
||||
owner = split[0]
|
||||
name = split[1]
|
||||
}
|
||||
|
||||
config := github.CurrentConfig()
|
||||
host, err := config.DefaultHost()
|
||||
if err != nil {
|
||||
utils.Check(github.FormatError("cloning repository", err))
|
||||
}
|
||||
if owner == "" {
|
||||
owner = host.User
|
||||
}
|
||||
|
||||
expectWiki := strings.HasSuffix(name, ".wiki")
|
||||
if expectWiki {
|
||||
name = strings.TrimSuffix(name, ".wiki")
|
||||
}
|
||||
|
||||
project := github.NewProject(owner, name, host.Host)
|
||||
gh := github.NewClient(project.Host)
|
||||
repo, err := gh.Repository(project)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "HTTP 404") {
|
||||
err = fmt.Errorf("Error: repository %s/%s doesn't exist", project.Owner, project.Name)
|
||||
}
|
||||
utils.Check(err)
|
||||
}
|
||||
|
||||
owner = repo.Owner.Login
|
||||
name = repo.Name
|
||||
if expectWiki {
|
||||
if !repo.HasWiki {
|
||||
utils.Check(fmt.Errorf("Error: %s/%s doesn't have a wiki", owner, name))
|
||||
} else {
|
||||
name = name + ".wiki"
|
||||
}
|
||||
}
|
||||
|
||||
if !isSSH &&
|
||||
args.Command != "submodule" &&
|
||||
!github.IsHttpsProtocol() {
|
||||
isSSH = repo.Private || repo.Permissions.Push
|
||||
}
|
||||
|
||||
targetDir = name
|
||||
url := project.GitURL(name, owner, isSSH)
|
||||
args.ReplaceParam(i, url)
|
||||
|
||||
if repo.Parent != nil && args.Command == "clone" && originName != upstreamName {
|
||||
args.AfterFn(func() error {
|
||||
upstreamUrl := project.GitURL(repo.Parent.Name, repo.Parent.Owner.Login, repo.Parent.Private)
|
||||
addRemote := cmd.New("git")
|
||||
addRemote.WithArgs("-C", targetDir)
|
||||
addRemote.WithArgs("remote", "add", "-f", upstreamName, upstreamUrl)
|
||||
if !quiet {
|
||||
ui.Errorf("Adding remote '%s' for the '%s/%s' repo\n",
|
||||
upstreamName, repo.Parent.Owner.Login, repo.Parent.Name)
|
||||
}
|
||||
output, err := addRemote.CombinedOutput()
|
||||
if err != nil {
|
||||
ui.Errorln(output)
|
||||
}
|
||||
return err
|
||||
})
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
targetDir = args.Params[i]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,63 +175,3 @@ func parseClonePrivateFlag(args *Args) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
func getCloneUrl(nameWithOwner string, isSSH, allowSSH bool) string {
|
||||
name := nameWithOwner
|
||||
owner := ""
|
||||
if strings.Contains(name, "/") {
|
||||
split := strings.SplitN(name, "/", 2)
|
||||
owner = split[0]
|
||||
name = split[1]
|
||||
}
|
||||
|
||||
var host *github.Host
|
||||
if owner == "" {
|
||||
config := github.CurrentConfig()
|
||||
h, err := config.DefaultHost()
|
||||
if err != nil {
|
||||
utils.Check(github.FormatError("cloning repository", err))
|
||||
}
|
||||
|
||||
host = h
|
||||
owner = host.User
|
||||
}
|
||||
|
||||
var hostStr string
|
||||
if host != nil {
|
||||
hostStr = host.Host
|
||||
}
|
||||
|
||||
expectWiki := strings.HasSuffix(name, ".wiki")
|
||||
if expectWiki {
|
||||
name = strings.TrimSuffix(name, ".wiki")
|
||||
}
|
||||
|
||||
project := github.NewProject(owner, name, hostStr)
|
||||
gh := github.NewClient(project.Host)
|
||||
repo, err := gh.Repository(project)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "HTTP 404") {
|
||||
err = fmt.Errorf("Error: repository %s/%s doesn't exist", project.Owner, project.Name)
|
||||
}
|
||||
utils.Check(err)
|
||||
}
|
||||
|
||||
owner = repo.Owner.Login
|
||||
name = repo.Name
|
||||
if expectWiki {
|
||||
if !repo.HasWiki {
|
||||
utils.Check(fmt.Errorf("Error: %s/%s doesn't have a wiki", owner, name))
|
||||
} else {
|
||||
name = name + ".wiki"
|
||||
}
|
||||
}
|
||||
|
||||
if !isSSH &&
|
||||
allowSSH &&
|
||||
!github.IsHttpsProtocol() {
|
||||
isSSH = repo.Private || repo.Permissions.Push
|
||||
}
|
||||
|
||||
return project.GitURL(name, owner, isSSH)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,61 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone rtomayko/ronn`
|
||||
Then it should clone "git://github.com/rtomayko/ronn.git"
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone a fork
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
get('/repos/defunkt/ronn1') {
|
||||
json :private => false,
|
||||
:name => 'ronn1', :owner => { :login => 'defunkt' },
|
||||
:parent => {
|
||||
:private => true,
|
||||
:name => 'ronn', :owner => { :login => 'rtomayko' }
|
||||
},
|
||||
:permissions => { :push => false }
|
||||
}
|
||||
"""
|
||||
When I successfully run `hub clone defunkt/ronn1`
|
||||
Then the stderr should contain "Adding remote 'upstream' for the 'rtomayko/ronn' repo"
|
||||
When I cd to "ronn1"
|
||||
Then the url for "origin" should be "git://github.com/defunkt/ronn1.git"
|
||||
Then the url for "upstream" should be "git@github.com:rtomayko/ronn.git"
|
||||
|
||||
Scenario: Clone a fork into a custom directory
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
get('/repos/defunkt/ronn1') {
|
||||
json :private => false,
|
||||
:name => 'ronn1', :owner => { :login => 'defunkt' },
|
||||
:parent => {
|
||||
:private => true,
|
||||
:name => 'ronn', :owner => { :login => 'rtomayko' }
|
||||
},
|
||||
:permissions => { :push => false }
|
||||
}
|
||||
"""
|
||||
When I successfully run `hub clone defunkt/ronn1 my-fork`
|
||||
And I cd to "my-fork"
|
||||
Then the url for "origin" should be "git://github.com/defunkt/ronn1.git"
|
||||
Then the url for "upstream" should be "git@github.com:rtomayko/ronn.git"
|
||||
|
||||
Scenario: Clone a fork as "upstream"
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
get('/repos/defunkt/ronn1') {
|
||||
json :private => false,
|
||||
:name => 'ronn1', :owner => { :login => 'defunkt' },
|
||||
:parent => {
|
||||
:private => true,
|
||||
:name => 'ronn', :owner => { :login => 'rtomayko' }
|
||||
},
|
||||
:permissions => { :push => false }
|
||||
}
|
||||
"""
|
||||
When I successfully run `hub clone -o upstream defunkt/ronn1`
|
||||
And I cd to "ronn1"
|
||||
Then the url for "upstream" should be "git://github.com/defunkt/ronn1.git"
|
||||
And there should be no "origin" remote
|
||||
|
||||
Scenario: Clone a public repo with period in name
|
||||
Given the GitHub API server:
|
||||
|
@ -26,7 +80,6 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone hookio/hook.js`
|
||||
Then it should clone "git://github.com/hookio/hook.js.git"
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone a public repo that starts with a period
|
||||
Given the GitHub API server:
|
||||
|
@ -39,7 +92,6 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone zhuangya/.vim`
|
||||
Then it should clone "git://github.com/zhuangya/.vim.git"
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone a repo even if same-named directory exists
|
||||
Given the GitHub API server:
|
||||
|
@ -53,7 +105,6 @@ Feature: hub clone
|
|||
And a directory named "rtomayko/ronn"
|
||||
When I successfully run `hub clone rtomayko/ronn`
|
||||
Then it should clone "git://github.com/rtomayko/ronn.git"
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone a public repo with HTTPS
|
||||
Given HTTPS is preferred
|
||||
|
@ -67,7 +118,6 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone rtomayko/ronn`
|
||||
Then it should clone "https://github.com/rtomayko/ronn.git"
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone command aliased
|
||||
Given the GitHub API server:
|
||||
|
@ -81,7 +131,6 @@ Feature: hub clone
|
|||
When I successfully run `git config --global alias.c "clone --bare"`
|
||||
And I successfully run `hub c rtomayko/ronn`
|
||||
Then "git clone --bare git://github.com/rtomayko/ronn.git" should be run
|
||||
And there should be no output
|
||||
|
||||
Scenario: Unchanged public clone
|
||||
When I successfully run `hub clone git://github.com/rtomayko/ronn.git`
|
||||
|
@ -90,39 +139,32 @@ Feature: hub clone
|
|||
Scenario: Unchanged public clone with path
|
||||
When I successfully run `hub clone git://github.com/rtomayko/ronn.git ronnie`
|
||||
Then the git command should be unchanged
|
||||
And there should be no output
|
||||
|
||||
Scenario: Unchanged private clone
|
||||
When I successfully run `hub clone git@github.com:rtomayko/ronn.git`
|
||||
Then the git command should be unchanged
|
||||
And there should be no output
|
||||
|
||||
Scenario: Unchanged clone with complex arguments
|
||||
When I successfully run `hub clone --template=one/two git://github.com/defunkt/resque.git --origin master resquetastic`
|
||||
Then the git command should be unchanged
|
||||
And there should be no output
|
||||
|
||||
Scenario: Unchanged local clone
|
||||
When I successfully run `hub clone ./dotfiles`
|
||||
Then the git command should be unchanged
|
||||
And there should be no output
|
||||
|
||||
Scenario: Unchanged local clone with destination
|
||||
Given a directory named ".git"
|
||||
When I successfully run `hub clone -l . ../copy`
|
||||
Then the git command should be unchanged
|
||||
And there should be no output
|
||||
|
||||
Scenario: Unchanged local clone from bare repo
|
||||
Given a bare git repo in "rtomayko/ronn"
|
||||
When I successfully run `hub clone rtomayko/ronn`
|
||||
Then the git command should be unchanged
|
||||
And there should be no output
|
||||
|
||||
Scenario: Unchanged clone with host alias
|
||||
When I successfully run `hub clone shortcut:git/repo.git`
|
||||
Then the git command should be unchanged
|
||||
And there should be no output
|
||||
|
||||
Scenario: Preview cloning a private repo
|
||||
Given the GitHub API server:
|
||||
|
@ -148,7 +190,6 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone -p rtomayko/ronn`
|
||||
Then it should clone "git@github.com:rtomayko/ronn.git"
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone my repo
|
||||
Given the GitHub API server:
|
||||
|
@ -161,7 +202,6 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone dotfiles`
|
||||
Then it should clone "git@github.com:mislav/dotfiles.git"
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone my repo that doesn't exist
|
||||
Given the GitHub API server:
|
||||
|
@ -185,7 +225,6 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone --bare -o master dotfiles`
|
||||
Then "git clone --bare -o master git@github.com:mislav/dotfiles.git" should be run
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone repo to which I have push access to
|
||||
Given the GitHub API server:
|
||||
|
@ -198,7 +237,6 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone sstephenson/rbenv`
|
||||
Then "git clone git@github.com:sstephenson/rbenv.git" should be run
|
||||
And there should be no output
|
||||
|
||||
Scenario: Preview cloning a repo I have push access to
|
||||
Given the GitHub API server:
|
||||
|
@ -226,19 +264,16 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone myorg/myrepo`
|
||||
Then it should clone "git@git.my.org:myorg/myrepo.git"
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone from existing directory is a local clone
|
||||
Given a directory named "dotfiles/.git"
|
||||
When I successfully run `hub clone dotfiles`
|
||||
Then the git command should be unchanged
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone from git bundle is a local clone
|
||||
Given a git bundle named "my-bundle"
|
||||
When I successfully run `hub clone my-bundle`
|
||||
Then the git command should be unchanged
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone a wiki
|
||||
Given the GitHub API server:
|
||||
|
@ -252,7 +287,6 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone rtomayko/ronn.wiki`
|
||||
Then it should clone "git://github.com/RTomayko/ronin.wiki.git"
|
||||
And there should be no output
|
||||
|
||||
Scenario: Clone a nonexisting wiki
|
||||
Given the GitHub API server:
|
||||
|
@ -285,4 +319,3 @@ Feature: hub clone
|
|||
"""
|
||||
When I successfully run `hub clone rtomayko/ronn`
|
||||
Then it should clone "git://github.com/RTomayko/ronin.git"
|
||||
And there should be no output
|
||||
|
|
|
@ -3,6 +3,23 @@
|
|||
# executed in testing. It logs commands to "~/.history" so afterwards it can be
|
||||
# asserted that they ran.
|
||||
|
||||
global_args=()
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"--git-dir="* )
|
||||
global_args+=("$1")
|
||||
shift 1
|
||||
;;
|
||||
-c | -C )
|
||||
global_args+=("$1" "$2")
|
||||
shift 2
|
||||
;;
|
||||
* )
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
command="$1"
|
||||
[ "$command" = "config" ] || echo git "$@" >> "$HOME"/.history
|
||||
|
||||
|
@ -12,7 +29,22 @@ case "$command" in
|
|||
echo branch
|
||||
echo commit
|
||||
;;
|
||||
"clone" | "fetch" | "pull" | "push" )
|
||||
clone )
|
||||
numargs="${#@}"
|
||||
url="${@:$numargs:1}"
|
||||
dir="$(basename "${url%.git}")"
|
||||
[ "${url/:/}" != "$url" ] || url="${@:$numargs-1:1}"
|
||||
origin_name="origin"
|
||||
[ "$2" != "-o" ] || origin_name="$3"
|
||||
printf "Cloning into '%s'...\n" "$dir"
|
||||
mkdir -p "$dir" 2>/dev/null && ( cd "$dir"
|
||||
"$HUB_SYSTEM_GIT" init --quiet
|
||||
"$HUB_SYSTEM_GIT" commit --quiet --allow-empty -m 'initial commit'
|
||||
"$HUB_SYSTEM_GIT" remote add "$origin_name" "$url"
|
||||
)
|
||||
true
|
||||
;;
|
||||
fetch | pull | push )
|
||||
# don't actually execute these commands
|
||||
exit
|
||||
;;
|
||||
|
@ -21,9 +53,9 @@ case "$command" in
|
|||
if [ "$command $2 $3" = "remote add -f" ]; then
|
||||
subcommand=$2
|
||||
shift 3
|
||||
exec "$HUB_SYSTEM_GIT" $command $subcommand "$@"
|
||||
exec "$HUB_SYSTEM_GIT" "${global_args[@]}" $command $subcommand "$@"
|
||||
else
|
||||
exec "$HUB_SYSTEM_GIT" "$@"
|
||||
exec "$HUB_SYSTEM_GIT" "${global_args[@]}" "$@"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
|
Загрузка…
Ссылка в новой задаче