diff --git a/commands/command_filter_process.go b/commands/command_filter_process.go index eaf0d266..d644a323 100644 --- a/commands/command_filter_process.go +++ b/commands/command_filter_process.go @@ -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()), diff --git a/config/config.go b/config/config.go index 5846302c..0b3bdde6 100644 --- a/config/config.go +++ b/config/config.go @@ -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. diff --git a/git/git.go b/git/git.go index 8759585f..e8a2db51 100644 --- a/git/git.go +++ b/git/git.go @@ -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) +}