github.com/catandhorse/git-lfs@v2.5.2+incompatible/commands/command_migrate_export.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 8 "github.com/git-lfs/git-lfs/errors" 9 "github.com/git-lfs/git-lfs/filepathfilter" 10 "github.com/git-lfs/git-lfs/git" 11 "github.com/git-lfs/git-lfs/git/githistory" 12 "github.com/git-lfs/git-lfs/lfs" 13 "github.com/git-lfs/git-lfs/tasklog" 14 "github.com/git-lfs/git-lfs/tools" 15 "github.com/git-lfs/gitobj" 16 "github.com/spf13/cobra" 17 ) 18 19 func migrateExportCommand(cmd *cobra.Command, args []string) { 20 ensureWorkingCopyClean(os.Stdin, os.Stderr) 21 22 l := tasklog.NewLogger(os.Stderr) 23 defer l.Close() 24 25 db, err := getObjectDatabase() 26 if err != nil { 27 ExitWithError(err) 28 } 29 defer db.Close() 30 31 rewriter := getHistoryRewriter(cmd, db, l) 32 33 filter := rewriter.Filter() 34 if len(filter.Include()) <= 0 { 35 ExitWithError(errors.Errorf("fatal: one or more files must be specified with --include")) 36 } 37 38 tracked := trackedFromExportFilter(filter) 39 gitfilter := lfs.NewGitFilter(cfg) 40 41 opts := &githistory.RewriteOptions{ 42 Verbose: migrateVerbose, 43 ObjectMapFilePath: objectMapFilePath, 44 BlobFn: func(path string, b *gitobj.Blob) (*gitobj.Blob, error) { 45 if filepath.Base(path) == ".gitattributes" { 46 return b, nil 47 } 48 49 ptr, err := lfs.DecodePointer(b.Contents) 50 if err != nil { 51 if errors.IsNotAPointerError(err) { 52 return b, nil 53 } 54 return nil, err 55 } 56 57 downloadPath, err := gitfilter.ObjectPath(ptr.Oid) 58 if err != nil { 59 return nil, err 60 } 61 62 return gitobj.NewBlobFromFile(downloadPath) 63 }, 64 65 TreeCallbackFn: func(path string, t *gitobj.Tree) (*gitobj.Tree, error) { 66 if path != "/" { 67 // Ignore non-root trees. 68 return t, nil 69 } 70 71 ours := tracked 72 theirs, err := trackedFromAttrs(db, t) 73 if err != nil { 74 return nil, err 75 } 76 77 // Create a blob of the attributes that are optionally 78 // present in the "t" tree's .gitattributes blob, and 79 // union in the patterns that we've tracked. 80 // 81 // Perform this Union() operation each time we visit a 82 // root tree such that if the underlying .gitattributes 83 // is present and has a diff between commits in the 84 // range of commits to migrate, those changes are 85 // preserved. 86 blob, err := trackedToBlob(db, theirs.Clone().Union(ours)) 87 if err != nil { 88 return nil, err 89 } 90 91 // Finally, return a copy of the tree "t" that has the 92 // new .gitattributes file included/replaced. 93 return t.Merge(&gitobj.TreeEntry{ 94 Name: ".gitattributes", 95 Filemode: 0100644, 96 Oid: blob, 97 }), nil 98 }, 99 100 UpdateRefs: true, 101 } 102 103 requireInRepo() 104 105 opts, err = rewriteOptions(args, opts, l) 106 if err != nil { 107 ExitWithError(err) 108 } 109 110 remote := cfg.Remote() 111 if cmd.Flag("remote").Changed { 112 remote = exportRemote 113 } 114 remoteURL := getAPIClient().Endpoints.RemoteEndpoint("download", remote).Url 115 if remoteURL == "" && cmd.Flag("remote").Changed { 116 ExitWithError(errors.Errorf("fatal: invalid remote %s provided", remote)) 117 } 118 119 // If we have a valid remote, pre-download all objects using the Transfer Queue 120 if remoteURL != "" { 121 q := newDownloadQueue(getTransferManifestOperationRemote("Download", remote), remote) 122 gs := lfs.NewGitScanner(func(p *lfs.WrappedPointer, err error) { 123 if err != nil { 124 return 125 } 126 127 if !filter.Allows(p.Name) { 128 return 129 } 130 131 downloadPath, err := gitfilter.ObjectPath(p.Oid) 132 if err != nil { 133 return 134 } 135 136 if _, err := os.Stat(downloadPath); os.IsNotExist(err) { 137 q.Add(p.Name, downloadPath, p.Oid, p.Size) 138 } 139 }) 140 gs.ScanRefs(opts.Include, opts.Exclude, nil) 141 142 q.Wait() 143 144 for _, err := range q.Errors() { 145 if err != nil { 146 ExitWithError(err) 147 } 148 } 149 } 150 151 // Perform the rewrite 152 if _, err := rewriter.Rewrite(opts); err != nil { 153 ExitWithError(err) 154 } 155 156 // Only perform `git-checkout(1) -f` if the repository is non-bare. 157 if bare, _ := git.IsBare(); !bare { 158 t := l.Waiter("migrate: checkout") 159 err := git.Checkout("", nil, true) 160 t.Complete() 161 162 if err != nil { 163 ExitWithError(err) 164 } 165 } 166 167 fetchPruneCfg := lfs.NewFetchPruneConfig(cfg.Git) 168 169 // Set our preservation time-window for objects existing on the remote to 170 // 0. Because the newly rewritten commits have not yet been pushed, some 171 // exported objects can still exist on the remote within the time window 172 // and thus will not be pruned from the cache. 173 fetchPruneCfg.FetchRecentRefsDays = 0 174 175 // Prune our cache 176 prune(fetchPruneCfg, false, false, true) 177 } 178 179 // trackedFromExportFilter returns an ordered set of strings where each entry 180 // is a line we intend to place in the .gitattributes file. It adds/removes the 181 // filter/diff/merge=lfs attributes based on patterns included/excluded in the 182 // given filter. Since `migrate export` removes files from Git LFS, it will 183 // remove attributes for included files, and add attributes for excluded files 184 func trackedFromExportFilter(filter *filepathfilter.Filter) *tools.OrderedSet { 185 tracked := tools.NewOrderedSet() 186 187 for _, include := range filter.Include() { 188 tracked.Add(fmt.Sprintf("%s text !filter !merge !diff", escapeAttrPattern(include))) 189 } 190 191 for _, exclude := range filter.Exclude() { 192 tracked.Add(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -text", escapeAttrPattern(exclude))) 193 } 194 195 return tracked 196 }