606ba4c774bbc5003bfd05ab2c13820c6efaee96
[scm/test.git] / commands / command_migrate_import.go
1 package commands
2
3 import (
4         "bufio"
5         "bytes"
6         "encoding/hex"
7         "fmt"
8         "os"
9         "path/filepath"
10         "strings"
11
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"
20 )
21
22 func migrateImportCommand(cmd *cobra.Command, args []string) {
23         l := tasklog.NewLogger(os.Stderr)
24         defer l.Close()
25
26         db, err := getObjectDatabase()
27         if err != nil {
28                 ExitWithError(err)
29         }
30         defer db.Close()
31
32         rewriter := getHistoryRewriter(cmd, db, l)
33
34         tracked := trackedFromFilter(rewriter.Filter())
35         exts := tools.NewOrderedSet()
36         gitfilter := lfs.NewGitFilter(cfg)
37
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" {
42                                 return b, nil
43                         }
44
45                         var buf bytes.Buffer
46
47                         if _, err := clean(gitfilter, &buf, b.Contents, path, b.Size); err != nil {
48                                 return nil, err
49                         }
50
51                         if ext := filepath.Ext(path); len(ext) > 0 {
52                                 exts.Add(fmt.Sprintf("*%s filter=lfs diff=lfs merge=lfs -text", ext))
53                         }
54
55                         return &odb.Blob{
56                                 Contents: &buf, Size: int64(buf.Len()),
57                         }, nil
58                 },
59
60                 TreeCallbackFn: func(path string, t *odb.Tree) (*odb.Tree, error) {
61                         if path != "/" {
62                                 // Ignore non-root trees.
63                                 return t, nil
64                         }
65
66                         ours := tracked
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.
72                                 ours = exts
73                         }
74
75                         theirs, err := trackedFromAttrs(db, t)
76                         if err != nil {
77                                 return nil, err
78                         }
79
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.
83                         //
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
88                         // preserved.
89                         blob, err := trackedToBlob(db, theirs.Clone().Union(ours))
90                         if err != nil {
91                                 return nil, err
92                         }
93
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",
98                                 Filemode: 0100644,
99                                 Oid:      blob,
100                         }), nil
101                 },
102
103                 UpdateRefs: true,
104         })
105
106         // Only perform `git-checkout(1) -f` if the repository is
107         // non-bare.
108         if bare, _ := git.IsBare(); !bare {
109                 t := l.Waiter("migrate: checkout")
110                 err := git.Checkout("", nil, true)
111                 t.Complete()
112
113                 if err != nil {
114                         ExitWithError(err)
115                 }
116         }
117 }
118
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()
124
125         for _, include := range filter.Include() {
126                 tracked.Add(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -text", escapeAttrPattern(include)))
127         }
128
129         for _, exclude := range filter.Exclude() {
130                 tracked.Add(fmt.Sprintf("%s text -filter -merge -diff", escapeAttrPattern(exclude)))
131         }
132
133         return tracked
134 }
135
136 var (
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)
140 )
141
142 // trackedFromAttrs returns an ordered line-delimited set of the contents of a
143 // .gitattributes blob in a given tree "t".
144 //
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) {
148         var oid []byte
149
150         for _, e := range t.Entries {
151                 if strings.ToLower(e.Name) == ".gitattributes" && e.Type() == odb.BlobObjectType {
152                         oid = e.Oid
153                         break
154                 }
155         }
156
157         if oid == nil {
158                 // TODO(@ttaylorr): make (*tools.OrderedSet)(nil) a valid
159                 // receiver for non-mutative methods.
160                 return tools.NewOrderedSet(), nil
161         }
162
163         sha1 := hex.EncodeToString(oid)
164
165         if s, ok := attrsCache[sha1]; ok {
166                 return s, nil
167         }
168
169         blob, err := db.Blob(oid)
170         if err != nil {
171                 return nil, err
172         }
173
174         attrs := tools.NewOrderedSet()
175
176         scanner := bufio.NewScanner(blob.Contents)
177         for scanner.Scan() {
178                 attrs.Add(scanner.Text())
179         }
180
181         if err := scanner.Err(); err != nil {
182                 return nil, err
183         }
184
185         attrsCache[sha1] = attrs
186
187         return attrsCache[sha1], nil
188 }
189
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
194
195         for pattern := range patterns.Iter() {
196                 fmt.Fprintf(&attrs, "%s\n", pattern)
197         }
198
199         return db.WriteBlob(&odb.Blob{
200                 Contents: &attrs,
201                 Size:     int64(attrs.Len()),
202         })
203 }