github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/commands/command_migrate.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "strings" 7 8 "github.com/git-lfs/git-lfs/errors" 9 "github.com/git-lfs/git-lfs/git" 10 "github.com/git-lfs/git-lfs/git/githistory" 11 "github.com/git-lfs/git-lfs/git/odb" 12 "github.com/git-lfs/git-lfs/tasklog" 13 "github.com/spf13/cobra" 14 ) 15 16 var ( 17 // migrateIncludeRefs is a set of Git references to explicitly include 18 // in the migration. 19 migrateIncludeRefs []string 20 // migrateExcludeRefs is a set of Git references to explicitly exclude 21 // in the migration. 22 migrateExcludeRefs []string 23 24 // migrateSkipFetch assumes that the client has the latest copy of 25 // remote references, and thus should not contact the remote for a set 26 // of updated references. 27 migrateSkipFetch bool 28 29 // migrateEverything indicates the presence of the --everything flag, 30 // and instructs 'git lfs migrate' to migrate all local references. 31 migrateEverything bool 32 33 // migrateVerbose enables verbose logging 34 migrateVerbose bool 35 ) 36 37 // migrate takes the given command and arguments, *odb.ObjectDatabase, as well 38 // as a BlobRewriteFn to apply, and performs a migration. 39 func migrate(args []string, r *githistory.Rewriter, l *tasklog.Logger, opts *githistory.RewriteOptions) { 40 requireInRepo() 41 42 opts, err := rewriteOptions(args, opts, l) 43 if err != nil { 44 ExitWithError(err) 45 } 46 47 _, err = r.Rewrite(opts) 48 if err != nil { 49 ExitWithError(err) 50 } 51 } 52 53 // getObjectDatabase creates a *git.ObjectDatabase from the filesystem pointed 54 // at the .git directory of the currently checked-out repository. 55 func getObjectDatabase() (*odb.ObjectDatabase, error) { 56 dir, err := git.GitDir() 57 if err != nil { 58 return nil, errors.Wrap(err, "cannot open root") 59 } 60 return odb.FromFilesystem(filepath.Join(dir, "objects"), cfg.TempDir()) 61 } 62 63 // rewriteOptions returns *githistory.RewriteOptions able to be passed to a 64 // *githistory.Rewriter that reflect the current arguments and flags passed to 65 // an invocation of git-lfs-migrate(1). 66 // 67 // It is merged with the given "opts". In other words, an identical "opts" is 68 // returned, where the Include and Exclude fields have been filled based on the 69 // following rules: 70 // 71 // The included and excluded references are determined based on the output of 72 // includeExcludeRefs (see below for documentation and detail). 73 // 74 // If any of the above could not be determined without error, that error will be 75 // returned immediately. 76 func rewriteOptions(args []string, opts *githistory.RewriteOptions, l *tasklog.Logger) (*githistory.RewriteOptions, error) { 77 include, exclude, err := includeExcludeRefs(l, args) 78 if err != nil { 79 return nil, err 80 } 81 82 return &githistory.RewriteOptions{ 83 Include: include, 84 Exclude: exclude, 85 86 UpdateRefs: opts.UpdateRefs, 87 Verbose: opts.Verbose, 88 89 BlobFn: opts.BlobFn, 90 TreeCallbackFn: opts.TreeCallbackFn, 91 }, nil 92 } 93 94 // includeExcludeRefs returns fully-qualified sets of references to include, and 95 // exclude, or an error if those could not be determined. 96 // 97 // They are determined based on the following rules: 98 // 99 // - Include all local refs/heads/<branch> references for each branch 100 // specified as an argument. 101 // - Include the currently checked out branch if no branches are given as 102 // arguments and the --include-ref= or --exclude-ref= flag(s) aren't given. 103 // - Include all references given in --include-ref=<ref>. 104 // - Exclude all references given in --exclude-ref=<ref>. 105 func includeExcludeRefs(l *tasklog.Logger, args []string) (include, exclude []string, err error) { 106 hardcore := len(migrateIncludeRefs) > 0 || len(migrateExcludeRefs) > 0 107 108 if len(args) == 0 && !hardcore && !migrateEverything { 109 // If no branches were given explicitly AND neither 110 // --include-ref or --exclude-ref flags were given, then add the 111 // currently checked out reference. 112 current, err := currentRefToMigrate() 113 if err != nil { 114 return nil, nil, err 115 } 116 args = append(args, current.Name) 117 } 118 119 if migrateEverything && len(args) > 0 { 120 return nil, nil, errors.New("fatal: cannot use --everything with explicit reference arguments") 121 } 122 123 for _, name := range args { 124 var excluded bool 125 if strings.HasPrefix("^", name) { 126 name = name[1:] 127 excluded = true 128 } 129 130 // Then, loop through each branch given, resolve that reference, 131 // and include it. 132 ref, err := git.ResolveRef(name) 133 if err != nil { 134 return nil, nil, err 135 } 136 137 if excluded { 138 exclude = append(exclude, ref.Refspec()) 139 } else { 140 include = append(include, ref.Refspec()) 141 } 142 } 143 144 if hardcore { 145 if migrateEverything { 146 return nil, nil, errors.New("fatal: cannot use --everything with --include-ref or --exclude-ref") 147 } 148 149 // If either --include-ref=<ref> or --exclude-ref=<ref> were 150 // given, append those to the include and excluded reference 151 // set, respectively. 152 include = append(include, migrateIncludeRefs...) 153 exclude = append(exclude, migrateExcludeRefs...) 154 } else if migrateEverything { 155 localRefs, err := git.LocalRefs() 156 if err != nil { 157 return nil, nil, err 158 } 159 160 for _, ref := range localRefs { 161 include = append(include, ref.Refspec()) 162 } 163 } else { 164 bare, err := git.IsBare() 165 if err != nil { 166 return nil, nil, errors.Wrap(err, "fatal: unable to determine bareness") 167 } 168 169 if !bare { 170 // Otherwise, if neither --include-ref=<ref> or 171 // --exclude-ref=<ref> were given, include no additional 172 // references, and exclude all remote references that 173 // are remote branches or remote tags. 174 remoteRefs, err := getRemoteRefs(l) 175 if err != nil { 176 return nil, nil, err 177 } 178 179 for _, rr := range remoteRefs { 180 exclude = append(exclude, rr.Refspec()) 181 } 182 } 183 } 184 185 return include, exclude, nil 186 } 187 188 // getRemoteRefs returns a fully qualified set of references belonging to all 189 // remotes known by the currently checked-out repository, or an error if those 190 // references could not be determined. 191 func getRemoteRefs(l *tasklog.Logger) ([]*git.Ref, error) { 192 var refs []*git.Ref 193 194 remotes, err := git.RemoteList() 195 if err != nil { 196 return nil, err 197 } 198 199 if !migrateSkipFetch { 200 w := l.Waiter("migrate: Fetching remote refs") 201 if err := git.Fetch(remotes...); err != nil { 202 return nil, err 203 } 204 w.Complete() 205 } 206 207 for _, remote := range remotes { 208 var refsForRemote []*git.Ref 209 if migrateSkipFetch { 210 refsForRemote, err = git.CachedRemoteRefs(remote) 211 } else { 212 refsForRemote, err = git.RemoteRefs(remote) 213 } 214 215 if err != nil { 216 return nil, err 217 } 218 219 for _, rr := range refsForRemote { 220 // HACK(@ttaylorr): add remote name to fully-qualify 221 // references: 222 rr.Name = fmt.Sprintf("%s/%s", remote, rr.Name) 223 224 refs = append(refs, rr) 225 } 226 } 227 228 return refs, nil 229 } 230 231 // formatRefName returns the fully-qualified name for the given Git reference 232 // "ref". 233 func formatRefName(ref *git.Ref, remote string) string { 234 var name []string 235 236 switch ref.Type { 237 case git.RefTypeRemoteBranch: 238 name = []string{"refs", "remotes", remote, ref.Name} 239 case git.RefTypeRemoteTag: 240 name = []string{"refs", "tags", ref.Name} 241 default: 242 return ref.Name 243 } 244 return strings.Join(name, "/") 245 246 } 247 248 // currentRefToMigrate returns the fully-qualified name of the currently 249 // checked-out reference, or an error if the reference's type was not a local 250 // branch. 251 func currentRefToMigrate() (*git.Ref, error) { 252 current, err := git.CurrentRef() 253 if err != nil { 254 return nil, err 255 } 256 257 if current.Type == git.RefTypeOther || 258 current.Type == git.RefTypeRemoteBranch || 259 current.Type == git.RefTypeRemoteTag { 260 261 return nil, errors.Errorf("fatal: cannot migrate non-local ref: %s", current.Name) 262 } 263 return current, nil 264 } 265 266 // getHistoryRewriter returns a history rewriter that includes the filepath 267 // filter given by the --include and --exclude arguments. 268 func getHistoryRewriter(cmd *cobra.Command, db *odb.ObjectDatabase, l *tasklog.Logger) *githistory.Rewriter { 269 include, exclude := getIncludeExcludeArgs(cmd) 270 filter := buildFilepathFilter(cfg, include, exclude) 271 272 return githistory.NewRewriter(db, 273 githistory.WithFilter(filter), githistory.WithLogger(l)) 274 } 275 276 func init() { 277 info := NewCommand("info", migrateInfoCommand) 278 info.Flags().IntVar(&migrateInfoTopN, "top", 5, "--top=<n>") 279 info.Flags().StringVar(&migrateInfoAboveFmt, "above", "", "--above=<n>") 280 info.Flags().StringVar(&migrateInfoUnitFmt, "unit", "", "--unit=<unit>") 281 282 importCmd := NewCommand("import", migrateImportCommand) 283 importCmd.Flags().BoolVar(&migrateVerbose, "verbose", false, "Verbose logging") 284 285 RegisterCommand("migrate", nil, func(cmd *cobra.Command) { 286 cmd.PersistentFlags().StringVarP(&includeArg, "include", "I", "", "Include a list of paths") 287 cmd.PersistentFlags().StringVarP(&excludeArg, "exclude", "X", "", "Exclude a list of paths") 288 289 cmd.PersistentFlags().StringSliceVar(&migrateIncludeRefs, "include-ref", nil, "An explicit list of refs to include") 290 cmd.PersistentFlags().StringSliceVar(&migrateExcludeRefs, "exclude-ref", nil, "An explicit list of refs to exclude") 291 cmd.PersistentFlags().BoolVar(&migrateEverything, "everything", false, "Migrate all local references") 292 cmd.PersistentFlags().BoolVar(&migrateSkipFetch, "skip-fetch", false, "Assume up-to-date remote references.") 293 294 cmd.AddCommand(importCmd, info) 295 }) 296 }