12 "github.com/git-lfs/git-lfs/filepathfilter"
13 "github.com/git-lfs/git-lfs/git"
14 "github.com/git-lfs/git-lfs/git/githistory"
15 "github.com/git-lfs/git-lfs/git/odb"
16 "github.com/git-lfs/git-lfs/lfs"
17 "github.com/git-lfs/git-lfs/tasklog"
18 "github.com/git-lfs/git-lfs/tools"
19 "github.com/spf13/cobra"
22 func migrateImportCommand(cmd *cobra.Command, args []string) {
23 l := tasklog.NewLogger(os.Stderr)
26 db, err := getObjectDatabase()
32 rewriter := getHistoryRewriter(cmd, db, l)
34 tracked := trackedFromFilter(rewriter.Filter())
35 exts := tools.NewOrderedSet()
36 gitfilter := lfs.NewGitFilter(cfg)
38 migrate(args, rewriter, l, &githistory.RewriteOptions{
39 Verbose: migrateVerbose,
40 BlobFn: func(path string, b *odb.Blob) (*odb.Blob, error) {
41 if filepath.Base(path) == ".gitattributes" {
47 if _, err := clean(gitfilter, &buf, b.Contents, path, b.Size); err != nil {
51 if ext := filepath.Ext(path); len(ext) > 0 {
52 exts.Add(fmt.Sprintf("*%s filter=lfs diff=lfs merge=lfs -text", ext))
56 Contents: &buf, Size: int64(buf.Len()),
60 TreeCallbackFn: func(path string, t *odb.Tree) (*odb.Tree, error) {
62 // Ignore non-root trees.
67 if ours.Cardinality() == 0 {
68 // If there were no explicitly tracked
69 // --include, --exclude filters, assume that the
70 // include set is the wildcard filepath
71 // extensions of files tracked.
75 theirs, err := trackedFromAttrs(db, t)
80 // Create a blob of the attributes that are optionally
81 // present in the "t" tree's .gitattributes blob, and
82 // union in the patterns that we've tracked.
84 // Perform this Union() operation each time we visit a
85 // root tree such that if the underlying .gitattributes
86 // is present and has a diff between commits in the
87 // range of commits to migrate, those changes are
89 blob, err := trackedToBlob(db, theirs.Clone().Union(ours))
94 // Finally, return a copy of the tree "t" that has the
95 // new .gitattributes file included/replaced.
96 return t.Merge(&odb.TreeEntry{
97 Name: ".gitattributes",
106 // Only perform `git-checkout(1) -f` if the repository is
108 if bare, _ := git.IsBare(); !bare {
109 t := l.Waiter("migrate: checkout")
110 err := git.Checkout("", nil, true)
119 // trackedFromFilter returns an ordered set of strings where each entry is a
120 // line in the .gitattributes file. It adds/removes the fiter/diff/merge=lfs
121 // attributes based on patterns included/excldued in the given filter.
122 func trackedFromFilter(filter *filepathfilter.Filter) *tools.OrderedSet {
123 tracked := tools.NewOrderedSet()
125 for _, include := range filter.Include() {
126 tracked.Add(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -text", escapeAttrPattern(include)))
129 for _, exclude := range filter.Exclude() {
130 tracked.Add(fmt.Sprintf("%s text -filter -merge -diff", escapeAttrPattern(exclude)))
137 // attrsCache maintains a cache from the hex-encoded SHA1 of a
138 // .gitattributes blob to the set of patterns parsed from that blob.
139 attrsCache = make(map[string]*tools.OrderedSet)
142 // trackedFromAttrs returns an ordered line-delimited set of the contents of a
143 // .gitattributes blob in a given tree "t".
145 // It returns an empty set if no attributes file could be found, or an error if
146 // it could not otherwise be opened.
147 func trackedFromAttrs(db *odb.ObjectDatabase, t *odb.Tree) (*tools.OrderedSet, error) {
150 for _, e := range t.Entries {
151 if strings.ToLower(e.Name) == ".gitattributes" && e.Type() == odb.BlobObjectType {
158 // TODO(@ttaylorr): make (*tools.OrderedSet)(nil) a valid
159 // receiver for non-mutative methods.
160 return tools.NewOrderedSet(), nil
163 sha1 := hex.EncodeToString(oid)
165 if s, ok := attrsCache[sha1]; ok {
169 blob, err := db.Blob(oid)
174 attrs := tools.NewOrderedSet()
176 scanner := bufio.NewScanner(blob.Contents)
178 attrs.Add(scanner.Text())
181 if err := scanner.Err(); err != nil {
185 attrsCache[sha1] = attrs
187 return attrsCache[sha1], nil
190 // trackedToBlob writes and returns the OID of a .gitattributes blob based on
191 // the patterns given in the ordered set of patterns, "patterns".
192 func trackedToBlob(db *odb.ObjectDatabase, patterns *tools.OrderedSet) ([]byte, error) {
193 var attrs bytes.Buffer
195 for pattern := range patterns.Iter() {
196 fmt.Fprintf(&attrs, "%s\n", pattern)
199 return db.WriteBlob(&odb.Blob{
201 Size: int64(attrs.Len()),