Skip to content

locking: teach pre-push hook to check for locks#1815

Merged
ttaylorr merged 17 commits intomasterfrom
locking-pre-push
Jan 13, 2017
Merged

locking: teach pre-push hook to check for locks#1815
ttaylorr merged 17 commits intomasterfrom
locking-pre-push

Conversation

@ttaylorr
Copy link
Contributor

This pull-request teaches the pre-push hook to warn when you are pushing files that you own the lock on, and to error (preventing the push) when you are pushing files that you don't own the lock of.

Here's a breakdown of what happened:

  1. 18faf7a...8eb3659: teach the pre-push hook to search for locks in range
  2. b3707a6...18ec881: implement and use findLocks helper to make searching for locks more concise
  3. aa21a3d...518aeb8: integration tests for pushing owned and unowned locks
  4. f1b2f98: Preemptively use the --no-verify flag to avoid calling the pre-commit flag introduced in locking: add pre-commit hook to detect inappropriately modified files #1816.

Some things that I'd like to take a look at before merging:

  • Should the pre-push hook be configurable where to search for locks? It seems that callers should be able to decide between searching the local lock cache, or searching the remote's locks. A flag seems like the wrong approach, since you'd have to update .git/hooks/pre-push each time, but a config option seems reasonable.
  • Should the pre-push hook warn about your own locks? Should this be configurable?
  • Should the commands package own a singleton, private, instance of a *locking.Client?

/cc @git-lfs/core

Copy link
Contributor

@sinbad sinbad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main comment here is that although I agree with these changes, catching the conflict at pre-push time should be the last resort, because at that point someone may well have to lose some work. We should aim to catch conflicts like this much earlier; that's part of the read-only work which I have in progress (rewritten from previous PR). But as a safety check it's fine.

I wondered whether we should try to allow --force to override this, but I don't think we have sight of that option in the hook (without platform-specific hacks like calling ps). The force unlock command is the safety valve there.

}

if len(myLocks) > 0 {
Print("Pushing your locked files:")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to explain more what this means in practice - i.e. that the user should consider unlocking the files if they have no further changes to publish after this push.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! Added that in f82c96d.

}

if len(lockConflicts) > 0 {
Error("Some files are locked in %s...%s", left, cfg.CurrentRemote)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could be explicit like "You cannot push changes because someone else has these files locked"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good: c9aa19a.

func findLocks(lc *locking.Client, filter map[string]string, limit int, localOnly bool) (map[string]locking.Lock, error) {
locks, err := lc.SearchLocks(filter, limit, localOnly)
if err != nil {
return nil, errors.Wrap(err, "error finding locks")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just "finding locks" is a good error wrap message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad! Went ahead and cleaned this up in c0047f6.

// returned immediately, otherise a map of lock.Path -> lock will be returned
// instead.
func findLocks(lc *locking.Client, filter map[string]string, limit int, localOnly bool) (map[string]locking.Lock, error) {
locks, err := lc.SearchLocks(filter, limit, localOnly)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the client do anything special for 404/410 errors? Merging this PR would halt all pushes for LFS servers without the locking endpoints.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for brining this up 😄 . I went ahead and changed the implementation of the Search() method on the *locking.Client to only try and deserialize the response if it was a 200 OK. If the response was a 404 or 410, the lack of deserialization would return an empty slice of locks, making the lock-related parts of the pre-push command effectively a no-op.

I don't think any changes are needed for the Lock() and Unlock() methods, since those are mutative operations and should error out if the server doesn't implement them.

Changes are in: 6fb728a.

@ttaylorr
Copy link
Contributor Author

OK, everything should be up-to-date and work with the latest changes from #1812 and #1863. I opted to put this implementation in the *commands.uploadContext, since it's an easy 🎯, but this should definitely receive some more attention in the future if/when we decide to get rid of *uploadContext.

var canUpload bool = true

lockQuery := map[string]string{"path": p.Name}
locks, err := c.lockClient.SearchLocks(lockQuery, 1, false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making an API call per file seems like a lot of overhead. I think our early attempts queried all the locks up front, and checked each file locally. That does mean that we'll have to maintain a potentially huge list of locks in memory locally, so we may want to look at an improved interface for verifying groups of files in a single POST request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really good call; I implemented this change in 25bd4a1.

@ttaylorr
Copy link
Contributor Author

Some things I'd like to follow up on, post-merge:

  • Teach pre-push to be able to configure whether to use the local lock cache exclusively.
  • Teach pre-push to stop pushing files if encountered locked file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants