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  }