github.com/2lambda123/git-lfs@v2.5.2+incompatible/commands/lockverifier.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/git-lfs/git-lfs/config"
    10  	"github.com/git-lfs/git-lfs/errors"
    11  	"github.com/git-lfs/git-lfs/git"
    12  	"github.com/git-lfs/git-lfs/lfsapi"
    13  	"github.com/git-lfs/git-lfs/locking"
    14  	"github.com/git-lfs/git-lfs/tq"
    15  )
    16  
    17  type verifyState byte
    18  
    19  const (
    20  	verifyStateUnknown verifyState = iota
    21  	verifyStateEnabled
    22  	verifyStateDisabled
    23  )
    24  
    25  func verifyLocksForUpdates(lv *lockVerifier, updates []*git.RefUpdate) {
    26  	for _, update := range updates {
    27  		lv.Verify(update.Right())
    28  	}
    29  }
    30  
    31  // lockVerifier verifies locked files before updating one or more refs.
    32  type lockVerifier struct {
    33  	endpoint     lfsapi.Endpoint
    34  	verifyState  verifyState
    35  	verifiedRefs map[string]bool
    36  
    37  	// all existing locks
    38  	ourLocks   map[string]*refLock
    39  	theirLocks map[string]*refLock
    40  
    41  	// locks from ourLocks that have been modified
    42  	ownedLocks []*refLock
    43  
    44  	// locks from theirLocks that have been modified
    45  	unownedLocks []*refLock
    46  }
    47  
    48  func (lv *lockVerifier) Verify(ref *git.Ref) {
    49  	if lv.verifyState == verifyStateDisabled || lv.verifiedRefs[ref.Refspec()] {
    50  		return
    51  	}
    52  
    53  	lockClient := newLockClient()
    54  	ours, theirs, err := lockClient.VerifiableLocks(ref, 0)
    55  	if err != nil {
    56  		if errors.IsNotImplementedError(err) {
    57  			disableFor(lv.endpoint.Url)
    58  		} else if lv.verifyState == verifyStateUnknown || lv.verifyState == verifyStateEnabled {
    59  			if errors.IsAuthError(err) {
    60  				if lv.verifyState == verifyStateUnknown {
    61  					Error("WARNING: Authentication error: %s", err)
    62  				} else if lv.verifyState == verifyStateEnabled {
    63  					Exit("ERROR: Authentication error: %s", err)
    64  				}
    65  			} else {
    66  				Print("Remote %q does not support the LFS locking API. Consider disabling it with:", cfg.PushRemote())
    67  				Print("  $ git config lfs.%s.locksverify false", lv.endpoint.Url)
    68  				if lv.verifyState == verifyStateEnabled {
    69  					ExitWithError(err)
    70  				}
    71  			}
    72  		}
    73  	} else if lv.verifyState == verifyStateUnknown {
    74  		Print("Locking support detected on remote %q. Consider enabling it with:", cfg.PushRemote())
    75  		Print("  $ git config lfs.%s.locksverify true", lv.endpoint.Url)
    76  	}
    77  
    78  	lv.addLocks(ref, ours, lv.ourLocks)
    79  	lv.addLocks(ref, theirs, lv.theirLocks)
    80  	lv.verifiedRefs[ref.Refspec()] = true
    81  }
    82  
    83  func (lv *lockVerifier) addLocks(ref *git.Ref, locks []locking.Lock, set map[string]*refLock) {
    84  	for _, l := range locks {
    85  		if rl, ok := set[l.Path]; ok {
    86  			if err := rl.Add(ref, l); err != nil {
    87  				Error("WARNING: error adding %q lock for ref %q: %+v", l.Path, ref, err)
    88  			}
    89  		} else {
    90  			set[l.Path] = lv.newRefLocks(ref, l)
    91  		}
    92  	}
    93  }
    94  
    95  // Determines if a filename is lockable. Implements lfs.GitScannerSet
    96  func (lv *lockVerifier) Contains(name string) bool {
    97  	if lv == nil {
    98  		return false
    99  	}
   100  	_, ok := lv.theirLocks[name]
   101  	return ok
   102  }
   103  
   104  func (lv *lockVerifier) LockedByThem(name string) bool {
   105  	if lock, ok := lv.theirLocks[name]; ok {
   106  		lv.unownedLocks = append(lv.unownedLocks, lock)
   107  		return true
   108  	}
   109  	return false
   110  }
   111  
   112  func (lv *lockVerifier) LockedByUs(name string) bool {
   113  	if lock, ok := lv.ourLocks[name]; ok {
   114  		lv.ownedLocks = append(lv.ownedLocks, lock)
   115  		return true
   116  	}
   117  	return false
   118  }
   119  
   120  func (lv *lockVerifier) UnownedLocks() []*refLock {
   121  	return lv.unownedLocks
   122  }
   123  
   124  func (lv *lockVerifier) HasUnownedLocks() bool {
   125  	return len(lv.unownedLocks) > 0
   126  }
   127  
   128  func (lv *lockVerifier) OwnedLocks() []*refLock {
   129  	return lv.ownedLocks
   130  }
   131  
   132  func (lv *lockVerifier) HasOwnedLocks() bool {
   133  	return len(lv.ownedLocks) > 0
   134  }
   135  
   136  func (lv *lockVerifier) Enabled() bool {
   137  	return lv.verifyState == verifyStateEnabled
   138  }
   139  
   140  func (lv *lockVerifier) newRefLocks(ref *git.Ref, l locking.Lock) *refLock {
   141  	return &refLock{
   142  		allRefs: lv.verifiedRefs,
   143  		path:    l.Path,
   144  		refs:    map[*git.Ref]locking.Lock{ref: l},
   145  	}
   146  }
   147  
   148  func newLockVerifier(m *tq.Manifest) *lockVerifier {
   149  	lv := &lockVerifier{
   150  		endpoint:     getAPIClient().Endpoints.Endpoint("upload", cfg.PushRemote()),
   151  		verifiedRefs: make(map[string]bool),
   152  		ourLocks:     make(map[string]*refLock),
   153  		theirLocks:   make(map[string]*refLock),
   154  	}
   155  
   156  	// Do not check locks for standalone transfer, because there is no LFS
   157  	// server to ask.
   158  	if m.IsStandaloneTransfer() {
   159  		lv.verifyState = verifyStateDisabled
   160  	} else {
   161  		lv.verifyState = getVerifyStateFor(lv.endpoint.Url)
   162  	}
   163  
   164  	return lv
   165  }
   166  
   167  // refLock represents a unique locked file path, potentially across multiple
   168  // refs. It tracks each individual lock in case different users locked the
   169  // same path across multiple refs.
   170  type refLock struct {
   171  	path    string
   172  	allRefs map[string]bool
   173  	refs    map[*git.Ref]locking.Lock
   174  }
   175  
   176  // Path returns the locked path.
   177  func (r *refLock) Path() string {
   178  	return r.path
   179  }
   180  
   181  // Owners returns the list of owners that locked this file, including what
   182  // specific refs the files were locked in. If a user locked a file on all refs,
   183  // don't bother listing them.
   184  //
   185  // Example: technoweenie, bob (refs: foo)
   186  func (r *refLock) Owners() string {
   187  	users := make(map[string][]string, len(r.refs))
   188  	for ref, lock := range r.refs {
   189  		u := lock.Owner.Name
   190  		if _, ok := users[u]; !ok {
   191  			users[u] = make([]string, 0, len(r.refs))
   192  		}
   193  		users[u] = append(users[u], ref.Name)
   194  	}
   195  
   196  	owners := make([]string, 0, len(users))
   197  	for name, refs := range users {
   198  		seenRefCount := 0
   199  		for _, ref := range refs {
   200  			if r.allRefs[ref] {
   201  				seenRefCount++
   202  			}
   203  		}
   204  		if seenRefCount == len(r.allRefs) { // lock is included in all refs, so don't list them
   205  			owners = append(owners, name)
   206  			continue
   207  		}
   208  
   209  		sort.Strings(refs)
   210  		owners = append(owners, fmt.Sprintf("%s (refs: %s)", name, strings.Join(refs, ", ")))
   211  	}
   212  	sort.Strings(owners)
   213  	return strings.Join(owners, ", ")
   214  }
   215  
   216  func (r *refLock) Add(ref *git.Ref, l locking.Lock) error {
   217  	r.refs[ref] = l
   218  	return nil
   219  }
   220  
   221  // getVerifyStateFor returns whether or not lock verification is enabled for the
   222  // given url. If no state has been explicitly set, an "unknown" state will be
   223  // returned instead.
   224  func getVerifyStateFor(rawurl string) verifyState {
   225  	uc := config.NewURLConfig(cfg.Git)
   226  
   227  	v, ok := uc.Get("lfs", rawurl, "locksverify")
   228  	if !ok {
   229  		if supportsLockingAPI(rawurl) {
   230  			return verifyStateEnabled
   231  		}
   232  		return verifyStateUnknown
   233  	}
   234  
   235  	if enabled, _ := strconv.ParseBool(v); enabled {
   236  		return verifyStateEnabled
   237  	}
   238  	return verifyStateDisabled
   239  }