86 lines
1.8 KiB
Go
86 lines
1.8 KiB
Go
package worktree
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
const windowsGOOS = "windows"
|
|
|
|
type targetPathLockEntry struct {
|
|
token chan struct{}
|
|
refs int
|
|
}
|
|
|
|
type targetPathLockRegistry struct {
|
|
mu sync.Mutex
|
|
entries map[string]*targetPathLockEntry
|
|
}
|
|
|
|
var worktreeTargetPathLocks = targetPathLockRegistry{
|
|
entries: make(map[string]*targetPathLockEntry),
|
|
}
|
|
|
|
func acquireWorktreeTargetPath(ctx context.Context, path string) (func(), error) {
|
|
key, err := normalizedWorktreeTargetPath(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entry := worktreeTargetPathLocks.reference(key)
|
|
select {
|
|
case <-entry.token:
|
|
var once sync.Once
|
|
return func() {
|
|
once.Do(func() {
|
|
entry.token <- struct{}{}
|
|
worktreeTargetPathLocks.unreference(key, entry)
|
|
})
|
|
}, nil
|
|
case <-ctx.Done():
|
|
worktreeTargetPathLocks.unreference(key, entry)
|
|
return nil, ctx.Err()
|
|
}
|
|
}
|
|
|
|
func normalizedWorktreeTargetPath(path string) (string, error) {
|
|
return normalizedWorktreeTargetPathForOS(path, runtime.GOOS)
|
|
}
|
|
|
|
func normalizedWorktreeTargetPathForOS(path, goos string) (string, error) {
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve worktree target path: %w", err)
|
|
}
|
|
key := filepath.Clean(absPath)
|
|
if goos == windowsGOOS {
|
|
key = strings.ToLower(key)
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
func (r *targetPathLockRegistry) reference(key string) *targetPathLockEntry {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
entry := r.entries[key]
|
|
if entry == nil {
|
|
entry = &targetPathLockEntry{token: make(chan struct{}, 1)}
|
|
entry.token <- struct{}{}
|
|
r.entries[key] = entry
|
|
}
|
|
entry.refs++
|
|
return entry
|
|
}
|
|
|
|
func (r *targetPathLockRegistry) unreference(key string, entry *targetPathLockEntry) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
entry.refs--
|
|
if entry.refs == 0 && r.entries[key] == entry {
|
|
delete(r.entries, key)
|
|
}
|
|
}
|