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  }