2017-06-21 16:43:56 +00:00
|
|
|
package commands
|
|
|
|
|
2017-06-23 18:41:55 +00:00
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/git-lfs/git-lfs/filepathfilter"
|
|
|
|
"github.com/git-lfs/git-lfs/git"
|
|
|
|
"github.com/git-lfs/git-lfs/git/githistory"
|
2017-08-29 21:05:11 +00:00
|
|
|
"github.com/git-lfs/git-lfs/git/githistory/log"
|
2017-06-23 18:41:55 +00:00
|
|
|
"github.com/git-lfs/git-lfs/git/odb"
|
|
|
|
"github.com/git-lfs/git-lfs/tools"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
2017-06-21 16:43:56 +00:00
|
|
|
|
|
|
|
func migrateImportCommand(cmd *cobra.Command, args []string) {
|
2017-08-29 21:05:11 +00:00
|
|
|
l := log.NewLogger(os.Stderr)
|
|
|
|
|
2017-06-23 18:41:55 +00:00
|
|
|
db, err := getObjectDatabase()
|
|
|
|
if err != nil {
|
|
|
|
ExitWithError(err)
|
|
|
|
}
|
2017-09-19 20:07:37 +00:00
|
|
|
defer db.Close()
|
|
|
|
|
2017-08-29 21:05:11 +00:00
|
|
|
rewriter := getHistoryRewriter(cmd, db, l)
|
2017-06-23 18:41:55 +00:00
|
|
|
|
|
|
|
tracked := trackedFromFilter(rewriter.Filter())
|
|
|
|
exts := tools.NewOrderedSet()
|
|
|
|
|
2017-08-29 21:05:11 +00:00
|
|
|
migrate(args, rewriter, l, &githistory.RewriteOptions{
|
2017-09-22 18:43:04 +00:00
|
|
|
Verbose: migrateVerbose,
|
2017-06-23 18:41:55 +00:00
|
|
|
BlobFn: func(path string, b *odb.Blob) (*odb.Blob, error) {
|
2017-06-26 17:27:21 +00:00
|
|
|
if filepath.Base(path) == ".gitattributes" {
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
2017-06-23 18:41:55 +00:00
|
|
|
var buf bytes.Buffer
|
|
|
|
|
2017-08-15 15:39:06 +00:00
|
|
|
if _, err := clean(&buf, b.Contents, path, b.Size); err != nil {
|
2017-06-23 18:41:55 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-06-26 15:07:18 +00:00
|
|
|
if ext := filepath.Ext(path); len(ext) > 0 {
|
2017-06-23 18:41:55 +00:00
|
|
|
exts.Add(fmt.Sprintf("*%s filter=lfs diff=lfs merge=lfs -text", ext))
|
|
|
|
}
|
|
|
|
|
|
|
|
return &odb.Blob{
|
|
|
|
Contents: &buf, Size: int64(buf.Len()),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
|
|
|
|
TreeCallbackFn: func(path string, t *odb.Tree) (*odb.Tree, error) {
|
|
|
|
if path != string(os.PathSeparator) {
|
|
|
|
// Ignore non-root trees.
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
2017-06-26 16:51:51 +00:00
|
|
|
ours := tracked
|
|
|
|
if ours.Cardinality() == 0 {
|
2017-06-23 18:41:55 +00:00
|
|
|
// If there were no explicitly tracked
|
|
|
|
// --include, --exclude filters, assume that the
|
|
|
|
// include set is the wildcard filepath
|
|
|
|
// extensions of files tracked.
|
2017-06-26 16:51:51 +00:00
|
|
|
ours = exts
|
2017-06-23 18:41:55 +00:00
|
|
|
}
|
|
|
|
|
2017-06-26 16:51:51 +00:00
|
|
|
theirs, err := trackedFromAttrs(db, t)
|
2017-06-23 18:41:55 +00:00
|
|
|
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.
|
2017-06-26 16:51:51 +00:00
|
|
|
blob, err := trackedToBlob(db, theirs.Clone().Union(ours))
|
2017-06-23 18:41:55 +00:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
|
2017-09-22 21:57:07 +00:00
|
|
|
// Only perform `git-checkout(1) -f` if the repository is
|
|
|
|
// non-bare.
|
2017-07-05 16:29:50 +00:00
|
|
|
if bare, _ := git.IsBare(); !bare {
|
2017-09-22 21:57:07 +00:00
|
|
|
t := l.Waiter("migrate: checkout")
|
|
|
|
err := git.Checkout("", nil, true)
|
|
|
|
t.Complete()
|
|
|
|
|
|
|
|
if err != nil {
|
2017-07-05 16:29:50 +00:00
|
|
|
ExitWithError(err)
|
|
|
|
}
|
2017-06-23 18:41:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// trackedFromFilter 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/excldued in the given filter.
|
|
|
|
func trackedFromFilter(filter *filepathfilter.Filter) *tools.OrderedSet {
|
|
|
|
tracked := tools.NewOrderedSet()
|
|
|
|
|
|
|
|
for _, include := range filter.Include() {
|
|
|
|
tracked.Add(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -text", include))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, exclude := range filter.Exclude() {
|
|
|
|
tracked.Add(fmt.Sprintf("%s text -filter -merge -diff", exclude))
|
|
|
|
}
|
|
|
|
|
|
|
|
return tracked
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
// attrsCache maintains a cache from the hex-encoded SHA1 of a
|
|
|
|
// .gitattributes blob to the set of patterns parsed from that blob.
|
2017-06-26 17:27:21 +00:00
|
|
|
attrsCache = make(map[string]*tools.OrderedSet)
|
2017-06-23 18:41:55 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// trackedFromAttrs returns an ordered line-delimited set of the contents of a
|
|
|
|
// .gitattributes blob in a given tree "t".
|
|
|
|
//
|
|
|
|
// It returns an empty set if no attributes file could be found, or an error if
|
|
|
|
// it could not otherwise be opened.
|
|
|
|
func trackedFromAttrs(db *odb.ObjectDatabase, t *odb.Tree) (*tools.OrderedSet, error) {
|
|
|
|
var oid []byte
|
|
|
|
|
|
|
|
for _, e := range t.Entries {
|
|
|
|
if strings.ToLower(e.Name) == ".gitattributes" && e.Type() == odb.BlobObjectType {
|
|
|
|
oid = e.Oid
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if oid == nil {
|
|
|
|
// TODO(@ttaylorr): make (*tools.OrderedSet)(nil) a valid
|
|
|
|
// receiver for non-mutative methods.
|
|
|
|
return tools.NewOrderedSet(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
sha1 := hex.EncodeToString(oid)
|
|
|
|
|
|
|
|
if s, ok := attrsCache[sha1]; ok {
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
blob, err := db.Blob(oid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
attrs := tools.NewOrderedSet()
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(blob.Contents)
|
|
|
|
for scanner.Scan() {
|
|
|
|
attrs.Add(scanner.Text())
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
attrsCache[sha1] = attrs
|
|
|
|
|
|
|
|
return attrsCache[sha1], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// trackedToBlob writes and returns the OID of a .gitattributes blob based on
|
|
|
|
// the patterns given in the ordered set of patterns, "patterns".
|
|
|
|
func trackedToBlob(db *odb.ObjectDatabase, patterns *tools.OrderedSet) ([]byte, error) {
|
|
|
|
var attrs bytes.Buffer
|
|
|
|
|
|
|
|
for pattern := range patterns.Iter() {
|
|
|
|
fmt.Fprintf(&attrs, "%s\n", pattern)
|
|
|
|
}
|
2017-06-21 16:43:56 +00:00
|
|
|
|
2017-06-23 18:41:55 +00:00
|
|
|
return db.WriteBlob(&odb.Blob{
|
|
|
|
Contents: &attrs,
|
|
|
|
Size: int64(attrs.Len()),
|
|
|
|
})
|
2017-06-21 16:43:56 +00:00
|
|
|
}
|