2017-06-07 17:47:58 +00:00
|
|
|
package commands
|
|
|
|
|
|
|
|
import (
|
2017-11-16 23:44:20 +00:00
|
|
|
"fmt"
|
2017-06-09 23:37:15 +00:00
|
|
|
"path/filepath"
|
2017-06-09 23:38:59 +00:00
|
|
|
"strings"
|
2017-06-09 23:37:15 +00:00
|
|
|
|
2017-06-09 23:38:16 +00:00
|
|
|
"github.com/git-lfs/git-lfs/errors"
|
2017-06-09 23:37:15 +00:00
|
|
|
"github.com/git-lfs/git-lfs/git"
|
2017-06-09 23:40:42 +00:00
|
|
|
"github.com/git-lfs/git-lfs/git/githistory"
|
2017-06-09 23:37:15 +00:00
|
|
|
"github.com/git-lfs/git-lfs/git/odb"
|
2017-11-22 22:07:24 +00:00
|
|
|
"github.com/git-lfs/git-lfs/tasklog"
|
2017-06-07 17:47:58 +00:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
2017-06-09 23:35:11 +00:00
|
|
|
var (
|
|
|
|
// migrateIncludeRefs is a set of Git references to explicitly include
|
|
|
|
// in the migration.
|
|
|
|
migrateIncludeRefs []string
|
|
|
|
// migrateExcludeRefs is a set of Git references to explicitly exclude
|
|
|
|
// in the migration.
|
|
|
|
migrateExcludeRefs []string
|
2017-09-07 17:47:17 +00:00
|
|
|
|
2017-11-20 17:48:50 +00:00
|
|
|
// migrateSkipFetch assumes that the client has the latest copy of
|
|
|
|
// remote references, and thus should not contact the remote for a set
|
|
|
|
// of updated references.
|
|
|
|
migrateSkipFetch bool
|
2017-11-17 16:45:30 +00:00
|
|
|
|
2017-09-07 17:47:17 +00:00
|
|
|
// migrateEverything indicates the presence of the --everything flag,
|
|
|
|
// and instructs 'git lfs migrate' to migrate all local references.
|
|
|
|
migrateEverything bool
|
2017-09-22 18:43:04 +00:00
|
|
|
|
|
|
|
// migrateVerbose enables verbose logging
|
|
|
|
migrateVerbose bool
|
2017-06-09 23:35:11 +00:00
|
|
|
)
|
2017-06-07 17:47:58 +00:00
|
|
|
|
2017-06-15 19:52:55 +00:00
|
|
|
// migrate takes the given command and arguments, *odb.ObjectDatabase, as well
|
|
|
|
// as a BlobRewriteFn to apply, and performs a migration.
|
2017-11-22 22:07:24 +00:00
|
|
|
func migrate(args []string, r *githistory.Rewriter, l *tasklog.Logger, opts *githistory.RewriteOptions) {
|
2017-06-09 23:41:41 +00:00
|
|
|
requireInRepo()
|
|
|
|
|
2017-08-29 21:06:35 +00:00
|
|
|
opts, err := rewriteOptions(args, opts, l)
|
2017-06-09 23:41:41 +00:00
|
|
|
if err != nil {
|
|
|
|
ExitWithError(err)
|
|
|
|
}
|
|
|
|
|
2017-06-21 16:42:36 +00:00
|
|
|
_, err = r.Rewrite(opts)
|
2017-06-09 23:41:41 +00:00
|
|
|
if err != nil {
|
|
|
|
ExitWithError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-09 23:37:15 +00:00
|
|
|
// getObjectDatabase creates a *git.ObjectDatabase from the filesystem pointed
|
|
|
|
// at the .git directory of the currently checked-out repository.
|
|
|
|
func getObjectDatabase() (*odb.ObjectDatabase, error) {
|
|
|
|
dir, err := git.GitDir()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "cannot open root")
|
|
|
|
}
|
2017-10-25 00:59:36 +00:00
|
|
|
return odb.FromFilesystem(filepath.Join(dir, "objects"), cfg.TempDir())
|
2017-06-09 23:37:15 +00:00
|
|
|
}
|
|
|
|
|
2017-06-09 23:40:42 +00:00
|
|
|
// rewriteOptions returns *githistory.RewriteOptions able to be passed to a
|
|
|
|
// *githistory.Rewriter that reflect the current arguments and flags passed to
|
2017-06-21 16:54:55 +00:00
|
|
|
// an invocation of git-lfs-migrate(1).
|
2017-06-09 23:40:42 +00:00
|
|
|
//
|
2017-06-21 16:54:55 +00:00
|
|
|
// It is merged with the given "opts". In other words, an identical "opts" is
|
|
|
|
// returned, where the Include and Exclude fields have been filled based on the
|
|
|
|
// following rules:
|
2017-06-09 23:40:42 +00:00
|
|
|
//
|
|
|
|
// The included and excluded references are determined based on the output of
|
|
|
|
// includeExcludeRefs (see below for documentation and detail).
|
|
|
|
//
|
|
|
|
// If any of the above could not be determined without error, that error will be
|
|
|
|
// returned immediately.
|
2017-11-22 22:07:24 +00:00
|
|
|
func rewriteOptions(args []string, opts *githistory.RewriteOptions, l *tasklog.Logger) (*githistory.RewriteOptions, error) {
|
2017-08-29 21:06:35 +00:00
|
|
|
include, exclude, err := includeExcludeRefs(l, args)
|
2017-06-09 23:40:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &githistory.RewriteOptions{
|
|
|
|
Include: include,
|
|
|
|
Exclude: exclude,
|
|
|
|
|
2017-06-22 00:26:24 +00:00
|
|
|
UpdateRefs: opts.UpdateRefs,
|
2017-09-22 18:43:04 +00:00
|
|
|
Verbose: opts.Verbose,
|
2017-06-22 00:26:24 +00:00
|
|
|
|
2017-06-21 16:54:55 +00:00
|
|
|
BlobFn: opts.BlobFn,
|
|
|
|
TreeCallbackFn: opts.TreeCallbackFn,
|
2017-06-09 23:40:42 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-06-09 23:40:08 +00:00
|
|
|
// includeExcludeRefs returns fully-qualified sets of references to include, and
|
|
|
|
// exclude, or an error if those could not be determined.
|
|
|
|
//
|
|
|
|
// They are determined based on the following rules:
|
|
|
|
//
|
|
|
|
// - Include all local refs/heads/<branch> references for each branch
|
|
|
|
// specified as an argument.
|
|
|
|
// - Include the currently checked out branch if no branches are given as
|
|
|
|
// arguments and the --include-ref= or --exclude-ref= flag(s) aren't given.
|
|
|
|
// - Include all references given in --include-ref=<ref>.
|
|
|
|
// - Exclude all references given in --exclude-ref=<ref>.
|
2017-11-22 22:07:24 +00:00
|
|
|
func includeExcludeRefs(l *tasklog.Logger, args []string) (include, exclude []string, err error) {
|
2017-06-09 23:40:08 +00:00
|
|
|
hardcore := len(migrateIncludeRefs) > 0 || len(migrateExcludeRefs) > 0
|
|
|
|
|
2017-09-07 17:47:17 +00:00
|
|
|
if len(args) == 0 && !hardcore && !migrateEverything {
|
2017-06-09 23:40:08 +00:00
|
|
|
// If no branches were given explicitly AND neither
|
|
|
|
// --include-ref or --exclude-ref flags were given, then add the
|
|
|
|
// currently checked out reference.
|
|
|
|
current, err := currentRefToMigrate()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
args = append(args, current.Name)
|
|
|
|
}
|
|
|
|
|
2017-09-07 17:47:17 +00:00
|
|
|
if migrateEverything && len(args) > 0 {
|
|
|
|
return nil, nil, errors.New("fatal: cannot use --everything with explicit reference arguments")
|
|
|
|
}
|
|
|
|
|
2017-06-09 23:40:08 +00:00
|
|
|
for _, name := range args {
|
|
|
|
// Then, loop through each branch given, resolve that reference,
|
|
|
|
// and include it.
|
|
|
|
ref, err := git.ResolveRef(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2017-11-17 15:42:51 +00:00
|
|
|
include = append(include, ref.Refspec())
|
2017-06-09 23:40:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if hardcore {
|
2017-09-07 17:47:17 +00:00
|
|
|
if migrateEverything {
|
|
|
|
return nil, nil, errors.New("fatal: cannot use --everything with --include-ref or --exclude-ref")
|
|
|
|
}
|
|
|
|
|
2017-06-09 23:40:08 +00:00
|
|
|
// If either --include-ref=<ref> or --exclude-ref=<ref> were
|
|
|
|
// given, append those to the include and excluded reference
|
|
|
|
// set, respectively.
|
|
|
|
include = append(include, migrateIncludeRefs...)
|
|
|
|
exclude = append(exclude, migrateExcludeRefs...)
|
2017-09-07 17:47:17 +00:00
|
|
|
} else if migrateEverything {
|
|
|
|
localRefs, err := git.LocalRefs()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ref := range localRefs {
|
2017-11-17 15:42:51 +00:00
|
|
|
include = append(include, ref.Refspec())
|
2017-09-07 17:47:17 +00:00
|
|
|
}
|
2017-06-09 23:40:08 +00:00
|
|
|
} else {
|
|
|
|
// Otherwise, if neither --include-ref=<ref> or
|
|
|
|
// --exclude-ref=<ref> were given, include no additional
|
|
|
|
// references, and exclude all remote references that are remote
|
|
|
|
// branches or remote tags.
|
2017-08-29 21:06:35 +00:00
|
|
|
remoteRefs, err := getRemoteRefs(l)
|
2017-06-09 23:40:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2017-11-16 23:44:20 +00:00
|
|
|
for _, rr := range remoteRefs {
|
2017-11-17 15:42:51 +00:00
|
|
|
exclude = append(exclude, rr.Refspec())
|
2017-11-16 23:44:20 +00:00
|
|
|
}
|
2017-06-09 23:40:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return include, exclude, nil
|
|
|
|
}
|
|
|
|
|
2017-06-09 23:39:34 +00:00
|
|
|
// getRemoteRefs returns a fully qualified set of references belonging to all
|
|
|
|
// remotes known by the currently checked-out repository, or an error if those
|
|
|
|
// references could not be determined.
|
2017-11-22 22:07:24 +00:00
|
|
|
func getRemoteRefs(l *tasklog.Logger) ([]*git.Ref, error) {
|
2017-11-16 23:44:20 +00:00
|
|
|
var refs []*git.Ref
|
2017-06-09 23:39:34 +00:00
|
|
|
|
|
|
|
remotes, err := git.RemoteList()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-11-20 17:48:50 +00:00
|
|
|
if !migrateSkipFetch {
|
2017-11-17 16:45:30 +00:00
|
|
|
w := l.Waiter("migrate: Fetching remote refs")
|
|
|
|
if err := git.Fetch(remotes...); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
w.Complete()
|
2017-08-29 21:06:20 +00:00
|
|
|
}
|
|
|
|
|
2017-06-09 23:39:34 +00:00
|
|
|
for _, remote := range remotes {
|
2017-11-17 16:45:30 +00:00
|
|
|
var refsForRemote []*git.Ref
|
2017-11-20 17:48:50 +00:00
|
|
|
if migrateSkipFetch {
|
2017-11-17 16:45:30 +00:00
|
|
|
refsForRemote, err = git.CachedRemoteRefs(remote)
|
|
|
|
} else {
|
|
|
|
refsForRemote, err = git.RemoteRefs(remote)
|
|
|
|
}
|
|
|
|
|
2017-06-09 23:39:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-11-16 23:44:20 +00:00
|
|
|
for _, rr := range refsForRemote {
|
|
|
|
// HACK(@ttaylorr): add remote name to fully-qualify
|
|
|
|
// references:
|
|
|
|
rr.Name = fmt.Sprintf("%s/%s", remote, rr.Name)
|
|
|
|
|
|
|
|
refs = append(refs, rr)
|
2017-06-09 23:39:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return refs, nil
|
|
|
|
}
|
|
|
|
|
2017-06-09 23:38:59 +00:00
|
|
|
// formatRefName returns the fully-qualified name for the given Git reference
|
|
|
|
// "ref".
|
|
|
|
func formatRefName(ref *git.Ref, remote string) string {
|
|
|
|
var name []string
|
|
|
|
|
|
|
|
switch ref.Type {
|
|
|
|
case git.RefTypeRemoteBranch:
|
|
|
|
name = []string{"refs", "remotes", remote, ref.Name}
|
|
|
|
case git.RefTypeRemoteTag:
|
|
|
|
name = []string{"refs", "tags", ref.Name}
|
|
|
|
default:
|
|
|
|
return ref.Name
|
|
|
|
}
|
|
|
|
return strings.Join(name, "/")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-06-09 23:38:16 +00:00
|
|
|
// currentRefToMigrate returns the fully-qualified name of the currently
|
|
|
|
// checked-out reference, or an error if the reference's type was not a local
|
|
|
|
// branch.
|
|
|
|
func currentRefToMigrate() (*git.Ref, error) {
|
|
|
|
current, err := git.CurrentRef()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if current.Type == git.RefTypeOther ||
|
|
|
|
current.Type == git.RefTypeRemoteBranch ||
|
|
|
|
current.Type == git.RefTypeRemoteTag {
|
|
|
|
|
|
|
|
return nil, errors.Errorf("fatal: cannot migrate non-local ref: %s", current.Name)
|
|
|
|
}
|
|
|
|
return current, nil
|
|
|
|
}
|
|
|
|
|
2017-06-09 23:41:15 +00:00
|
|
|
// getHistoryRewriter returns a history rewriter that includes the filepath
|
|
|
|
// filter given by the --include and --exclude arguments.
|
2017-11-22 22:07:24 +00:00
|
|
|
func getHistoryRewriter(cmd *cobra.Command, db *odb.ObjectDatabase, l *tasklog.Logger) *githistory.Rewriter {
|
2017-06-09 23:41:15 +00:00
|
|
|
include, exclude := getIncludeExcludeArgs(cmd)
|
|
|
|
filter := buildFilepathFilter(cfg, include, exclude)
|
|
|
|
|
2017-06-14 22:10:42 +00:00
|
|
|
return githistory.NewRewriter(db,
|
2017-08-29 21:05:11 +00:00
|
|
|
githistory.WithFilter(filter), githistory.WithLogger(l))
|
2017-06-09 23:41:15 +00:00
|
|
|
}
|
|
|
|
|
2017-06-07 17:47:58 +00:00
|
|
|
func init() {
|
2017-06-12 21:22:49 +00:00
|
|
|
info := NewCommand("info", migrateInfoCommand)
|
|
|
|
info.Flags().IntVar(&migrateInfoTopN, "top", 5, "--top=<n>")
|
2017-07-31 17:47:13 +00:00
|
|
|
info.Flags().StringVar(&migrateInfoAboveFmt, "above", "", "--above=<n>")
|
2017-06-15 21:08:15 +00:00
|
|
|
info.Flags().StringVar(&migrateInfoUnitFmt, "unit", "", "--unit=<unit>")
|
2017-06-12 21:22:49 +00:00
|
|
|
|
2017-06-21 16:43:56 +00:00
|
|
|
importCmd := NewCommand("import", migrateImportCommand)
|
2017-09-22 18:43:04 +00:00
|
|
|
importCmd.Flags().BoolVar(&migrateVerbose, "verbose", false, "Verbose logging")
|
2017-06-21 16:43:56 +00:00
|
|
|
|
2017-06-09 23:35:11 +00:00
|
|
|
RegisterCommand("migrate", nil, func(cmd *cobra.Command) {
|
2017-09-08 17:57:52 +00:00
|
|
|
cmd.PersistentFlags().StringVarP(&includeArg, "include", "I", "", "Include a list of paths")
|
|
|
|
cmd.PersistentFlags().StringVarP(&excludeArg, "exclude", "X", "", "Exclude a list of paths")
|
|
|
|
|
|
|
|
cmd.PersistentFlags().StringSliceVar(&migrateIncludeRefs, "include-ref", nil, "An explicit list of refs to include")
|
|
|
|
cmd.PersistentFlags().StringSliceVar(&migrateExcludeRefs, "exclude-ref", nil, "An explicit list of refs to exclude")
|
|
|
|
cmd.PersistentFlags().BoolVar(&migrateEverything, "everything", false, "Migrate all local references")
|
2017-11-20 17:48:50 +00:00
|
|
|
cmd.PersistentFlags().BoolVar(&migrateSkipFetch, "skip-fetch", false, "Assume up-to-date remote references.")
|
2017-06-09 23:35:11 +00:00
|
|
|
|
2017-09-11 14:52:50 +00:00
|
|
|
cmd.AddCommand(importCmd, info)
|
2017-06-09 23:35:11 +00:00
|
|
|
})
|
2017-06-07 17:47:58 +00:00
|
|
|
}
|