locking: new "locks --verify" option

"locks --verify" verifies the server-side lock owner and denotes own
locks in the output by 'O'.

Internally, the /locks/verify API is used to have the server reporting
locks categorized by "ours" and "theirs". Note that this categorization
must be performed on the server because only the server knows the
mapping between login and lock owner name.

This basically resolves issue #3252 but does not yet address the
combination of "--verify" and "--cached".
This commit is contained in:
Marc Strapetz 2019-03-28 10:12:33 +01:00
parent b0e7a6fed9
commit ffe6220b62
3 changed files with 73 additions and 5 deletions

@ -1,7 +1,7 @@
package commands package commands
import ( import (
"encoding/json" "io"
"os" "os"
"sort" "sort"
"strings" "strings"
@ -44,11 +44,44 @@ func locksCommand(cmd *cobra.Command, args []string) {
} }
} }
locks, err := lockClient.SearchLocks(filters, locksCmdFlags.Limit, locksCmdFlags.Local, locksCmdFlags.Cached) if locksCmdFlags.Verify {
if len(filters) > 0 {
Exit("--verify option can't be combined with filters")
}
if locksCmdFlags.Local {
Exit("--verify option can't be combined with --local")
}
if locksCmdFlags.Cached {
Exit("--verify option can't be combined with --cached")
}
}
var locks []locking.Lock
var locksOwned map[locking.Lock]bool
var jsonWriteFunc func(io.Writer) error
if locksCmdFlags.Verify {
var ourLocks, theirLocks []locking.Lock
ourLocks, theirLocks, err = lockClient.SearchLocksVerifiable(locksCmdFlags.Limit)
jsonWriteFunc = func(writer io.Writer) error {
return lockClient.EncodeLocksVerifiable(ourLocks, theirLocks, writer)
}
locks = append(ourLocks, theirLocks...)
locksOwned = make(map[locking.Lock]bool)
for _, lock := range ourLocks {
locksOwned[lock] = true
}
} else {
locks, err = lockClient.SearchLocks(filters, locksCmdFlags.Limit, locksCmdFlags.Local, locksCmdFlags.Cached)
jsonWriteFunc = func(writer io.Writer) error {
return lockClient.EncodeLocks(locks, writer)
}
}
// Print any we got before exiting // Print any we got before exiting
if locksCmdFlags.JSON { if locksCmdFlags.JSON {
if err := json.NewEncoder(os.Stdout).Encode(locks); err != nil { if err := jsonWriteFunc(os.Stdout); err != nil {
Error(err.Error()) Error(err.Error())
} }
return return
@ -77,7 +110,16 @@ func locksCommand(cmd *cobra.Command, args []string) {
pathPadding := tools.MaxInt(maxPathLen-len(lock.Path), 0) pathPadding := tools.MaxInt(maxPathLen-len(lock.Path), 0)
namePadding := tools.MaxInt(maxNameLen-len(ownerName), 0) namePadding := tools.MaxInt(maxNameLen-len(ownerName), 0)
Print("%s%s\t%s%s\tID:%s", lock.Path, strings.Repeat(" ", pathPadding), kind := ""
if locksOwned != nil {
if locksOwned[lock] {
kind = "O "
} else {
kind = " "
}
}
Print("%s%s%s\t%s%s\tID:%s", kind, lock.Path, strings.Repeat(" ", pathPadding),
ownerName, strings.Repeat(" ", namePadding), ownerName, strings.Repeat(" ", namePadding),
lock.Id, lock.Id,
) )
@ -108,6 +150,9 @@ type locksFlags struct {
// for non-local queries, report cached query results from the last query // for non-local queries, report cached query results from the last query
// instead of actually querying the server again // instead of actually querying the server again
Cached bool Cached bool
// for non-local queries, verify lock owner on server and
// denote our locks in output
Verify bool
} }
// Filters produces a filter based on locksFlags instance. // Filters produces a filter based on locksFlags instance.
@ -137,6 +182,7 @@ func init() {
cmd.Flags().IntVarP(&locksCmdFlags.Limit, "limit", "l", 0, "optional limit for number of results to return") cmd.Flags().IntVarP(&locksCmdFlags.Limit, "limit", "l", 0, "optional limit for number of results to return")
cmd.Flags().BoolVarP(&locksCmdFlags.Local, "local", "", false, "only list cached local record of own locks") cmd.Flags().BoolVarP(&locksCmdFlags.Local, "local", "", false, "only list cached local record of own locks")
cmd.Flags().BoolVarP(&locksCmdFlags.Cached, "cached", "", false, "list cached lock information from the last remote query, instead of actually querying the server") cmd.Flags().BoolVarP(&locksCmdFlags.Cached, "cached", "", false, "list cached lock information from the last remote query, instead of actually querying the server")
cmd.Flags().BoolVarP(&locksCmdFlags.Verify, "verify", "", false, "verify lock owner on server and mark own locks by 'O'")
cmd.Flags().BoolVarP(&locksCmdFlags.JSON, "json", "", false, "print output in json") cmd.Flags().BoolVarP(&locksCmdFlags.JSON, "json", "", false, "print output in json")
}) })
} }

@ -29,6 +29,16 @@ Lists current locks from the Git LFS server.
last known locks in case you are offline. There is no guarantee that locks last known locks in case you are offline. There is no guarantee that locks
on the server have not changed in the meanwhile. on the server have not changed in the meanwhile.
* `--verify`:
Verifies the lock owner on the server and marks our own locks by 'O'.
Own locks are actually held by us and corresponding files can be updated for
the next push. All other locks are held by someone else.
Contrary to --local, this option will also detect locks which are held by us
despite no local lock information being available (e.g. because the file had
been locked from a different clone);
it will also detect 'broken' locks (e.g. if someone else has forcefully
unlocked our files).
* `-l <num>` `--limit=<num>`: * `-l <num>` `--limit=<num>`:
Specifies number of results to return. Specifies number of results to return.

@ -3,6 +3,7 @@ package locking
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -468,13 +469,24 @@ func (c *Client) readLocksFromCacheFile(path string) ([]Lock, error) {
return locks, nil return locks, nil
} }
func (c *Client) EncodeLocks(locks []Lock, writer io.Writer) error {
return json.NewEncoder(writer).Encode(locks)
}
func (c *Client) EncodeLocksVerifiable(ourLocks, theirLocks []Lock, writer io.Writer) error {
return json.NewEncoder(writer).Encode(&lockVerifiableList{
Ours: ourLocks,
Theirs: theirLocks,
})
}
func (c *Client) writeLocksToCacheFile(path string, locks []Lock) error { func (c *Client) writeLocksToCacheFile(path string, locks []Lock) error {
file, err := os.Create(path) file, err := os.Create(path)
if err != nil { if err != nil {
return err return err
} }
err = json.NewEncoder(file).Encode(locks) err = c.EncodeLocks(locks, file)
if err != nil { if err != nil {
file.Close() file.Close()
return err return err