Merge pull request #5561 from bk2204/locking-performance

Improve locking performance
This commit is contained in:
brian m. carlson 2023-11-29 16:00:39 +00:00 committed by GitHub
commit 6c94277c6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 41 deletions

@ -23,6 +23,11 @@ func lockCommand(cmd *cobra.Command, args []string) {
cfg.SetRemote(lockRemote) cfg.SetRemote(lockRemote)
} }
lockData, err := computeLockData()
if err != nil {
ExitWithError(err)
}
refUpdate := git.NewRefUpdate(cfg.Git, cfg.PushRemote(), cfg.CurrentRef(), nil) refUpdate := git.NewRefUpdate(cfg.Git, cfg.PushRemote(), cfg.CurrentRef(), nil)
lockClient := newLockClient() lockClient := newLockClient()
lockClient.RemoteRef = refUpdate.RemoteRef() lockClient.RemoteRef = refUpdate.RemoteRef()
@ -31,7 +36,7 @@ func lockCommand(cmd *cobra.Command, args []string) {
success := true success := true
locks := make([]locking.Lock, 0, len(args)) locks := make([]locking.Lock, 0, len(args))
for _, path := range args { for _, path := range args {
path, err := lockPath(path) path, err := lockPath(lockData, path)
if err != nil { if err != nil {
Error(err.Error()) Error(err.Error())
success = false success = false
@ -67,6 +72,28 @@ func lockCommand(cmd *cobra.Command, args []string) {
} }
} }
type lockData struct {
rootDir string
workingDir string
}
// computeLockData computes data about the given repository and working
// directory to use in lockPath.
func computeLockData() (*lockData, error) {
wd, err := tools.Getwd()
if err != nil {
return nil, err
}
wd, err = tools.CanonicalizeSystemPath(wd)
if err != nil {
return nil, err
}
return &lockData{
rootDir: cfg.LocalWorkingDir(),
workingDir: wd,
}, nil
}
// lockPaths relativizes the given filepath such that it is relative to the root // lockPaths relativizes the given filepath such that it is relative to the root
// path of the repository it is contained within, taking into account the // path of the repository it is contained within, taking into account the
// working directory of the caller. // working directory of the caller.
@ -74,42 +101,28 @@ func lockCommand(cmd *cobra.Command, args []string) {
// lockPaths also respects different filesystem directory separators, so that a // lockPaths also respects different filesystem directory separators, so that a
// Windows path of "\foo\bar" will be normalized to "foo/bar". // Windows path of "\foo\bar" will be normalized to "foo/bar".
// //
// If the root directory, working directory, or file cannot be // If the file path cannot be determined, an error will be returned. If the file
// determined, an error will be returned. If the file in question is // in question is actually a directory, an error will be returned. Otherwise,
// actually a directory, an error will be returned. Otherwise, the cleaned path // the cleaned path will be returned.
// will be returned.
// //
// For example: // For example:
// - Working directory: /code/foo/bar/ // - Working directory: /code/foo/bar/
// - Repository root: /code/foo/ // - Repository root: /code/foo/
// - File to lock: ./baz // - File to lock: ./baz
// - Resolved path bar/baz // - Resolved path bar/baz
func lockPath(file string) (string, error) { func lockPath(data *lockData, file string) (string, error) {
repo, err := git.RootDir()
if err != nil {
return "", err
}
wd, err := os.Getwd()
if err != nil {
return "", err
}
wd, err = tools.CanonicalizeSystemPath(wd)
if err != nil {
return "", errors.Wrapf(err,
tr.Tr.Get("could not follow symlinks for %s", wd))
}
var abs string var abs string
var err error
if filepath.IsAbs(file) { if filepath.IsAbs(file) {
abs, err = tools.CanonicalizeSystemPath(file) abs, err = tools.CanonicalizeSystemPath(file)
if err != nil { if err != nil {
return "", errors.New(tr.Tr.Get("unable to canonicalize path %q: %v", file, err)) return "", errors.New(tr.Tr.Get("unable to canonicalize path %q: %v", file, err))
} }
} else { } else {
abs = filepath.Join(wd, file) abs = filepath.Join(data.workingDir, file)
} }
path, err := filepath.Rel(repo, abs) path, err := filepath.Rel(data.rootDir, abs)
if err != nil { if err != nil {
return "", err return "", err
} }

@ -19,7 +19,11 @@ var (
) )
func locksCommand(cmd *cobra.Command, args []string) { func locksCommand(cmd *cobra.Command, args []string) {
filters, err := locksCmdFlags.Filters() lockData, err := computeLockData()
if err != nil {
ExitWithError(err)
}
filters, err := locksCmdFlags.Filters(lockData)
if err != nil { if err != nil {
Exit(tr.Tr.Get("Error building filters: %v", err)) Exit(tr.Tr.Get("Error building filters: %v", err))
} }
@ -154,11 +158,11 @@ type locksFlags struct {
} }
// Filters produces a filter based on locksFlags instance. // Filters produces a filter based on locksFlags instance.
func (l *locksFlags) Filters() (map[string]string, error) { func (l *locksFlags) Filters(data *lockData) (map[string]string, error) {
filters := make(map[string]string) filters := make(map[string]string)
if l.Path != "" { if l.Path != "" {
path, err := lockPath(l.Path) path, err := lockPath(data, l.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -58,6 +58,11 @@ func unlockCommand(cmd *cobra.Command, args []string) {
cfg.SetRemote(lockRemote) cfg.SetRemote(lockRemote)
} }
lockData, err := computeLockData()
if err != nil {
ExitWithError(err)
}
refUpdate := git.NewRefUpdate(cfg.Git, cfg.PushRemote(), cfg.CurrentRef(), nil) refUpdate := git.NewRefUpdate(cfg.Git, cfg.PushRemote(), cfg.CurrentRef(), nil)
lockClient := newLockClient() lockClient := newLockClient()
lockClient.RemoteRef = refUpdate.RemoteRef() lockClient.RemoteRef = refUpdate.RemoteRef()
@ -67,7 +72,7 @@ func unlockCommand(cmd *cobra.Command, args []string) {
success := true success := true
if hasPath { if hasPath {
for _, pathspec := range args { for _, pathspec := range args {
path, err := lockPath(pathspec) path, err := lockPath(lockData, pathspec)
if err != nil { if err != nil {
if !unlockCmdFlags.Force { if !unlockCmdFlags.Force {
locks = handleUnlockError(locks, "", path, errors.New(tr.Tr.Get("Unable to determine path: %v", err.Error()))) locks = handleUnlockError(locks, "", path, errors.New(tr.Tr.Get("Unable to determine path: %v", err.Error())))

@ -531,13 +531,20 @@ func ValidateRemote(remote string) error {
if err != nil { if err != nil {
return err return err
} }
return ValidateRemoteFromList(remotes, remote)
}
// ValidateRemote checks that a named remote is valid for use given a list from
// RemoteList. This is completely identical to ValidateRemote, except that it
// allows caching the remote list.
func ValidateRemoteFromList(remotes []string, remote string) error {
for _, r := range remotes { for _, r := range remotes {
if r == remote { if r == remote {
return nil return nil
} }
} }
if err = ValidateRemoteURL(remote); err == nil { if err := ValidateRemoteURL(remote); err == nil {
return nil return nil
} }

@ -37,10 +37,12 @@ type endpointGitFinder struct {
gitConfig *git.Configuration gitConfig *git.Configuration
gitEnv config.Environment gitEnv config.Environment
gitProtocol string gitProtocol string
gitDir string
aliasMu sync.Mutex aliasMu sync.Mutex
aliases map[string]string aliases map[string]string
pushAliases map[string]string pushAliases map[string]string
remoteList []string
accessMu sync.Mutex accessMu sync.Mutex
urlAccess map[string]creds.AccessMode urlAccess map[string]creds.AccessMode
@ -52,15 +54,27 @@ func NewEndpointFinder(ctx lfshttp.Context) EndpointFinder {
ctx = lfshttp.NewContext(nil, nil, nil) ctx = lfshttp.NewContext(nil, nil, nil)
} }
var gitDir string
cfg := ctx.GitConfig()
if cfg != nil && cfg.GitDir != "" {
gitDir = cfg.GitDir
} else if dir, err := git.GitDir(); err == nil {
gitDir = dir
}
e := &endpointGitFinder{ e := &endpointGitFinder{
gitConfig: ctx.GitConfig(), gitConfig: ctx.GitConfig(),
gitEnv: ctx.GitEnv(), gitEnv: ctx.GitEnv(),
gitProtocol: "https", gitProtocol: "https",
gitDir: gitDir,
aliases: make(map[string]string), aliases: make(map[string]string),
pushAliases: make(map[string]string), pushAliases: make(map[string]string),
urlAccess: make(map[string]creds.AccessMode), urlAccess: make(map[string]creds.AccessMode),
} }
remotes, _ := git.RemoteList()
e.remoteList = remotes
e.urlConfig = config.NewURLConfig(e.gitEnv) e.urlConfig = config.NewURLConfig(e.gitEnv)
if v, ok := e.gitEnv.Get("lfs.gitprotocol"); ok { if v, ok := e.gitEnv.Get("lfs.gitprotocol"); ok {
e.gitProtocol = v e.gitProtocol = v
@ -124,11 +138,10 @@ func (e *endpointGitFinder) RemoteEndpoint(operation, remote string) lfshttp.End
return e.NewEndpointFromCloneURL(operation, url) return e.NewEndpointFromCloneURL(operation, url)
} }
gitDir, err := git.GitDir()
// Finally, fall back on .git/FETCH_HEAD but only if it exists and no specific remote was requested // Finally, fall back on .git/FETCH_HEAD but only if it exists and no specific remote was requested
// We can't know which remote FETCH_HEAD is pointing to // We can't know which remote FETCH_HEAD is pointing to
if err == nil && remote == defaultRemote { if e.gitDir != "" && remote == defaultRemote {
url, err := parseFetchHead(strings.Join([]string{gitDir, "FETCH_HEAD"}, "/")) url, err := parseFetchHead(strings.Join([]string{e.gitDir, "FETCH_HEAD"}, "/"))
if err == nil { if err == nil {
endpoint := e.NewEndpointFromCloneURL("download", url) endpoint := e.NewEndpointFromCloneURL("download", url)
return endpoint return endpoint
@ -187,7 +200,7 @@ func (e *endpointGitFinder) GitRemoteURL(remote string, forpush bool) string {
} }
} }
if err := git.ValidateRemote(remote); err == nil { if err := git.ValidateRemoteFromList(e.remoteList, remote); err == nil {
return remote return remote
} }

@ -67,6 +67,8 @@ func NewClient(remote string, lfsClient *lfsapi.Client, cfg *config.Configuratio
cache: &nilLockCacher{}, cache: &nilLockCacher{},
cfg: cfg, cfg: cfg,
ModifyIgnoredFiles: lfsClient.GitEnv().Bool("lfs.lockignoredfiles", false), ModifyIgnoredFiles: lfsClient.GitEnv().Bool("lfs.lockignoredfiles", false),
LocalWorkingDir: cfg.LocalWorkingDir(),
LocalGitDir: cfg.LocalGitDir(),
}, nil }, nil
} }
@ -120,7 +122,7 @@ func (c *Client) LockFile(path string) (Lock, error) {
return Lock{}, errors.Wrap(err, tr.Tr.Get("lock cache")) return Lock{}, errors.Wrap(err, tr.Tr.Get("lock cache"))
} }
abs, err := getAbsolutePath(path) abs, err := c.getAbsolutePath(path)
if err != nil { if err != nil {
return Lock{}, errors.Wrap(err, tr.Tr.Get("make lock path absolute")) return Lock{}, errors.Wrap(err, tr.Tr.Get("make lock path absolute"))
} }
@ -141,13 +143,8 @@ func (c *Client) LockFile(path string) (Lock, error) {
// dir/foo/bar.txt, getAbsolutePath will return: // dir/foo/bar.txt, getAbsolutePath will return:
// //
// /usr/local/src/my-repo/dir/foo/bar.txt // /usr/local/src/my-repo/dir/foo/bar.txt
func getAbsolutePath(p string) (string, error) { func (c *Client) getAbsolutePath(p string) (string, error) {
root, err := git.RootDir() return filepath.Join(c.LocalWorkingDir, p), nil
if err != nil {
return "", err
}
return filepath.Join(root, p), nil
} }
// UnlockFile attempts to unlock a file on the current remote // UnlockFile attempts to unlock a file on the current remote
@ -182,7 +179,7 @@ func (c *Client) UnlockFileById(id string, force bool) error {
} }
if unlockRes.Lock != nil { if unlockRes.Lock != nil {
abs, err := getAbsolutePath(unlockRes.Lock.Path) abs, err := c.getAbsolutePath(unlockRes.Lock.Path)
if err != nil { if err != nil {
return errors.Wrap(err, tr.Tr.Get("make lock path absolute")) return errors.Wrap(err, tr.Tr.Get("make lock path absolute"))
} }