9db862c28d
When a user has gotten their locks into a bad state, it should be possible to unlock them by using the ID and --force, even if Git LFS thinks that the file does not exist or that the repository is for some reason not in a sane state. Consequently, skip the check for existence in --force mode and just attempt the operation. This allows users to recover from an issue where we could produce relative paths with ".." in them when using junctions on Windows, which prevented the locks from being unlocked.
146 lines
3.6 KiB
Go
146 lines
3.6 KiB
Go
package commands
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
|
|
"github.com/git-lfs/git-lfs/errors"
|
|
"github.com/git-lfs/git-lfs/git"
|
|
"github.com/git-lfs/git-lfs/locking"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
unlockCmdFlags unlockFlags
|
|
)
|
|
|
|
// unlockFlags holds the flags given to the `git lfs unlock` command
|
|
type unlockFlags struct {
|
|
// Id is the Id of the lock that is being unlocked.
|
|
Id string
|
|
// Force specifies whether or not the `lfs unlock` command was invoked
|
|
// with "--force", signifying the user's intent to break another
|
|
// individual's lock(s).
|
|
Force bool
|
|
}
|
|
|
|
var unlockUsage = "Usage: git lfs unlock (--id my-lock-id | <path>)"
|
|
|
|
func unlockCommand(cmd *cobra.Command, args []string) {
|
|
hasPath := len(args) > 0
|
|
hasId := len(unlockCmdFlags.Id) > 0
|
|
if hasPath == hasId {
|
|
// If there is both an `--id` AND a `<path>`, or there is
|
|
// neither, print the usage and quit.
|
|
Exit(unlockUsage)
|
|
}
|
|
|
|
if len(lockRemote) > 0 {
|
|
cfg.SetRemote(lockRemote)
|
|
}
|
|
|
|
refUpdate := git.NewRefUpdate(cfg.Git, cfg.PushRemote(), cfg.CurrentRef(), nil)
|
|
lockClient := newLockClient()
|
|
lockClient.RemoteRef = refUpdate.Right()
|
|
defer lockClient.Close()
|
|
|
|
if hasPath {
|
|
path, err := lockPath(args[0])
|
|
if err != nil {
|
|
if !unlockCmdFlags.Force {
|
|
Exit("Unable to determine path: %v", err.Error())
|
|
}
|
|
path = args[0]
|
|
}
|
|
|
|
// This call can early-out
|
|
unlockAbortIfFileModified(path)
|
|
|
|
err = lockClient.UnlockFile(path, unlockCmdFlags.Force)
|
|
if err != nil {
|
|
Exit("%s", errors.Cause(err))
|
|
}
|
|
|
|
if !locksCmdFlags.JSON {
|
|
Print("Unlocked %s", path)
|
|
return
|
|
}
|
|
} else if unlockCmdFlags.Id != "" {
|
|
// This call can early-out
|
|
unlockAbortIfFileModifiedById(unlockCmdFlags.Id, lockClient)
|
|
|
|
err := lockClient.UnlockFileById(unlockCmdFlags.Id, unlockCmdFlags.Force)
|
|
if err != nil {
|
|
Exit("Unable to unlock %v: %v", unlockCmdFlags.Id, errors.Cause(err))
|
|
}
|
|
|
|
if !locksCmdFlags.JSON {
|
|
Print("Unlocked Lock %s", unlockCmdFlags.Id)
|
|
return
|
|
}
|
|
} else {
|
|
Error(unlockUsage)
|
|
}
|
|
|
|
if err := json.NewEncoder(os.Stdout).Encode(struct {
|
|
Unlocked bool `json:"unlocked"`
|
|
}{true}); err != nil {
|
|
Error(err.Error())
|
|
}
|
|
return
|
|
}
|
|
|
|
func unlockAbortIfFileModified(path string) {
|
|
modified, err := git.IsFileModified(path)
|
|
|
|
if err != nil {
|
|
if unlockCmdFlags.Force {
|
|
// Since git/git@b9a7d55, `git-status(1)` causes an
|
|
// error when asked about files that don't exist,
|
|
// causing `err != nil`, as above.
|
|
//
|
|
// Unlocking a files that does not exist with
|
|
// --force is OK.
|
|
return
|
|
}
|
|
Exit(err.Error())
|
|
}
|
|
|
|
if modified {
|
|
if unlockCmdFlags.Force {
|
|
// Only a warning
|
|
Error("Warning: unlocking with uncommitted changes because --force")
|
|
} else {
|
|
Exit("Cannot unlock file with uncommitted changes")
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func unlockAbortIfFileModifiedById(id string, lockClient *locking.Client) {
|
|
// Get the path so we can check the status
|
|
filter := map[string]string{"id": id}
|
|
// try local cache first
|
|
locks, _ := lockClient.SearchLocks(filter, 0, true, false)
|
|
if len(locks) == 0 {
|
|
// Fall back on calling server
|
|
locks, _ = lockClient.SearchLocks(filter, 0, false, false)
|
|
}
|
|
|
|
if len(locks) == 0 {
|
|
// Don't block if we can't determine the path, may be cleaning up old data
|
|
return
|
|
}
|
|
|
|
unlockAbortIfFileModified(locks[0].Path)
|
|
}
|
|
|
|
func init() {
|
|
RegisterCommand("unlock", unlockCommand, func(cmd *cobra.Command) {
|
|
cmd.Flags().StringVarP(&lockRemote, "remote", "r", "", lockRemoteHelp)
|
|
cmd.Flags().StringVarP(&unlockCmdFlags.Id, "id", "i", "", "unlock a lock by its ID")
|
|
cmd.Flags().BoolVarP(&unlockCmdFlags.Force, "force", "f", false, "forcibly break another user's lock(s)")
|
|
cmd.Flags().BoolVarP(&locksCmdFlags.JSON, "json", "", false, "print output in json")
|
|
})
|
|
}
|