2018-06-13 21:10:32 +00:00
|
|
|
package commands
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"github.com/git-lfs/git-lfs/errors"
|
|
|
|
"github.com/git-lfs/git-lfs/filepathfilter"
|
|
|
|
"github.com/git-lfs/git-lfs/git"
|
|
|
|
"github.com/git-lfs/git-lfs/git/githistory"
|
|
|
|
"github.com/git-lfs/git-lfs/git/odb"
|
|
|
|
"github.com/git-lfs/git-lfs/lfs"
|
|
|
|
"github.com/git-lfs/git-lfs/tasklog"
|
|
|
|
"github.com/git-lfs/git-lfs/tools"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
|
|
|
func migrateExportCommand(cmd *cobra.Command, args []string) {
|
|
|
|
l := tasklog.NewLogger(os.Stderr)
|
|
|
|
defer l.Close()
|
|
|
|
|
|
|
|
db, err := getObjectDatabase()
|
|
|
|
if err != nil {
|
|
|
|
ExitWithError(err)
|
|
|
|
}
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
rewriter := getHistoryRewriter(cmd, db, l)
|
|
|
|
|
|
|
|
filter := rewriter.Filter()
|
|
|
|
if len(filter.Include()) <= 0 {
|
|
|
|
ExitWithError(errors.Errorf("fatal: one or more files must be specified with --include"))
|
|
|
|
}
|
|
|
|
|
|
|
|
tracked := trackedFromExportFilter(filter)
|
|
|
|
gitfilter := lfs.NewGitFilter(cfg)
|
|
|
|
|
2018-06-21 21:03:54 +00:00
|
|
|
opts := &githistory.RewriteOptions{
|
2018-06-13 21:10:32 +00:00
|
|
|
Verbose: migrateVerbose,
|
|
|
|
ObjectMapFilePath: objectMapFilePath,
|
|
|
|
BlobFn: func(path string, b *odb.Blob) (*odb.Blob, error) {
|
|
|
|
if filepath.Base(path) == ".gitattributes" {
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
2018-06-25 21:36:57 +00:00
|
|
|
ptr, err := lfs.DecodePointer(b.Contents)
|
|
|
|
if errors.IsNotAPointerError(err) {
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-06-13 21:10:32 +00:00
|
|
|
|
2018-06-25 21:36:57 +00:00
|
|
|
downloadPath, err := gitfilter.ObjectPath(ptr.Oid)
|
|
|
|
if err != nil {
|
2018-06-13 21:10:32 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-06-25 21:36:57 +00:00
|
|
|
return odb.NewBlobFromFile(downloadPath)
|
2018-06-13 21:10:32 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
TreeCallbackFn: func(path string, t *odb.Tree) (*odb.Tree, error) {
|
|
|
|
if path != "/" {
|
|
|
|
// Ignore non-root trees.
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ours := tracked
|
|
|
|
theirs, err := trackedFromAttrs(db, t)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a blob of the attributes that are optionally
|
|
|
|
// present in the "t" tree's .gitattributes blob, and
|
|
|
|
// union in the patterns that we've tracked.
|
|
|
|
//
|
|
|
|
// Perform this Union() operation each time we visit a
|
|
|
|
// root tree such that if the underlying .gitattributes
|
|
|
|
// is present and has a diff between commits in the
|
|
|
|
// range of commits to migrate, those changes are
|
|
|
|
// preserved.
|
|
|
|
blob, err := trackedToBlob(db, theirs.Clone().Union(ours))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, return a copy of the tree "t" that has the
|
|
|
|
// new .gitattributes file included/replaced.
|
|
|
|
return t.Merge(&odb.TreeEntry{
|
|
|
|
Name: ".gitattributes",
|
|
|
|
Filemode: 0100644,
|
|
|
|
Oid: blob,
|
|
|
|
}), nil
|
|
|
|
},
|
|
|
|
|
|
|
|
UpdateRefs: true,
|
2018-06-21 21:03:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
requireInRepo()
|
|
|
|
|
|
|
|
opts, err = rewriteOptions(args, opts, l)
|
|
|
|
if err != nil {
|
|
|
|
ExitWithError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have a valid remote, pre-download all objects using the Transfer Queue
|
|
|
|
if remoteURL := getAPIClient().Endpoints.RemoteEndpoint("download", cfg.Remote()).Url; remoteURL != "" {
|
|
|
|
q := newDownloadQueue(getTransferManifestOperationRemote("Download", cfg.Remote()), cfg.Remote())
|
2018-06-21 22:43:34 +00:00
|
|
|
if err := rewriter.ScanForPointers(q, opts, gitfilter); err != nil {
|
2018-06-21 21:03:54 +00:00
|
|
|
ExitWithError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
q.Wait()
|
|
|
|
|
|
|
|
for _, err := range q.Errors() {
|
|
|
|
if err != nil {
|
|
|
|
ExitWithError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform the rewrite
|
|
|
|
if _, err := rewriter.Rewrite(opts); err != nil {
|
|
|
|
ExitWithError(err)
|
|
|
|
}
|
2018-06-13 21:10:32 +00:00
|
|
|
|
2018-06-25 21:10:31 +00:00
|
|
|
// Only perform `git-checkout(1) -f` if the repository is non-bare.
|
2018-06-13 21:10:32 +00:00
|
|
|
if bare, _ := git.IsBare(); !bare {
|
|
|
|
t := l.Waiter("migrate: checkout")
|
|
|
|
err := git.Checkout("", nil, true)
|
|
|
|
t.Complete()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
ExitWithError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-25 21:10:31 +00:00
|
|
|
// trackedFromExportFilter returns an ordered set of strings where each entry
|
|
|
|
// is a line we intend to place in the .gitattributes file. It adds/removes the
|
|
|
|
// filter/diff/merge=lfs attributes based on patterns included/excluded in the
|
|
|
|
// given filter. Since `migrate export` removes files from Git LFS, it will
|
|
|
|
// remove attributes for included files, and add attributes for excluded files
|
2018-06-13 21:10:32 +00:00
|
|
|
func trackedFromExportFilter(filter *filepathfilter.Filter) *tools.OrderedSet {
|
|
|
|
tracked := tools.NewOrderedSet()
|
|
|
|
|
|
|
|
for _, include := range filter.Include() {
|
|
|
|
tracked.Add(fmt.Sprintf("%s text -filter -merge -diff", escapeAttrPattern(include)))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, exclude := range filter.Exclude() {
|
|
|
|
tracked.Add(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -text", escapeAttrPattern(exclude)))
|
|
|
|
}
|
|
|
|
|
|
|
|
return tracked
|
|
|
|
}
|