482260c7e3
Error strings should not be capitalized (unless beginning with proper nouns or acronyms) or end with punctuation: https://github.com/golang/go/wiki/CodeReviewComments#error-strings
171 lines
4.3 KiB
Go
171 lines
4.3 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/git-lfs/git-lfs/filepathfilter"
|
|
"github.com/git-lfs/git-lfs/git"
|
|
"github.com/git-lfs/git-lfs/lfs"
|
|
"github.com/git-lfs/git-lfs/tasklog"
|
|
"github.com/git-lfs/git-lfs/tq"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
checkoutTo string
|
|
checkoutBase bool
|
|
checkoutOurs bool
|
|
checkoutTheirs bool
|
|
)
|
|
|
|
func checkoutCommand(cmd *cobra.Command, args []string) {
|
|
requireInRepo()
|
|
|
|
stage, err := whichCheckout()
|
|
if err != nil {
|
|
Exit("Error parsing args: %v", err)
|
|
}
|
|
|
|
if checkoutTo != "" && stage != git.IndexStageDefault {
|
|
checkoutConflict(rootedPaths(args)[0], stage)
|
|
return
|
|
} else if checkoutTo != "" || stage != git.IndexStageDefault {
|
|
Exit("--to and exactly one of --theirs, --ours, and --base must be used together")
|
|
}
|
|
|
|
ref, err := git.CurrentRef()
|
|
if err != nil {
|
|
Panic(err, "Could not checkout")
|
|
}
|
|
|
|
singleCheckout := newSingleCheckout(cfg.Git, "")
|
|
if singleCheckout.Skip() {
|
|
fmt.Println("Cannot checkout LFS objects, Git LFS is not installed.")
|
|
return
|
|
}
|
|
|
|
var totalBytes int64
|
|
var pointers []*lfs.WrappedPointer
|
|
logger := tasklog.NewLogger(os.Stdout,
|
|
tasklog.ForceProgress(cfg.ForceProgress()),
|
|
)
|
|
meter := tq.NewMeter(cfg)
|
|
meter.Direction = tq.Checkout
|
|
meter.Logger = meter.LoggerFromEnv(cfg.Os)
|
|
logger.Enqueue(meter)
|
|
chgitscanner := lfs.NewGitScanner(cfg, func(p *lfs.WrappedPointer, err error) {
|
|
if err != nil {
|
|
LoggedError(err, "Scanner error: %s", err)
|
|
return
|
|
}
|
|
|
|
totalBytes += p.Size
|
|
meter.Add(p.Size)
|
|
meter.StartTransfer(p.Name)
|
|
pointers = append(pointers, p)
|
|
})
|
|
|
|
chgitscanner.Filter = filepathfilter.New(rootedPaths(args), nil)
|
|
|
|
if err := chgitscanner.ScanTree(ref.Sha); err != nil {
|
|
ExitWithError(err)
|
|
}
|
|
chgitscanner.Close()
|
|
|
|
meter.Start()
|
|
for _, p := range pointers {
|
|
singleCheckout.Run(p)
|
|
|
|
// not strictly correct (parallel) but we don't have a callback & it's just local
|
|
// plus only 1 slot in channel so it'll block & be close
|
|
meter.TransferBytes("checkout", p.Name, p.Size, totalBytes, int(p.Size))
|
|
meter.FinishTransfer(p.Name)
|
|
}
|
|
|
|
meter.Finish()
|
|
singleCheckout.Close()
|
|
}
|
|
|
|
func checkoutConflict(file string, stage git.IndexStage) {
|
|
singleCheckout := newSingleCheckout(cfg.Git, "")
|
|
if singleCheckout.Skip() {
|
|
fmt.Println("Cannot checkout LFS objects, Git LFS is not installed.")
|
|
return
|
|
}
|
|
|
|
ref, err := git.ResolveRef(fmt.Sprintf(":%d:%s", stage, file))
|
|
if err != nil {
|
|
Exit("Could not checkout (are you not in the middle of a merge?): %v", err)
|
|
}
|
|
|
|
scanner, err := git.NewObjectScanner(cfg.OSEnv())
|
|
if err != nil {
|
|
Exit("Could not create object scanner: %v", err)
|
|
}
|
|
|
|
if !scanner.Scan(ref.Sha) {
|
|
Exit("Could not find object %q", ref.Sha)
|
|
}
|
|
|
|
ptr, err := lfs.DecodePointer(scanner.Contents())
|
|
if err != nil {
|
|
Exit("Could not find decoder pointer for object %q: %v", ref.Sha, err)
|
|
}
|
|
|
|
p := &lfs.WrappedPointer{Name: file, Pointer: ptr}
|
|
|
|
if err := singleCheckout.RunToPath(p, checkoutTo); err != nil {
|
|
Exit("Error checking out %v to %q: %v", ref.Sha, checkoutTo, err)
|
|
}
|
|
singleCheckout.Close()
|
|
}
|
|
|
|
func whichCheckout() (stage git.IndexStage, err error) {
|
|
seen := 0
|
|
stage = git.IndexStageDefault
|
|
|
|
if checkoutBase {
|
|
seen++
|
|
stage = git.IndexStageBase
|
|
}
|
|
if checkoutOurs {
|
|
seen++
|
|
stage = git.IndexStageOurs
|
|
}
|
|
if checkoutTheirs {
|
|
seen++
|
|
stage = git.IndexStageTheirs
|
|
}
|
|
|
|
if seen > 1 {
|
|
return 0, fmt.Errorf("at most one of --base, --theirs, and --ours is allowed")
|
|
}
|
|
return stage, nil
|
|
}
|
|
|
|
// Parameters are filters
|
|
// firstly convert any pathspecs to the root of the repo, in case this is being
|
|
// executed in a sub-folder
|
|
func rootedPaths(args []string) []string {
|
|
pathConverter, err := lfs.NewCurrentToRepoPathConverter(cfg)
|
|
if err != nil {
|
|
Panic(err, "Could not checkout")
|
|
}
|
|
|
|
rootedpaths := make([]string, 0, len(args))
|
|
for _, arg := range args {
|
|
rootedpaths = append(rootedpaths, pathConverter.Convert(arg))
|
|
}
|
|
return rootedpaths
|
|
}
|
|
|
|
func init() {
|
|
RegisterCommand("checkout", checkoutCommand, func(cmd *cobra.Command) {
|
|
cmd.Flags().StringVar(&checkoutTo, "to", "", "Checkout a conflicted file to this path")
|
|
cmd.Flags().BoolVar(&checkoutOurs, "ours", false, "Checkout our version of a conflicted file")
|
|
cmd.Flags().BoolVar(&checkoutTheirs, "theirs", false, "Checkout their version of a conflicted file")
|
|
cmd.Flags().BoolVar(&checkoutBase, "base", false, "Checkout the base version of a conflicted file")
|
|
})
|
|
}
|