git-lfs/tq/manifest.go
Chris Darroch 2ecc5533e1 tq/manifest.go: lookup standalone endpoint once
The findStandaloneTransfer() function of the "tq" package was updated
in commit 28f9b73d2f2bc529ce4385d372ab900e3763b854 of PR #3905 to
determine whether to use the built-in file standalone transfer adapter
based on the actual endpoint URL, taking into account settings like
"lfs.url" which override the endpoint URL we would otherwise construct
from the current remote's URL.  However, the code still checked the
"lfs.<url>.standaloneTransferAgent" setting using the URL constructed
from the remote's URL, and so called both the Endpoint() and
RemoteEndpoint() methods of the EndpointFinder interface.

Then in commit 594f8e386cce3441e06c9094ab5e251f0e07ca1f of PR #4446,
when the pure SSH transfer protocol was introduced, the call to
the RemoteEndpoint() method was replaced with Endpoint(), which was
done with the intent that it would honour "lfs.url" settings as well.

However, this resulted in duplicate calls to Endpoint(), so we can
remove one of them now.

Note that the change in commit 594f8e386cce3441e06c9094ab5e251f0e07ca1f
of PR #4446 implies that the URL used when looking for
"lfs.<url>.standaloneTransferAgent" settings is the URL as fully
determined by settings such as "lfs.url" that override what
would be otherwise constructed based on the remote's URL.  We
expect to add documentation to this effect in a subsequent PR.
2023-10-17 10:55:16 -07:00

388 lines
10 KiB
Go

package tq
import (
"strings"
"sync"
"github.com/git-lfs/git-lfs/v3/config"
"github.com/git-lfs/git-lfs/v3/fs"
"github.com/git-lfs/git-lfs/v3/lfsapi"
"github.com/git-lfs/git-lfs/v3/ssh"
"github.com/rubyist/tracerx"
)
const (
defaultMaxRetries = 8
defaultMaxRetryDelay = 10
defaultConcurrentTransfers = 8
)
type Manifest interface {
APIClient() *lfsapi.Client
MaxRetries() int
MaxRetryDelay() int
ConcurrentTransfers() int
IsStandaloneTransfer() bool
batchClient() BatchClient
GetAdapterNames(dir Direction) []string
GetDownloadAdapterNames() []string
GetUploadAdapterNames() []string
getAdapterNames(adapters map[string]NewAdapterFunc) []string
RegisterNewAdapterFunc(name string, dir Direction, f NewAdapterFunc)
NewAdapterOrDefault(name string, dir Direction) Adapter
NewAdapter(name string, dir Direction) Adapter
NewDownloadAdapter(name string) Adapter
NewUploadAdapter(name string) Adapter
Upgrade() *concreteManifest
Upgraded() bool
}
type lazyManifest struct {
f *fs.Filesystem
apiClient *lfsapi.Client
operation string
remote string
m *concreteManifest
}
func newLazyManifest(f *fs.Filesystem, apiClient *lfsapi.Client, operation, remote string) *lazyManifest {
return &lazyManifest{
f: f,
apiClient: apiClient,
operation: operation,
remote: remote,
m: nil,
}
}
func (m *lazyManifest) APIClient() *lfsapi.Client {
return m.Upgrade().APIClient()
}
func (m *lazyManifest) MaxRetries() int {
return m.Upgrade().MaxRetries()
}
func (m *lazyManifest) MaxRetryDelay() int {
return m.Upgrade().MaxRetryDelay()
}
func (m *lazyManifest) ConcurrentTransfers() int {
return m.Upgrade().ConcurrentTransfers()
}
func (m *lazyManifest) IsStandaloneTransfer() bool {
return m.Upgrade().IsStandaloneTransfer()
}
func (m *lazyManifest) batchClient() BatchClient {
return m.Upgrade().batchClient()
}
func (m *lazyManifest) GetAdapterNames(dir Direction) []string {
return m.Upgrade().GetAdapterNames(dir)
}
func (m *lazyManifest) GetDownloadAdapterNames() []string {
return m.Upgrade().GetDownloadAdapterNames()
}
func (m *lazyManifest) GetUploadAdapterNames() []string {
return m.Upgrade().GetUploadAdapterNames()
}
func (m *lazyManifest) getAdapterNames(adapters map[string]NewAdapterFunc) []string {
return m.Upgrade().getAdapterNames(adapters)
}
func (m *lazyManifest) RegisterNewAdapterFunc(name string, dir Direction, f NewAdapterFunc) {
m.Upgrade().RegisterNewAdapterFunc(name, dir, f)
}
func (m *lazyManifest) NewAdapterOrDefault(name string, dir Direction) Adapter {
return m.Upgrade().NewAdapterOrDefault(name, dir)
}
func (m *lazyManifest) NewAdapter(name string, dir Direction) Adapter {
return m.Upgrade().NewAdapter(name, dir)
}
func (m *lazyManifest) NewDownloadAdapter(name string) Adapter {
return m.Upgrade().NewDownloadAdapter(name)
}
func (m *lazyManifest) NewUploadAdapter(name string) Adapter {
return m.Upgrade().NewUploadAdapter(name)
}
func (m *lazyManifest) Upgrade() *concreteManifest {
if m.m == nil {
m.m = newConcreteManifest(m.f, m.apiClient, m.operation, m.remote)
}
return m.m
}
func (m *lazyManifest) Upgraded() bool {
return m.m != nil
}
type concreteManifest struct {
// maxRetries is the maximum number of retries a single object can
// attempt to make before it will be dropped. maxRetryDelay is the maximum
// time in seconds to wait between retry attempts when using backoff.
maxRetries int
maxRetryDelay int
concurrentTransfers int
basicTransfersOnly bool
standaloneTransferAgent string
tusTransfersAllowed bool
downloadAdapterFuncs map[string]NewAdapterFunc
uploadAdapterFuncs map[string]NewAdapterFunc
fs *fs.Filesystem
apiClient *lfsapi.Client
sshTransfer *ssh.SSHTransfer
batchClientAdapter BatchClient
mu sync.Mutex
}
func (m *concreteManifest) APIClient() *lfsapi.Client {
return m.apiClient
}
func (m *concreteManifest) MaxRetries() int {
return m.maxRetries
}
func (m *concreteManifest) MaxRetryDelay() int {
return m.maxRetryDelay
}
func (m *concreteManifest) ConcurrentTransfers() int {
return m.concurrentTransfers
}
func (m *concreteManifest) IsStandaloneTransfer() bool {
return m.standaloneTransferAgent != ""
}
func (m *concreteManifest) batchClient() BatchClient {
if r := m.MaxRetries(); r > 0 {
m.batchClientAdapter.SetMaxRetries(r)
}
return m.batchClientAdapter
}
func (m *concreteManifest) Upgrade() *concreteManifest {
return m
}
func (m *concreteManifest) Upgraded() bool {
return true
}
func NewManifest(f *fs.Filesystem, apiClient *lfsapi.Client, operation, remote string) Manifest {
return newLazyManifest(f, apiClient, operation, remote)
}
func newConcreteManifest(f *fs.Filesystem, apiClient *lfsapi.Client, operation, remote string) *concreteManifest {
if apiClient == nil {
cli, err := lfsapi.NewClient(nil)
if err != nil {
tracerx.Printf("unable to init tq.Manifest: %s", err)
return nil
}
apiClient = cli
}
sshTransfer := apiClient.SSHTransfer(operation, remote)
useSSHMultiplexing := false
if sshTransfer != nil {
useSSHMultiplexing = sshTransfer.IsMultiplexingEnabled()
}
m := &concreteManifest{
fs: f,
apiClient: apiClient,
batchClientAdapter: &tqClient{Client: apiClient},
downloadAdapterFuncs: make(map[string]NewAdapterFunc),
uploadAdapterFuncs: make(map[string]NewAdapterFunc),
sshTransfer: sshTransfer,
}
var tusAllowed bool
if git := apiClient.GitEnv(); git != nil {
if v := git.Int("lfs.transfer.maxretries", 0); v > 0 {
m.maxRetries = v
}
if v := git.Int("lfs.transfer.maxretrydelay", -1); v > -1 {
m.maxRetryDelay = v
}
if v := git.Int("lfs.concurrenttransfers", 0); v > 0 {
m.concurrentTransfers = v
}
m.basicTransfersOnly = git.Bool("lfs.basictransfersonly", false)
m.standaloneTransferAgent = findStandaloneTransfer(
apiClient, operation, remote,
)
tusAllowed = git.Bool("lfs.tustransfers", false)
configureCustomAdapters(git, m)
}
if m.maxRetries < 1 {
m.maxRetries = defaultMaxRetries
}
if m.maxRetryDelay < 1 {
m.maxRetryDelay = defaultMaxRetryDelay
}
if m.concurrentTransfers < 1 {
m.concurrentTransfers = defaultConcurrentTransfers
}
if sshTransfer != nil {
if !useSSHMultiplexing {
m.concurrentTransfers = 1
}
// Multiple concurrent transfers are not yet supported.
m.batchClientAdapter = &SSHBatchClient{
maxRetries: m.maxRetries,
transfer: sshTransfer,
}
}
configureBasicDownloadAdapter(m)
configureBasicUploadAdapter(m)
if tusAllowed {
configureTusAdapter(m)
}
configureSSHAdapter(m)
return m
}
func findDefaultStandaloneTransfer(url string) string {
if strings.HasPrefix(url, "file://") {
return standaloneFileName
}
return ""
}
func findStandaloneTransfer(client *lfsapi.Client, operation, remote string) string {
if operation == "" || remote == "" {
v, _ := client.GitEnv().Get("lfs.standalonetransferagent")
return v
}
ep := client.Endpoints.Endpoint(operation, remote)
uc := config.NewURLConfig(client.GitEnv())
v, ok := uc.Get("lfs", ep.Url, "standalonetransferagent")
if !ok {
return findDefaultStandaloneTransfer(ep.Url)
}
return v
}
// GetAdapterNames returns a list of the names of adapters available to be created
func (m *concreteManifest) GetAdapterNames(dir Direction) []string {
switch dir {
case Upload:
return m.GetUploadAdapterNames()
case Download:
return m.GetDownloadAdapterNames()
}
return nil
}
// GetDownloadAdapterNames returns a list of the names of download adapters available to be created
func (m *concreteManifest) GetDownloadAdapterNames() []string {
return m.getAdapterNames(m.downloadAdapterFuncs)
}
// GetUploadAdapterNames returns a list of the names of upload adapters available to be created
func (m *concreteManifest) GetUploadAdapterNames() []string {
return m.getAdapterNames(m.uploadAdapterFuncs)
}
// getAdapterNames returns a list of the names of adapters available to be created
func (m *concreteManifest) getAdapterNames(adapters map[string]NewAdapterFunc) []string {
if m.basicTransfersOnly {
return []string{BasicAdapterName}
}
m.mu.Lock()
defer m.mu.Unlock()
ret := make([]string, 0, len(adapters))
for n, _ := range adapters {
ret = append(ret, n)
}
return ret
}
// RegisterNewTransferAdapterFunc registers a new function for creating upload
// or download adapters. If a function with that name & direction is already
// registered, it is overridden
func (m *concreteManifest) RegisterNewAdapterFunc(name string, dir Direction, f NewAdapterFunc) {
m.mu.Lock()
defer m.mu.Unlock()
switch dir {
case Upload:
m.uploadAdapterFuncs[name] = f
case Download:
m.downloadAdapterFuncs[name] = f
}
}
// Create a new adapter by name and direction; default to BasicAdapterName if doesn't exist
func (m *concreteManifest) NewAdapterOrDefault(name string, dir Direction) Adapter {
if len(name) == 0 {
name = BasicAdapterName
}
a := m.NewAdapter(name, dir)
if a == nil {
tracerx.Printf("Defaulting to basic transfer adapter since %q did not exist", name)
a = m.NewAdapter(BasicAdapterName, dir)
}
return a
}
// Create a new adapter by name and direction, or nil if doesn't exist
func (m *concreteManifest) NewAdapter(name string, dir Direction) Adapter {
m.mu.Lock()
defer m.mu.Unlock()
switch dir {
case Upload:
if u, ok := m.uploadAdapterFuncs[name]; ok {
return u(name, dir)
}
case Download:
if d, ok := m.downloadAdapterFuncs[name]; ok {
return d(name, dir)
}
}
return nil
}
// Create a new download adapter by name, or BasicAdapterName if doesn't exist
func (m *concreteManifest) NewDownloadAdapter(name string) Adapter {
return m.NewAdapterOrDefault(name, Download)
}
// Create a new upload adapter by name, or BasicAdapterName if doesn't exist
func (m *concreteManifest) NewUploadAdapter(name string) Adapter {
return m.NewAdapterOrDefault(name, Upload)
}
// Env is any object with a config.Environment interface.
type Env interface {
Get(key string) (val string, ok bool)
GetAll(key string) []string
Bool(key string, def bool) (val bool)
Int(key string, def int) (val int)
All() map[string][]string
}