2015-07-23 16:53:58 +00:00
|
|
|
package commands
|
|
|
|
|
|
|
|
import (
|
2016-07-27 22:30:23 +00:00
|
|
|
"bytes"
|
2016-11-29 17:56:03 +00:00
|
|
|
"fmt"
|
2015-08-10 14:41:17 +00:00
|
|
|
"io"
|
2015-07-23 16:53:58 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"sync"
|
2015-07-28 13:50:19 +00:00
|
|
|
|
2016-11-15 17:01:18 +00:00
|
|
|
"github.com/git-lfs/git-lfs/errors"
|
2016-11-21 22:14:33 +00:00
|
|
|
"github.com/git-lfs/git-lfs/filepathfilter"
|
2016-11-15 17:01:18 +00:00
|
|
|
"github.com/git-lfs/git-lfs/git"
|
|
|
|
"github.com/git-lfs/git-lfs/lfs"
|
|
|
|
"github.com/git-lfs/git-lfs/progress"
|
2016-05-23 18:02:27 +00:00
|
|
|
"github.com/spf13/cobra"
|
2015-07-23 16:53:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func checkoutCommand(cmd *cobra.Command, args []string) {
|
2015-09-08 16:23:27 +00:00
|
|
|
requireInRepo()
|
2015-09-08 15:29:53 +00:00
|
|
|
|
2015-07-27 14:09:22 +00:00
|
|
|
// Parameters are filters
|
2015-07-28 17:03:15 +00:00
|
|
|
// firstly convert any pathspecs to the root of the repo, in case this is being executed in a sub-folder
|
2015-08-07 19:00:27 +00:00
|
|
|
var rootedpaths []string
|
|
|
|
|
2015-07-28 17:03:15 +00:00
|
|
|
inchan := make(chan string, 1)
|
|
|
|
outchan, err := lfs.ConvertCwdFilesRelativeToRepo(inchan)
|
|
|
|
if err != nil {
|
|
|
|
Panic(err, "Could not checkout")
|
|
|
|
}
|
|
|
|
for _, arg := range args {
|
|
|
|
inchan <- arg
|
|
|
|
rootedpaths = append(rootedpaths, <-outchan)
|
|
|
|
}
|
|
|
|
close(inchan)
|
2015-07-27 10:28:46 +00:00
|
|
|
|
2016-11-21 23:34:57 +00:00
|
|
|
filter := filepathfilter.New(rootedpaths, nil)
|
2016-12-06 23:52:28 +00:00
|
|
|
checkoutWithIncludeExclude(filter)
|
2015-07-29 09:26:14 +00:00
|
|
|
}
|
|
|
|
|
2016-12-07 02:10:46 +00:00
|
|
|
func checkoutFromFetchChan(in chan *lfs.WrappedPointer, filter *filepathfilter.Filter) {
|
2015-07-29 09:26:14 +00:00
|
|
|
ref, err := git.CurrentRef()
|
|
|
|
if err != nil {
|
|
|
|
Panic(err, "Could not checkout")
|
|
|
|
}
|
2016-11-29 17:56:03 +00:00
|
|
|
|
2015-07-29 09:26:14 +00:00
|
|
|
// Need to ScanTree to identify multiple files with the same content (fetch will only report oids once)
|
2016-11-29 17:56:03 +00:00
|
|
|
// use new gitscanner so mapping has all the scanned pointers before continuing
|
|
|
|
mapping := make(map[string][]*lfs.WrappedPointer)
|
2016-11-30 17:30:26 +00:00
|
|
|
chgitscanner := lfs.NewGitScanner(func(p *lfs.WrappedPointer, err error) {
|
2016-11-29 17:56:03 +00:00
|
|
|
if err != nil {
|
|
|
|
Panic(err, "Could not scan for Git LFS files")
|
|
|
|
return
|
|
|
|
}
|
2016-12-06 23:52:28 +00:00
|
|
|
mapping[p.Oid] = append(mapping[p.Oid], p)
|
2016-11-29 17:56:03 +00:00
|
|
|
})
|
2016-12-07 02:10:46 +00:00
|
|
|
chgitscanner.Filter = filter
|
2016-11-29 17:56:03 +00:00
|
|
|
|
2016-11-30 17:30:26 +00:00
|
|
|
if err := chgitscanner.ScanTree(ref.Sha, nil); err != nil {
|
2016-11-18 00:36:38 +00:00
|
|
|
ExitWithError(err)
|
|
|
|
}
|
2015-07-29 09:26:14 +00:00
|
|
|
|
2016-11-29 17:56:03 +00:00
|
|
|
chgitscanner.Close()
|
2015-07-29 09:26:14 +00:00
|
|
|
|
|
|
|
// Launch git update-index
|
|
|
|
c := make(chan *lfs.WrappedPointer)
|
2015-08-07 19:35:43 +00:00
|
|
|
|
2015-07-29 09:26:14 +00:00
|
|
|
var wait sync.WaitGroup
|
|
|
|
wait.Add(1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
checkoutWithChan(c)
|
|
|
|
wait.Done()
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Feed it from in, which comes from fetch
|
|
|
|
for p := range in {
|
|
|
|
// Add all of the files for this oid
|
|
|
|
for _, fp := range mapping[p.Oid] {
|
|
|
|
c <- fp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
close(c)
|
|
|
|
wait.Wait()
|
|
|
|
}
|
|
|
|
|
2016-12-06 23:52:28 +00:00
|
|
|
func checkoutWithIncludeExclude(filter *filepathfilter.Filter) {
|
2015-07-23 16:53:58 +00:00
|
|
|
ref, err := git.CurrentRef()
|
|
|
|
if err != nil {
|
|
|
|
Panic(err, "Could not checkout")
|
|
|
|
}
|
|
|
|
|
2016-11-29 17:56:03 +00:00
|
|
|
// this func has to load all pointers into memory
|
|
|
|
var pointers []*lfs.WrappedPointer
|
|
|
|
var multiErr error
|
|
|
|
chgitscanner := lfs.NewGitScanner(func(p *lfs.WrappedPointer, err error) {
|
|
|
|
if err != nil {
|
|
|
|
if multiErr != nil {
|
|
|
|
multiErr = fmt.Errorf("%v\n%v", multiErr, err)
|
|
|
|
} else {
|
|
|
|
multiErr = err
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
pointers = append(pointers, p)
|
|
|
|
})
|
|
|
|
|
2016-12-06 23:52:28 +00:00
|
|
|
chgitscanner.Filter = filter
|
|
|
|
|
2016-11-29 17:56:03 +00:00
|
|
|
if err := chgitscanner.ScanTree(ref.Sha, nil); err != nil {
|
2016-11-18 00:36:38 +00:00
|
|
|
ExitWithError(err)
|
|
|
|
}
|
2016-11-29 17:56:03 +00:00
|
|
|
chgitscanner.Close()
|
2016-11-18 00:36:38 +00:00
|
|
|
|
2016-11-29 17:56:03 +00:00
|
|
|
if multiErr != nil {
|
|
|
|
Panic(multiErr, "Could not scan for Git LFS files")
|
2015-07-23 16:53:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var wait sync.WaitGroup
|
|
|
|
wait.Add(1)
|
|
|
|
|
2015-08-03 09:41:20 +00:00
|
|
|
c := make(chan *lfs.WrappedPointer, 1)
|
2015-07-23 16:53:58 +00:00
|
|
|
|
2015-07-28 09:18:42 +00:00
|
|
|
go func() {
|
|
|
|
checkoutWithChan(c)
|
|
|
|
wait.Done()
|
|
|
|
}()
|
2015-07-31 15:25:41 +00:00
|
|
|
|
2016-08-10 20:23:03 +00:00
|
|
|
logPath, _ := cfg.Os.Get("GIT_LFS_PROGRESS")
|
2016-12-07 02:33:25 +00:00
|
|
|
meter := progress.NewMeter(logPath)
|
|
|
|
meter.Start()
|
|
|
|
var totalBytes int64
|
2015-07-23 16:53:58 +00:00
|
|
|
for _, pointer := range pointers {
|
2015-07-31 15:25:41 +00:00
|
|
|
totalBytes += pointer.Size
|
2016-12-07 02:33:25 +00:00
|
|
|
meter.StartTransfer(pointer.Name)
|
2016-12-06 23:52:28 +00:00
|
|
|
c <- pointer
|
|
|
|
// 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
|
2016-12-07 02:33:25 +00:00
|
|
|
meter.TransferBytes("checkout", pointer.Name, pointer.Size, totalBytes, int(pointer.Size))
|
|
|
|
meter.FinishTransfer(pointer.Name)
|
2015-07-23 16:53:58 +00:00
|
|
|
}
|
|
|
|
close(c)
|
|
|
|
wait.Wait()
|
2016-12-07 02:33:25 +00:00
|
|
|
meter.Finish()
|
2015-07-23 16:53:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Populate the working copy with the real content of objects where the file is
|
|
|
|
// either missing, or contains a matching pointer placeholder, from a list of pointers.
|
|
|
|
// If the file exists but has other content it is left alone
|
2015-08-07 19:35:43 +00:00
|
|
|
// Callers of this function MUST NOT Panic or otherwise exit the process
|
|
|
|
// without waiting for this function to shut down. If the process exits while
|
|
|
|
// update-index is in the middle of processing a file the git index can be left
|
|
|
|
// in a locked state.
|
2015-07-28 09:18:42 +00:00
|
|
|
func checkoutWithChan(in <-chan *lfs.WrappedPointer) {
|
2015-07-28 17:03:15 +00:00
|
|
|
// Get a converter from repo-relative to cwd-relative
|
|
|
|
// Since writing data & calling git update-index must be relative to cwd
|
|
|
|
repopathchan := make(chan string, 1)
|
|
|
|
cwdpathchan, err := lfs.ConvertRepoFilesRelativeToCwd(repopathchan)
|
|
|
|
if err != nil {
|
|
|
|
Panic(err, "Could not convert file paths")
|
|
|
|
}
|
|
|
|
|
2015-08-14 09:33:00 +00:00
|
|
|
// Don't fire up the update-index command until we have at least one file to
|
|
|
|
// give it. Otherwise git interprets the lack of arguments to mean param-less update-index
|
|
|
|
// which can trigger entire working copy to be re-examined, which triggers clean filters
|
|
|
|
// and which has unexpected side effects (e.g. downloading filtered-out files)
|
|
|
|
var cmd *exec.Cmd
|
|
|
|
var updateIdxStdin io.WriteCloser
|
2016-07-27 22:13:55 +00:00
|
|
|
var updateIdxOut bytes.Buffer
|
2015-07-23 16:53:58 +00:00
|
|
|
|
2015-08-07 19:35:43 +00:00
|
|
|
// From this point on, git update-index is running. Code in this loop MUST
|
|
|
|
// NOT Panic() or otherwise cause the process to exit. If the process exits
|
|
|
|
// while update-index is in the middle of updating, the index can remain in a
|
|
|
|
// locked state.
|
2015-07-28 17:03:15 +00:00
|
|
|
|
2015-07-28 09:18:42 +00:00
|
|
|
// As files come in, write them to the wd and update the index
|
2016-08-09 22:07:41 +00:00
|
|
|
|
2016-08-10 16:08:08 +00:00
|
|
|
manifest := TransferManifest()
|
2016-08-09 22:07:41 +00:00
|
|
|
|
2015-07-28 09:18:42 +00:00
|
|
|
for pointer := range in {
|
2015-07-23 16:53:58 +00:00
|
|
|
|
2015-07-28 09:18:42 +00:00
|
|
|
// Check the content - either missing or still this pointer (not exist is ok)
|
|
|
|
filepointer, err := lfs.DecodePointerFromFile(pointer.Name)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
2016-08-18 20:20:33 +00:00
|
|
|
if errors.IsNotAPointerError(err) {
|
2015-07-28 09:18:42 +00:00
|
|
|
// File has non-pointer content, leave it alone
|
|
|
|
continue
|
2015-07-23 16:53:58 +00:00
|
|
|
}
|
2015-08-07 19:35:43 +00:00
|
|
|
LoggedError(err, "Problem accessing %v", pointer.Name)
|
|
|
|
continue
|
2015-07-28 09:18:42 +00:00
|
|
|
}
|
2015-08-07 19:35:43 +00:00
|
|
|
|
2015-07-28 09:18:42 +00:00
|
|
|
if filepointer != nil && filepointer.Oid != pointer.Oid {
|
|
|
|
// User has probably manually reset a file to another commit
|
|
|
|
// while leaving it a pointer; don't mess with this
|
|
|
|
continue
|
|
|
|
}
|
2015-08-07 19:35:43 +00:00
|
|
|
|
2015-07-28 17:03:15 +00:00
|
|
|
repopathchan <- pointer.Name
|
|
|
|
cwdfilepath := <-cwdpathchan
|
2015-08-07 19:35:43 +00:00
|
|
|
|
2016-08-09 22:07:41 +00:00
|
|
|
err = lfs.PointerSmudgeToFile(cwdfilepath, pointer.Pointer, false, manifest, nil)
|
2015-07-28 09:18:42 +00:00
|
|
|
if err != nil {
|
2016-08-18 20:20:33 +00:00
|
|
|
if errors.IsDownloadDeclinedError(err) {
|
2015-08-06 16:12:11 +00:00
|
|
|
// acceptable error, data not local (fetch not run or include/exclude)
|
|
|
|
LoggedError(err, "Skipped checkout for %v, content not local. Use fetch to download.", pointer.Name)
|
|
|
|
} else {
|
2015-08-07 19:35:43 +00:00
|
|
|
LoggedError(err, "Could not checkout file")
|
|
|
|
continue
|
2015-08-06 16:12:11 +00:00
|
|
|
}
|
2015-07-23 16:53:58 +00:00
|
|
|
}
|
|
|
|
|
2016-07-05 16:21:44 +00:00
|
|
|
if cmd == nil {
|
2015-08-10 14:41:17 +00:00
|
|
|
// Fire up the update-index command
|
|
|
|
cmd = exec.Command("git", "update-index", "-q", "--refresh", "--stdin")
|
2016-07-27 22:13:55 +00:00
|
|
|
cmd.Stdout = &updateIdxOut
|
|
|
|
cmd.Stderr = &updateIdxOut
|
2015-08-10 14:41:17 +00:00
|
|
|
updateIdxStdin, err = cmd.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
Panic(err, "Could not update the index")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
Panic(err, "Could not update the index")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-07-05 16:21:44 +00:00
|
|
|
updateIdxStdin.Write([]byte(cwdfilepath + "\n"))
|
2015-07-28 09:18:42 +00:00
|
|
|
}
|
2015-07-28 17:03:15 +00:00
|
|
|
close(repopathchan)
|
2015-07-28 09:18:42 +00:00
|
|
|
|
2015-08-10 14:41:17 +00:00
|
|
|
if cmd != nil && updateIdxStdin != nil {
|
|
|
|
updateIdxStdin.Close()
|
|
|
|
if err := cmd.Wait(); err != nil {
|
2016-07-27 22:13:55 +00:00
|
|
|
LoggedError(err, "Error updating the git index:\n%s", updateIdxOut.String())
|
2015-08-10 14:41:17 +00:00
|
|
|
}
|
2015-07-28 09:18:42 +00:00
|
|
|
}
|
2015-07-23 16:53:58 +00:00
|
|
|
}
|
2016-08-10 15:33:25 +00:00
|
|
|
|
|
|
|
func init() {
|
2016-09-01 14:46:26 +00:00
|
|
|
RegisterCommand("checkout", checkoutCommand, nil)
|
2016-08-10 15:33:25 +00:00
|
|
|
}
|