git-lfs/commands/command_migrate_export.go

156 lines
4.0 KiB
Go
Raw Normal View History

package commands
import (
"bytes"
"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)
opts := &githistory.RewriteOptions{
Verbose: migrateVerbose,
ObjectMapFilePath: objectMapFilePath,
BlobFn: func(path string, b *odb.Blob) (*odb.Blob, error) {
if filepath.Base(path) == ".gitattributes" {
return b, nil
}
var buf bytes.Buffer
if _, err := smudge(gitfilter, &buf, b.Contents, path, false, rewriter.Filter()); err != nil {
return nil, err
}
return &odb.Blob{
Contents: &buf, Size: int64(buf.Len()),
}, nil
},
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,
}
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())
if err := rewriter.ScanForPointers(q, opts, gitfilter); err != nil {
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)
}
// Only perform `git-checkout(1) -f` if the repository is
// non-bare.
if bare, _ := git.IsBare(); !bare {
t := l.Waiter("migrate: checkout")
err := git.Checkout("", nil, true)
t.Complete()
if err != nil {
ExitWithError(err)
}
}
}
// trackedFromExportFilter returns an ordered set of strings where each entry is a
// line in the .gitattributes file. It adds/removes the fiter/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
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
}