Support for multiple remotes to solve issue #3835

Before this change, LFS was only respecting remotes which were directly
tracked by a branch, or, in a detached state, using a heuristic.
Sometimes this failed as reported in issue #3835.
This patch allows to use the tree-ish to retrieve a corresonding remote.

Thanks to Stan Hu, who provided inspiration to fix this bug.
This commit is contained in:
Stephan Rohmen 2022-07-01 18:04:52 +02:00
parent 11fe8dc62e
commit 71237a1ef4
3 changed files with 80 additions and 0 deletions

@ -95,6 +95,14 @@ func filterCommand(cmd *cobra.Command, args []string) {
closeOnce = new(sync.Once)
available = make(chan *tq.Transfer)
if cfg.AutoDetectRemoteEnabled() {
// update current remote with information gained by treeish
newRemote := git.FirstRemoteForTreeish(req.Header["treeish"])
if newRemote != "" {
cfg.SetRemote(newRemote)
}
}
q = tq.NewTransferQueue(
tq.Download,
getTransferManifestOperationRemote("download", cfg.Remote()),

@ -220,6 +220,10 @@ func (c *Configuration) IsDefaultRemote() bool {
return c.Remote() == defaultRemote
}
func (c *Configuration) AutoDetectRemoteEnabled() bool {
return c.Git.Bool("lfs.remote.autodetect", false)
}
// Remote returns the default remote based on:
// 1. The currently tracked remote branch, if present
// 2. The value of remote.lfsdefault.

@ -1481,3 +1481,71 @@ func ObjectDatabase(osEnv, gitEnv Environment, gitdir, tempdir string) (*gitobj.
}
return odb, nil
}
func remotesForTreeish(treeish string) []string {
var outp string
var err error
if treeish == "" {
//Treeish is empty for sparse checkout
tracerx.Printf("git: treeish: not provided")
outp, err = gitNoLFSSimple("branch", "-r", "--contains", "HEAD")
} else {
tracerx.Printf("git: treeish: %q", treeish)
outp, err = gitNoLFSSimple("branch", "-r", "--contains", treeish)
}
if err != nil || outp == "" {
tracerx.Printf("git: symbolic name: can't resolve symbolic name for ref: %q", treeish)
return []string{}
}
return strings.Split(outp, "\n")
}
// remoteForRef will try to determine the remote from the ref name.
// This will return an empty string if any of the remote names have a slash
// because slashes introduce ambiguity. Consider two refs:
//
// 1. upstream/main
// 2. upstream/test/main
//
// Is the remote "upstream" or "upstream/test"? It could be either, or both.
// We could use git for-each-ref with %(upstream:remotename) if there were a tracking branch,
// but this is not guaranteed to exist either.
func remoteForRef(refname string) string {
tracerx.Printf("git: working ref: %s", refname)
remotes, err := RemoteList()
if err != nil {
return ""
}
parts := strings.Split(refname, "/")
if len(parts) < 2 {
return ""
}
for _, name := range remotes {
if strings.Contains(name, "/") {
tracerx.Printf("git: ref remote: cannot determine remote for ref %s since remote %s contains a slash", refname, name)
return ""
}
}
remote := parts[0]
tracerx.Printf("git: working remote %s", remote)
return remote
}
func getValidRemote(refs []string) string {
for _, ref := range refs {
if ref != "" {
return ref
}
}
return ""
}
// FirstRemoteForTreeish returns the first remote found which contains the treeish.
func FirstRemoteForTreeish(treeish string) string {
name := getValidRemote(remotesForTreeish(treeish))
if name == "" {
tracerx.Printf("git: remote treeish: no valid remote refs parsed for %q", treeish)
return ""
}
return remoteForRef(name)
}