Skip to content

Commit 6e979c6

Browse files
authored
Merge pull request cli#12686 from 4RH1T3CT0R7/add-no-upstream-flag
Add --no-upstream flag to repo clone
2 parents d983ae0 + 6045a59 commit 6e979c6

2 files changed

Lines changed: 116 additions & 24 deletions

File tree

pkg/cmd/repo/clone/clone.go

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type CloneOptions struct {
2727
GitArgs []string
2828
Repository string
2929
UpstreamName string
30+
NoUpstream bool
3031
}
3132

3233
func NewCmdClone(f *cmdutil.Factory, runF func(*CloneOptions) error) *cobra.Command {
@@ -60,6 +61,7 @@ func NewCmdClone(f *cmdutil.Factory, runF func(*CloneOptions) error) *cobra.Comm
6061
the remote after the owner of the parent repository.
6162
6263
If the repository is a fork, its parent repository will be set as the default remote repository.
64+
To skip this behavior, use %[1]s--no-upstream%[1]s.
6365
`, "`"),
6466
Example: heredoc.Doc(`
6567
# Clone a repository from a specific org
@@ -77,6 +79,9 @@ func NewCmdClone(f *cmdutil.Factory, runF func(*CloneOptions) error) *cobra.Comm
7779
7880
# Clone a repository with additional git clone flags
7981
$ gh repo clone cli/cli -- --depth=1
82+
83+
# Clone a fork without adding an upstream remote
84+
$ gh repo clone myfork --no-upstream
8085
`),
8186
RunE: func(cmd *cobra.Command, args []string) error {
8287
opts.Repository = args[0]
@@ -91,6 +96,8 @@ func NewCmdClone(f *cmdutil.Factory, runF func(*CloneOptions) error) *cobra.Comm
9196
}
9297

9398
cmd.Flags().StringVarP(&opts.UpstreamName, "upstream-remote-name", "u", "upstream", "Upstream remote name when cloning a fork")
99+
cmd.Flags().BoolVar(&opts.NoUpstream, "no-upstream", false, "Do not add an upstream remote when cloning a fork")
100+
cmd.MarkFlagsMutuallyExclusive("upstream-remote-name", "no-upstream")
94101
cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
95102
if err == pflag.ErrHelp {
96103
return err
@@ -187,37 +194,43 @@ func cloneRun(opts *CloneOptions) error {
187194

188195
// If the repo is a fork, add the parent as an upstream remote and set the parent as the default repo.
189196
if canonicalRepo.Parent != nil {
190-
protocol := cfg.GitProtocol(canonicalRepo.Parent.RepoHost()).Value
191-
upstreamURL := ghrepo.FormatRemoteURL(canonicalRepo.Parent, protocol)
192-
193-
upstreamName := opts.UpstreamName
194-
if opts.UpstreamName == "@owner" {
195-
upstreamName = canonicalRepo.Parent.RepoOwner()
196-
}
197-
198197
gc := gitClient.Copy()
199198
gc.RepoDir = cloneDir
200199

201-
if _, err := gc.AddRemote(ctx, upstreamName, upstreamURL, []string{canonicalRepo.Parent.DefaultBranchRef.Name}); err != nil {
202-
return err
203-
}
200+
if opts.NoUpstream {
201+
if err := gc.SetRemoteResolution(ctx, "origin", "base"); err != nil {
202+
return err
203+
}
204+
} else {
205+
protocol := cfg.GitProtocol(canonicalRepo.Parent.RepoHost()).Value
206+
upstreamURL := ghrepo.FormatRemoteURL(canonicalRepo.Parent, protocol)
204207

205-
if err := gc.Fetch(ctx, upstreamName, ""); err != nil {
206-
return err
207-
}
208+
upstreamName := opts.UpstreamName
209+
if opts.UpstreamName == "@owner" {
210+
upstreamName = canonicalRepo.Parent.RepoOwner()
211+
}
208212

209-
if err := gc.SetRemoteBranches(ctx, upstreamName, `*`); err != nil {
210-
return err
211-
}
213+
if _, err := gc.AddRemote(ctx, upstreamName, upstreamURL, []string{canonicalRepo.Parent.DefaultBranchRef.Name}); err != nil {
214+
return err
215+
}
212216

213-
if err = gc.SetRemoteResolution(ctx, upstreamName, "base"); err != nil {
214-
return err
215-
}
217+
if err := gc.Fetch(ctx, upstreamName, ""); err != nil {
218+
return err
219+
}
216220

217-
connectedToTerminal := opts.IO.IsStdoutTTY()
218-
if connectedToTerminal {
219-
cs := opts.IO.ColorScheme()
220-
fmt.Fprintf(opts.IO.ErrOut, "%s Repository %s set as the default repository. To learn more about the default repository, run: gh repo set-default --help\n", cs.WarningIcon(), cs.Bold(ghrepo.FullName(canonicalRepo.Parent)))
221+
if err := gc.SetRemoteBranches(ctx, upstreamName, `*`); err != nil {
222+
return err
223+
}
224+
225+
if err := gc.SetRemoteResolution(ctx, upstreamName, "base"); err != nil {
226+
return err
227+
}
228+
229+
connectedToTerminal := opts.IO.IsStdoutTTY()
230+
if connectedToTerminal {
231+
cs := opts.IO.ColorScheme()
232+
fmt.Fprintf(opts.IO.ErrOut, "%s Repository %s set as the default repository. To learn more about the default repository, run: gh repo set-default --help\n", cs.WarningIcon(), cs.Bold(ghrepo.FullName(canonicalRepo.Parent)))
233+
}
221234
}
222235
}
223236
return nil

pkg/cmd/repo/clone/clone_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ func TestNewCmdClone(t *testing.T) {
5454
GitArgs: []string{"--depth", "1", "--recurse-submodules"},
5555
},
5656
},
57+
{
58+
name: "no-upstream flag",
59+
args: "OWNER/REPO --no-upstream",
60+
wantOpts: CloneOptions{
61+
Repository: "OWNER/REPO",
62+
GitArgs: []string{},
63+
NoUpstream: true,
64+
},
65+
},
66+
{
67+
name: "no-upstream with upstream-remote-name",
68+
args: "OWNER/REPO --no-upstream --upstream-remote-name test",
69+
wantErr: "if any flags in the group [upstream-remote-name no-upstream] are set none of the others can be; [no-upstream upstream-remote-name] were all set",
70+
},
5771
{
5872
name: "unknown argument",
5973
args: "OWNER/REPO --depth 1",
@@ -92,6 +106,7 @@ func TestNewCmdClone(t *testing.T) {
92106

93107
assert.Equal(t, tt.wantOpts.Repository, opts.Repository)
94108
assert.Equal(t, tt.wantOpts.GitArgs, opts.GitArgs)
109+
assert.Equal(t, tt.wantOpts.NoUpstream, opts.NoUpstream)
95110
})
96111
}
97112
}
@@ -344,6 +359,70 @@ func Test_RepoClone_withoutUsername(t *testing.T) {
344359
assert.Equal(t, "", output.Stderr())
345360
}
346361

362+
func Test_RepoClone_hasParent_noUpstream(t *testing.T) {
363+
reg := &httpmock.Registry{}
364+
defer reg.Verify(t)
365+
reg.Register(
366+
httpmock.GraphQL(`query RepositoryInfo\b`),
367+
httpmock.StringResponse(`
368+
{ "data": { "repository": {
369+
"name": "REPO",
370+
"owner": {
371+
"login": "OWNER"
372+
},
373+
"parent": {
374+
"name": "ORIG",
375+
"owner": {
376+
"login": "hubot"
377+
},
378+
"defaultBranchRef": {
379+
"name": "trunk"
380+
}
381+
}
382+
} } }
383+
`))
384+
385+
httpClient := &http.Client{Transport: reg}
386+
387+
cs, cmdTeardown := run.Stub()
388+
defer cmdTeardown(t)
389+
390+
cs.Register(`git clone https://github.com/OWNER/REPO.git`, 0, "")
391+
cs.Register(`git -C REPO config --add remote.origin.gh-resolved base`, 0, "")
392+
393+
_, err := runCloneCommand(httpClient, "OWNER/REPO --no-upstream")
394+
if err != nil {
395+
t.Fatalf("error running command `repo clone`: %v", err)
396+
}
397+
}
398+
399+
func Test_RepoClone_noParent_noUpstream(t *testing.T) {
400+
reg := &httpmock.Registry{}
401+
defer reg.Verify(t)
402+
reg.Register(
403+
httpmock.GraphQL(`query RepositoryInfo\b`),
404+
httpmock.StringResponse(`
405+
{ "data": { "repository": {
406+
"name": "REPO",
407+
"owner": {
408+
"login": "OWNER"
409+
}
410+
} } }
411+
`))
412+
413+
httpClient := &http.Client{Transport: reg}
414+
415+
cs, cmdTeardown := run.Stub()
416+
defer cmdTeardown(t)
417+
418+
cs.Register(`git clone https://github.com/OWNER/REPO.git`, 0, "")
419+
420+
_, err := runCloneCommand(httpClient, "OWNER/REPO --no-upstream")
421+
if err != nil {
422+
t.Fatalf("error running command `repo clone`: %v", err)
423+
}
424+
}
425+
347426
func TestSimplifyURL(t *testing.T) {
348427
tests := []struct {
349428
name string

0 commit comments

Comments
 (0)