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:
Mislav Marohnić 2018-12-21 14:21:54 +01:00
Родитель 90523e4da6
Коммит 7dd2e9617f
3 изменённых файлов: 177 добавлений и 91 удалений

Просмотреть файл

@ -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