github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/commands/command_migrate_import.go (about) 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 != string(os.PathSeparator) { 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", include)) 127 } 128 129 for _, exclude := range filter.Exclude() { 130 tracked.Add(fmt.Sprintf("%s text -filter -merge -diff", 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 }