-
Notifications
You must be signed in to change notification settings - Fork 6.5k
[gh repo clone] Add --no-upstream
flag
#10608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Conversation
Thanks for opening up this pull request, @iamazeem! β€οΈ Right now, we are a bit understaffed and juggling some high priority items, so I'm going to be a little slow following up on PRs. Just wanting to give you heads up. π |
@iamazeem : just want you to know I'm catching up on this PR today, stay tuned and sincere thank you for patience! π |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@iamazeem : In and of itself, these changes seem straightforward with some room for wording here and there. However, I think there is a larger problem with forked repositories without upstream
that we didn't consider previously.
I'm going to bring these up for guidance. π
pkg/cmd/repo/clone/clone.go
Outdated
|
||
To disable the addition of the %[1]supstream%[1]s remote for a forked repository, | ||
use the %[1]s--no-upstream%[1]s flag. For a non-forked repository, this flag has no effect. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggest: I'd like to attempt rephrasing a few of these sections to be smaller and easier to read.
If the repository is a fork, its parent repository will be added as an additional git remote named
%[1]supstream%[1]s and set as the default remote unless %[1]s--no-upstream%[1]s flag is specified.
The remote name can be configured using the %[1]s--upstream-remote-name%[1]s flag, which supports
the %[1]s@owner%[1]s syntax to name it after the owner of the parent repository.
as opposed to all of this:
cli/pkg/cmd/repo/clone/clone.go
Lines 58 to 66 in 1bb599c
If the repository is a fork, its parent repository will be added as an additional | |
git remote called %[1]supstream%[1]s. The remote name can be configured using %[1]s--upstream-remote-name%[1]s. | |
The %[1]s--upstream-remote-name%[1]s option supports an %[1]s@owner%[1]s value which will name | |
the remote after the owner of the parent repository. | |
If the repository is a fork, its parent repository will be set as the default remote repository. | |
To disable the addition of the %[1]supstream%[1]s remote for a forked repository, | |
use the %[1]s--no-upstream%[1]s flag. For a non-forked repository, this flag has no effect. |
@@ -186,7 +202,7 @@ func cloneRun(opts *CloneOptions) error { | |||
} | |||
|
|||
// If the repo is a fork, add the parent as an upstream remote and set the parent as the default repo. | |||
if canonicalRepo.Parent != nil { | |||
if !opts.NoUpstream && canonicalRepo.Parent != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: I believe these changes create a situation where forked repositories are not resolved properly via remotes as gh repo view
fails. π€
I want to raise this up with the other maintainers as I'm unsure if we still need to set remote resolution even though there is only 1 remote.
Demo
Repository: https://github.com/andyfeller/cli
$ make clean && make
rm -rf bin share
go build -trimpath -ldflags "-X github.com/cli/cli/v2/internal/build.Date=2025-04-14 -X github.com/cli/cli/v2/internal/build.Version=v2.68.1-23-g1bb599c1e " -o bin/gh ./cmd/gh
$ alias gh=$(realpath bin/gh)
$ cd ~/Documents/workspace/andyfeller
$ gh repo clone andyfeller/cli --no-upstream
Cloning into 'cli'...
remote: Enumerating objects: 56037, done.
remote: Total 56037 (delta 0), reused 0 (delta 0), pack-reused 56037 (from 1)
Receiving objects: 100% (56037/56037), 29.54 MiB | 22.13 MiB/s, done.
Resolving deltas: 100% (38350/38350), done.
$ cd cli
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/andyfeller/cli.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "trunk"]
remote = origin
merge = refs/heads/trunk
$ gh repo view
X No default remote repository has been set. To learn more about the default repository, run: gh repo set-default --help
please run `gh repo set-default` to select a default remote repository.
Looking at GH_DEBUG=api
output below, the RepositoryNetwork
GraphQL query points to logic that resolves the remotes:
Expand for full GH_DEBUG=api gh repo view output
$ GH_DEBUG=api gh repo view
[git remote -v]
[git config --get-regexp ^remote\..*\.gh-resolved$]
* Request at 2025-04-14 11:44:42.424889 -0400 EDT m=+0.084322709
* Request to https://api.github.com/graphql
> POST /graphql HTTP/1.1
> Host: api.github.com
> Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
> Authorization: token ββββββββββββββββββββ
> Content-Length: 388
> Content-Type: application/json; charset=utf-8
> Graphql-Features: merge_queue
> Time-Zone: America/New_York
> User-Agent: GitHub CLI v2.68.1-23-g1bb599c1e
GraphQL query:
fragment repo on Repository {
id
name
owner { login }
viewerPermission
defaultBranchRef {
name
}
isPrivate
}
query RepositoryNetwork {
viewer { login }
repo_000: repository(owner: "andyfeller", name: "cli") {
...repo
parent {
...repo
}
}
}
GraphQL variables: null
< HTTP/2.0 200 OK
< Access-Control-Allow-Origin: *
< Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
< Content-Security-Policy: default-src 'none'
< Content-Type: application/json; charset=utf-8
< Date: Mon, 14 Apr 2025 15:44:42 GMT
< Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
< Server: github.com
< Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
< Vary: Accept-Encoding, Accept, X-Requested-With
< X-Accepted-Oauth-Scopes: repo
< X-Content-Type-Options: nosniff
< X-Frame-Options: deny
< X-Github-Media-Type: github.v4; param=merge-info-preview.nebula-preview; format=json
< X-Github-Request-Id: C7FB:2D65EB:521533D:A3DF8F7:67FD2D6A
< X-Oauth-Client-Id: 178c6fc778ccc68e1d6a
< X-Oauth-Scopes: admin:org, gist, repo, workflow
< X-Ratelimit-Limit: 5000
< X-Ratelimit-Remaining: 5000
< X-Ratelimit-Reset: 1744645618
< X-Ratelimit-Resource: graphql
< X-Ratelimit-Used: 476
< X-Xss-Protection: 0
{
"data": {
"viewer": {
"login": "andyfeller"
},
"repo_000": {
"id": "R_kgDOMUqY2A",
"name": "cli",
"owner": {
"login": "andyfeller"
},
"viewerPermission": "ADMIN",
"defaultBranchRef": {
"name": "trunk"
},
"isPrivate": false,
"parent": {
"id": "MDEwOlJlcG9zaXRvcnkyMTI2MTMwNDk=",
"name": "cli",
"owner": {
"login": "cli"
},
"viewerPermission": "ADMIN",
"defaultBranchRef": {
"name": "trunk"
},
"isPrivate": false
}
}
}
}
* Request took 478.958417ms
X No default remote repository has been set. To learn more about the default repository, run: gh repo set-default --help
please run `gh repo set-default` to select a default remote repository.
Lines 61 to 109 in 408e21e
func (r *ResolvedRemotes) BaseRepo(io *iostreams.IOStreams) (ghrepo.Interface, error) { | |
if r.baseOverride != nil { | |
return r.baseOverride, nil | |
} | |
if len(r.remotes) == 0 { | |
return nil, errors.New("no git remotes") | |
} | |
// if any of the remotes already has a resolution, respect that | |
for _, r := range r.remotes { | |
if r.Resolved == "base" { | |
return r, nil | |
} else if r.Resolved != "" { | |
repo, err := ghrepo.FromFullName(r.Resolved) | |
if err != nil { | |
return nil, err | |
} | |
return ghrepo.NewWithHost(repo.RepoOwner(), repo.RepoName(), r.RepoHost()), nil | |
} | |
} | |
if !io.CanPrompt() { | |
// we cannot prompt, so just resort to the 1st remote | |
return r.remotes[0], nil | |
} | |
repos, err := r.NetworkRepos(defaultRemotesForLookup) | |
if err != nil { | |
return nil, err | |
} | |
if len(repos) == 0 { | |
return r.remotes[0], nil | |
} else if len(repos) == 1 { | |
return repos[0], nil | |
} | |
cs := io.ColorScheme() | |
fmt.Fprintf(io.ErrOut, | |
"%s No default remote repository has been set. To learn more about the default repository, run: gh repo set-default --help\n", | |
cs.FailureIcon()) | |
fmt.Fprintln(io.Out) | |
return nil, errors.New( | |
"please run `gh repo set-default` to select a default remote repository.") | |
} |
Lines 130 to 162 in 408e21e
// NetworkRepos fetches info about remotes for the network of repos. | |
// Pass a value of 0 to fetch info on all remotes. | |
func (r *ResolvedRemotes) NetworkRepos(remotesForLookup int) ([]*api.Repository, error) { | |
if r.network == nil { | |
err := resolveNetwork(r, remotesForLookup) | |
if err != nil { | |
return nil, err | |
} | |
} | |
var repos []*api.Repository | |
repoMap := map[string]bool{} | |
add := func(r *api.Repository) { | |
fn := ghrepo.FullName(r) | |
if _, ok := repoMap[fn]; !ok { | |
repoMap[fn] = true | |
repos = append(repos, r) | |
} | |
} | |
for _, repo := range r.network.Repositories { | |
if repo == nil { | |
continue | |
} | |
if repo.Parent != nil { | |
add(repo.Parent) | |
} | |
add(repo) | |
} | |
return repos, nil | |
} |
Co-authored-by: Andy Feller <[email protected]>
Bringing up #8274 (comment) internally to see if we can get movement on this PR. Thank you again for your patience as I know this has been dragging on π |
Fixes #8274.
Tests
bin/gh repo clone --help
Output
bin/gh repo clone iamazeem/cli --upstream-remote-name upstream --no-upstream
bin/gh repo clone iamazeem/cli cli1 -- --depth=1
bin/gh repo clone iamazeem/cli cli2 --no-upstream -- --depth=1