github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/readtree.go (about)

     1  package git
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"regexp"
     9  	"sort"
    10  	"strings"
    11  	"time"
    12  )
    13  
    14  // Options that may be passed in the command line to ReadTree.
    15  type ReadTreeOptions struct {
    16  	// Perform a merge
    17  	Merge bool
    18  	// Discard changes that are not in stage 0 while doing a merge.
    19  	Reset bool
    20  
    21  	// Also update the work tree
    22  	Update bool
    23  
    24  	// -i parameter to ReadTree. Not implemented
    25  	IgnoreWorktreeCheck bool
    26  
    27  	// Do not write index to disk.
    28  	DryRun bool
    29  
    30  	// Unused, just for consistency with real git command line.
    31  	Verbose bool
    32  
    33  	// Not implemented
    34  	TrivialMerge, AggressiveMerge bool
    35  
    36  	// Read the named tree into the directory prefix/ under the index
    37  	Prefix string
    38  
    39  	// Used as the name of a .gitignore to look for in each directory
    40  	ExcludePerDirectory string
    41  
    42  	// Name of the index file to use under c.GitDir
    43  	IndexOutput string
    44  
    45  	// Discard all the entries in the index instead of updating it to the
    46  	// named Treeish.
    47  	Empty bool
    48  
    49  	// Disable sparse checkout.
    50  	// Note that it's the caller's responsibility to set this option if
    51  	// core.sparsecheckout is not equal to true.
    52  	NoSparseCheckout bool
    53  }
    54  
    55  // Helper to safely check if path is the same in p1 and p2
    56  func samePath(p1, p2 IndexMap, path IndexPath) bool {
    57  	p1i, p1ok := p1[path]
    58  	p2i, p2ok := p2[path]
    59  
    60  	// It's in one but not the other directly, so it's not
    61  	// the same.
    62  	if p1ok != p2ok {
    63  		return false
    64  	}
    65  
    66  	// Avoid a nil pointer below by explicitly checking if one
    67  	// is missing.
    68  	if p1ok == false {
    69  		return p2ok == false
    70  	}
    71  	if p2ok == false {
    72  		return p1ok == false
    73  	}
    74  
    75  	// It's in both, so we can safely check the sha
    76  	return p1i.Sha1 == p2i.Sha1
    77  
    78  }
    79  
    80  func checkSparseMatches(c *Client, opt ReadTreeOptions, path IndexPath, patterns []IgnorePattern) bool {
    81  	if opt.NoSparseCheckout {
    82  		// If noSparseCheckout is set, just claim everything
    83  		// matches.
    84  		return true
    85  	}
    86  	fp, err := path.FilePath(c)
    87  	if err != nil {
    88  		panic(err)
    89  	}
    90  	f := fp.String()
    91  	matches := false
    92  	for _, pattern := range patterns {
    93  		if pattern.Negates() {
    94  			if pattern.Matches(f, false) {
    95  				matches = !matches
    96  			}
    97  		} else {
    98  			if pattern.Matches(f, false) {
    99  				matches = true
   100  			}
   101  		}
   102  	}
   103  	return matches
   104  }
   105  
   106  func parseSparsePatterns(c *Client, opt *ReadTreeOptions) []IgnorePattern {
   107  	if opt.NoSparseCheckout {
   108  		return nil
   109  	}
   110  	sparsefile := c.GitDir.File("info/sparse-checkout")
   111  	if !sparsefile.Exists() {
   112  		// If the file doesn't exist, pretend the
   113  		// flag to ignore the file was set since the
   114  		// logic is identical.
   115  		opt.NoSparseCheckout = true
   116  		return nil
   117  	}
   118  	sp, err := ParseIgnorePatterns(c, sparsefile, "")
   119  	if err != nil {
   120  		return nil
   121  	}
   122  	return sp
   123  }
   124  
   125  // ReadTreeThreeWay will perform a three-way merge on the trees stage1, stage2, and stage3.
   126  // In a normal merge, stage1 is the common ancestor, stage2 is "our" changes, and
   127  // stage3 is "their" changes. See git-read-tree(1) for details.
   128  //
   129  // If options.DryRun is not false, it will also be written to the Client's index file.
   130  func ReadTreeThreeWay(c *Client, opt ReadTreeOptions, stage1, stage2, stage3 Treeish) (*Index, error) {
   131  	idx, err := c.GitDir.ReadIndex()
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	resetremovals, err := checkReadtreePrereqs(c, opt, idx)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	origMap := idx.GetMap()
   142  
   143  	base, err := GetIndexMap(c, stage1)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	ours, err := GetIndexMap(c, stage2)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	theirs, err := GetIndexMap(c, stage3)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	// Create a slice which contins all objects in base, ours, or theirs
   159  	var allPaths []*IndexEntry
   160  	for path, _ := range base {
   161  		allPaths = append(allPaths, &IndexEntry{PathName: path})
   162  	}
   163  	for path, _ := range ours {
   164  		allPaths = append(allPaths, &IndexEntry{PathName: path})
   165  	}
   166  	for path, _ := range theirs {
   167  		allPaths = append(allPaths, &IndexEntry{PathName: path})
   168  	}
   169  	// Sort to ensure directories come before files.
   170  	sort.Sort(ByPath(allPaths))
   171  
   172  	// Remove duplicates and exclude files that aren't part of the
   173  	// sparse checkout rules if applicable.
   174  	var allObjects []IndexPath
   175  	for i := range allPaths {
   176  		if i > 0 && allPaths[i].PathName == allPaths[i-1].PathName {
   177  			continue
   178  		}
   179  		allObjects = append(allObjects, allPaths[i].PathName)
   180  	}
   181  	var dirs []IndexPath
   182  
   183  	// Checking for merge conflict with index. If this seems like a confusing mess, it's mostly
   184  	// because it was written to pass the t1000-read-tree-m-3way test case from the official git
   185  	// test suite.
   186  	//
   187  	// The logic can probably be cleaned up.
   188  	for path, orig := range origMap {
   189  		o, ok := ours[path]
   190  		if !ok {
   191  			// If it's been added to the index in the same state as Stage 3, and it's not in
   192  			// stage 1 or 2 it's fine.
   193  			if !base.Contains(path) && !ours.Contains(path) && samePath(origMap, theirs, path) {
   194  				continue
   195  			}
   196  
   197  			return idx, fmt.Errorf("Entry '%v' would be overwritten by a merge. Cannot merge.", path)
   198  		}
   199  
   200  		// Variable names mirror the O/A/B from the test suite, with "c" for contains
   201  		oc := base.Contains(path)
   202  		ac := ours.Contains(path)
   203  		bc := theirs.Contains(path)
   204  
   205  		if oc && ac && bc {
   206  			oa := samePath(base, ours, path)
   207  			ob := samePath(base, theirs, path)
   208  
   209  			// t1000-read-tree-m-3way.sh test 75 "must match A in O && A && B && O!=A && O==B case.
   210  			// (This means we can't error out if the Sha1s dont match.)
   211  			if !oa && ob {
   212  				continue
   213  			}
   214  			if oa && !ob {
   215  				// Relevent cases:
   216  				// Must match and be up-to-date in O && A && B && O==A && O!=B
   217  				// May  match B in                 O && A && B && O==A && O!=B
   218  				b, ok := theirs[path]
   219  				if ok && b.Sha1 == orig.Sha1 {
   220  					continue
   221  				} else if !path.IsClean(c, o.Sha1) {
   222  					return idx, fmt.Errorf("Entry '%v' would be overwritten by a merge. Cannot merge.", path)
   223  				}
   224  			}
   225  		}
   226  		// Must match and be up-to-date in !O && A && B && A != B case test from AND
   227  		// Must match and be up-to-date in O && A && B && A != B case test from
   228  		// t1000-read-tree-m-3way.sh in official git
   229  		if ac && bc && !samePath(ours, theirs, path) {
   230  			if !path.IsClean(c, o.Sha1) {
   231  				return idx, fmt.Errorf("Entry '%v' would be overwritten by a merge. Cannot merge.", path)
   232  			}
   233  		}
   234  
   235  		// Must match and be up-to-date in O && A && !B && !B && O != A case AND
   236  		// Must match and be up-to-date in O && A && !B && !B && O == A case from
   237  		// t1000-read-tree-m-3way.sh in official git
   238  		if oc && ac && !bc {
   239  			if !path.IsClean(c, o.Sha1) {
   240  				return idx, fmt.Errorf("Entry '%v' would be overwritten by a merge. Cannot merge.", path)
   241  			}
   242  		}
   243  
   244  		if o.Sha1 != orig.Sha1 {
   245  			return idx, fmt.Errorf("Entry '%v' would be overwritten by a merge. Cannot merge.", path)
   246  		}
   247  	}
   248  	idx = NewIndex()
   249  paths:
   250  	for _, path := range allObjects {
   251  		// Handle directory/file conflicts.
   252  		if base.HasDir(path) || ours.HasDir(path) || theirs.HasDir(path) {
   253  			if !opt.Merge && !opt.Reset {
   254  				// If not merging, the file wins.
   255  				// see http://www.stackoverflow.com/questions/52175720/how-does-git-read-tree-work-without-m-or-reset-option
   256  				continue
   257  			}
   258  			// Keep track of what was a directory so that other
   259  			// other paths know if they had a conflict higher
   260  			// up in the tree.
   261  			dirs = append(dirs, path)
   262  
   263  			// Add the non-directory version fo the appropriate stage
   264  			if p, ok := base[path]; ok {
   265  				idx.AddStage(c, path, p.Mode, p.Sha1, Stage1, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true})
   266  			}
   267  			if p, ok := ours[path]; ok {
   268  				idx.AddStage(c, path, p.Mode, p.Sha1, Stage2, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true})
   269  			}
   270  			if p, ok := theirs[path]; ok {
   271  				idx.AddStage(c, path, p.Mode, p.Sha1, Stage3, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true})
   272  			}
   273  			continue
   274  		}
   275  
   276  		// Handle the subfiles in any directory that had a conflict
   277  		// by just adding them in the appropriate stage, because
   278  		// there's no way for a directory and file to not be in
   279  		// conflict.
   280  		for _, d := range dirs {
   281  			if strings.HasPrefix(string(path), string(d+"/")) {
   282  				if p, ok := base[path]; ok {
   283  					if err := idx.AddStage(c, path, p.Mode, p.Sha1, Stage1, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil {
   284  						return nil, err
   285  					}
   286  				}
   287  				if p, ok := ours[path]; ok {
   288  					if err := idx.AddStage(c, path, p.Mode, p.Sha1, Stage2, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true, Replace: true}); err != nil {
   289  						return nil, err
   290  					}
   291  				}
   292  				if p, ok := theirs[path]; ok {
   293  					if err := idx.AddStage(c, path, p.Mode, p.Sha1, Stage3, p.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil {
   294  						return nil, err
   295  					}
   296  				}
   297  				continue paths
   298  			}
   299  		}
   300  
   301  		// From here on out, we assume everything is a file.
   302  
   303  		// All three trees are the same, don't do anything to the index.
   304  		if samePath(base, ours, path) && samePath(base, theirs, path) {
   305  			if err := idx.AddStage(c, path, ours[path].Mode, ours[path].Sha1, Stage0, ours[path].Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil {
   306  				panic(err)
   307  			}
   308  			continue
   309  		}
   310  
   311  		// If both stage2 and stage3 are the same, the work has been done in
   312  		// both branches, so collapse to stage0 (use our changes)
   313  		if samePath(ours, theirs, path) {
   314  			if ours.Contains(path) {
   315  				if err := idx.AddStage(c, path, ours[path].Mode, ours[path].Sha1, Stage0, ours[path].Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil {
   316  					panic(err)
   317  				}
   318  				continue
   319  			}
   320  		}
   321  
   322  		// If stage1 and stage2 are the same, our branch didn't do anything,
   323  		// but theirs did, so take their changes.
   324  		if samePath(base, ours, path) {
   325  			if theirs.Contains(path) {
   326  				if err := idx.AddStage(c, path, theirs[path].Mode, theirs[path].Sha1, Stage0, theirs[path].Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil {
   327  					panic(err)
   328  				}
   329  				continue
   330  			}
   331  		}
   332  
   333  		// If stage1 and stage3 are the same, we did something
   334  		// but they didn't, so take our changes
   335  		if samePath(base, theirs, path) {
   336  			if ours.Contains(path) {
   337  				o := ours[path]
   338  				if err := idx.AddStage(c, path, o.Mode, o.Sha1, Stage0, o.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true}); err != nil {
   339  					panic(err)
   340  				}
   341  				continue
   342  			}
   343  		}
   344  
   345  		// We couldn't short-circuit out, so add all three stages.
   346  
   347  		// Remove Stage0 if it exists. If it doesn't, then at worst we'll
   348  		// remove a stage that we're about to add back.
   349  		idx.RemoveFile(path)
   350  
   351  		if b, ok := base[path]; ok {
   352  			idx.AddStage(c, path, b.Mode, b.Sha1, Stage1, b.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true})
   353  		}
   354  		if o, ok := ours[path]; ok {
   355  			idx.AddStage(c, path, o.Mode, o.Sha1, Stage2, o.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true})
   356  		}
   357  		if t, ok := theirs[path]; ok {
   358  			idx.AddStage(c, path, t.Mode, t.Sha1, Stage3, t.Fsize, time.Now().UnixNano(), UpdateIndexOptions{Add: true})
   359  		}
   360  	}
   361  
   362  	if err := checkMergeAndUpdate(c, opt, origMap, idx, resetremovals); err != nil {
   363  		return nil, err
   364  	}
   365  
   366  	return idx, readtreeSaveIndex(c, opt, idx)
   367  }
   368  
   369  // ReadTreeFastForward will return a new Index with parent fast-forwarded to
   370  // from parent to dst. Local modifications to the work tree will be preserved.
   371  // If options.DryRun is not false, it will also be written to the Client's index file.
   372  func ReadTreeFastForward(c *Client, opt ReadTreeOptions, parent, dst Treeish) (*Index, error) {
   373  	// This is the table of how fast-forward merges work from git-read-tree(1)
   374  	// I == Index, H == parent, and M == dst in their terminology. (ie. It's a
   375  	// fast-forward from H to M while the index is in state I.)
   376  	//
   377  	//	      I 		  H	   M	    Result
   378  	//	     -------------------------------------------------------
   379  	//	   0  nothing		  nothing  nothing  (does not happen)
   380  	//	   1  nothing		  nothing  exists   use M
   381  	//	   2  nothing		  exists   nothing  remove path from index
   382  	//	   3  nothing		  exists   exists,  use M if "initial checkout",
   383  	//					   H == M   keep index otherwise
   384  	//					   exists,  fail
   385  	//					   H != M
   386  	//
   387  	//	      clean I==H  I==M
   388  	//	     ------------------
   389  	//	   4  yes   N/A   N/A	  nothing  nothing  keep index
   390  	//	   5  no    N/A   N/A	  nothing  nothing  keep index
   391  	//
   392  	//	   6  yes   N/A   yes	  nothing  exists   keep index
   393  	//	   7  no    N/A   yes	  nothing  exists   keep index
   394  	//	   8  yes   N/A   no	  nothing  exists   fail
   395  	//	   9  no    N/A   no	  nothing  exists   fail
   396  	//
   397  	//	   10 yes   yes   N/A	  exists   nothing  remove path from index
   398  	//	   11 no    yes   N/A	  exists   nothing  fail
   399  	//	   12 yes   no	  N/A	  exists   nothing  fail
   400  	//	   13 no    no	  N/A	  exists   nothing  fail
   401  	//
   402  	//	      clean (H==M)
   403  	//	     ------
   404  	//	   14 yes		  exists   exists   keep index
   405  	//	   15 no		  exists   exists   keep index
   406  	//
   407  	//	      clean I==H  I==M (H!=M)
   408  	//	     ------------------
   409  	//	   16 yes   no	  no	  exists   exists   fail
   410  	//	   17 no    no	  no	  exists   exists   fail
   411  	//	   18 yes   no	  yes	  exists   exists   keep index
   412  	//	   19 no    no	  yes	  exists   exists   keep index
   413  	//	   20 yes   yes   no	  exists   exists   use M
   414  	//	   21 no    yes   no	  exists   exists   fail
   415  	idx, err := c.GitDir.ReadIndex()
   416  	if err != nil {
   417  		return nil, err
   418  	}
   419  
   420  	resetremovals, err := checkReadtreePrereqs(c, opt, idx)
   421  	if err != nil {
   422  		return nil, err
   423  	}
   424  
   425  	I := idx.GetMap()
   426  
   427  	H, err := GetIndexMap(c, parent)
   428  	if err != nil {
   429  		return nil, err
   430  	}
   431  
   432  	M, err := GetIndexMap(c, dst)
   433  	if err != nil {
   434  		return nil, err
   435  	}
   436  
   437  	// Start by iterating through the index and handling cases 5-21.
   438  	// (We'll create a new index instead of trying to keep track of state
   439  	// of the existing index while iterating through it.)
   440  	newidx := NewIndex()
   441  
   442  	for pathname, IEntry := range I {
   443  		HEntry, HExists := H[pathname]
   444  		MEntry, MExists := M[pathname]
   445  		if !HExists && !MExists {
   446  			// Case 4-5
   447  			newidx.Objects = append(newidx.Objects, IEntry)
   448  			continue
   449  		} else if !HExists && MExists {
   450  			if IEntry.Sha1 == MEntry.Sha1 {
   451  				// Case 6-7
   452  				newidx.Objects = append(newidx.Objects, IEntry)
   453  				continue
   454  			}
   455  			// Case 8-9
   456  			return nil, fmt.Errorf("error: Entry '%s' would be overwritten by merge. Cannot merge.", pathname)
   457  		} else if HExists && !MExists {
   458  			if pathname.IsClean(c, IEntry.Sha1) && IEntry.Sha1 == HEntry.Sha1 {
   459  				// Case 10. Remove from the index.
   460  				// (Since it's a new index, we just don't add it)
   461  				continue
   462  			}
   463  			// Case 11 or 13 if it's not clean, case 12 if they don't match
   464  			if IEntry.Sha1 != HEntry.Sha1 {
   465  				return nil, fmt.Errorf("error: Entry '%s' would be overwritten by merge. Cannot merge.", pathname)
   466  			}
   467  			return nil, fmt.Errorf("Entry '%v' not uptodate. Cannot merge.", pathname)
   468  		} else {
   469  			if HEntry.Sha1 == MEntry.Sha1 {
   470  				// Case 14-15
   471  				newidx.Objects = append(newidx.Objects, IEntry)
   472  				continue
   473  			}
   474  			// H != M
   475  			if IEntry.Sha1 != HEntry.Sha1 && IEntry.Sha1 != MEntry.Sha1 {
   476  				// Case 16-17
   477  				return nil, fmt.Errorf("error: Entry '%s' would be overwritten by merge. Cannot merge.", pathname)
   478  			} else if IEntry.Sha1 != HEntry.Sha1 && IEntry.Sha1 == MEntry.Sha1 {
   479  				// Case 18-19
   480  				newidx.Objects = append(newidx.Objects, IEntry)
   481  				continue
   482  			} else if IEntry.Sha1 == HEntry.Sha1 && IEntry.Sha1 != MEntry.Sha1 {
   483  				if pathname.IsClean(c, IEntry.Sha1) {
   484  					// Case 20. Use M.
   485  					newidx.Objects = append(newidx.Objects, MEntry)
   486  					continue
   487  				} else {
   488  					return nil, fmt.Errorf("Entry '%v' not uptodate. Cannot merge.", pathname)
   489  				}
   490  			}
   491  		}
   492  	}
   493  
   494  	// Finally, handle the cases where it's in H or M but not I by going
   495  	// through the maps of H and M.
   496  	for pathname, MEntry := range M {
   497  		if _, IExists := I[pathname]; IExists {
   498  			// If it's in I, it was already handled above.
   499  			continue
   500  		}
   501  		HEntry, HExists := H[pathname]
   502  		if !HExists {
   503  			// It's in M but not I or H. Case 1. Use M.
   504  			newidx.Objects = append(newidx.Objects, MEntry)
   505  			continue
   506  		}
   507  		// Otherwise it's in both H and M but not I. Case 3.
   508  		if HEntry.Sha1 != MEntry.Sha1 {
   509  			if !c.GitDir.File("index").Exists() {
   510  				// Case 3 from the git-read-tree(1) is weird, but this
   511  				// is intended to handle it. If there is no index, add
   512  				// the file from M
   513  				newidx.Objects = append(newidx.Objects, MEntry)
   514  			} else {
   515  				return nil, fmt.Errorf("Could not fast-forward (case 3.)")
   516  			}
   517  		} else {
   518  			// It was unmodified between the two trees, but has been
   519  			// removed from the index. Keep the "Deleted" state by
   520  			// not adding it.
   521  			// If there is no index, however, we add it, since it's
   522  			// an initial checkout.
   523  			if !c.GitDir.File("index").Exists() {
   524  				newidx.Objects = append(newidx.Objects, MEntry)
   525  			}
   526  		}
   527  	}
   528  
   529  	// We need to make sure the number of index entries stays is correct,
   530  	// it's going to be an invalid index..
   531  	newidx.NumberIndexEntries = uint32(len(newidx.Objects))
   532  	if err := checkMergeAndUpdate(c, opt, I, newidx, resetremovals); err != nil {
   533  		return nil, err
   534  	}
   535  
   536  	return newidx, readtreeSaveIndex(c, opt, newidx)
   537  }
   538  
   539  // Helper to ensure the DryRun option gets checked no matter what the code path
   540  // for ReadTree.
   541  func readtreeSaveIndex(c *Client, opt ReadTreeOptions, i *Index) error {
   542  	if !opt.DryRun {
   543  		if opt.IndexOutput == "" {
   544  			opt.IndexOutput = "index"
   545  		}
   546  		f, err := c.GitDir.Create(File(opt.IndexOutput))
   547  		if err != nil {
   548  			return err
   549  		}
   550  		defer f.Close()
   551  		return i.WriteIndex(f)
   552  	}
   553  	return nil
   554  }
   555  
   556  // Check if the read-tree can be performed. Returns a list of files that need to be
   557  // removed if --reset is specified.
   558  func checkReadtreePrereqs(c *Client, opt ReadTreeOptions, idx *Index) ([]File, error) {
   559  	if opt.Update && opt.Prefix == "" && !opt.Merge && !opt.Reset {
   560  		return nil, fmt.Errorf("-u is meaningless without -m, --reset or --prefix")
   561  	}
   562  	if (opt.Prefix != "" && (opt.Merge || opt.Reset)) ||
   563  		(opt.Merge && (opt.Prefix != "" || opt.Reset)) ||
   564  		(opt.Reset && (opt.Prefix != "" || opt.Merge)) {
   565  		return nil, fmt.Errorf("Can only specify one of -u, --reset, or --prefix")
   566  	}
   567  	if opt.ExcludePerDirectory != "" && !opt.Update {
   568  		return nil, fmt.Errorf("--exclude-per-directory is meaningless without -u")
   569  	}
   570  	if idx == nil {
   571  		return nil, nil
   572  	}
   573  
   574  	toremove := make([]File, 0)
   575  	for _, entry := range idx.Objects {
   576  		if entry.Stage() != Stage0 {
   577  			if opt.Merge {
   578  				return nil, fmt.Errorf("You need to resolve your current index first")
   579  			}
   580  			if opt.Reset {
   581  				f, err := entry.PathName.FilePath(c)
   582  				if err != nil {
   583  				}
   584  				// Indexes are sorted, so only check if the last file is the
   585  				// same
   586  				if len(toremove) >= 1 && toremove[len(toremove)-1] == f {
   587  					continue
   588  				}
   589  				toremove = append(toremove, f)
   590  			}
   591  		}
   592  	}
   593  	return toremove, nil
   594  
   595  }
   596  
   597  // Reads a tree into the index. If DryRun is not false, it will also be written
   598  // to disk.
   599  func ReadTree(c *Client, opt ReadTreeOptions, tree Treeish) (*Index, error) {
   600  	idx, err := c.GitDir.ReadIndex()
   601  	if err != nil {
   602  		idx = NewIndex()
   603  	}
   604  	origMap := idx.GetMap()
   605  
   606  	resetremovals, err := checkReadtreePrereqs(c, opt, idx)
   607  	if err != nil {
   608  		return nil, err
   609  	}
   610  	// Convert to a new map before doing anything, so that checkMergeAndUpdate
   611  	// can compare the original update after we reset.
   612  	if opt.Empty {
   613  		idx.NumberIndexEntries = 0
   614  		idx.Objects = make([]*IndexEntry, 0)
   615  		if err := checkMergeAndUpdate(c, opt, origMap, idx, resetremovals); err != nil {
   616  			return nil, err
   617  		}
   618  		return idx, readtreeSaveIndex(c, opt, idx)
   619  	}
   620  	newidx := NewIndex()
   621  	if err := newidx.ResetIndex(c, tree); err != nil {
   622  		return nil, err
   623  	}
   624  	for _, entry := range newidx.Objects {
   625  		if opt.Prefix != "" {
   626  			// Add it to the original index with the prefix
   627  			entry.PathName = IndexPath(opt.Prefix) + entry.PathName
   628  			if err := idx.AddStage(c, entry.PathName, entry.Mode, entry.Sha1, Stage0, entry.Fsize, entry.Mtime, UpdateIndexOptions{Add: true}); err != nil {
   629  				return nil, err
   630  			}
   631  		}
   632  		if opt.Merge {
   633  			if oldentry, ok := origMap[entry.PathName]; ok {
   634  				newsha, _, err := HashFile("blob", string(entry.PathName))
   635  				if err != nil && newsha == entry.Sha1 {
   636  					entry.Ctime, entry.Ctimenano = oldentry.Ctime, oldentry.Ctimenano
   637  					entry.Mtime = oldentry.Mtime
   638  				}
   639  			}
   640  		}
   641  	}
   642  
   643  	if opt.Prefix == "" {
   644  		idx = newidx
   645  	}
   646  
   647  	if err := checkMergeAndUpdate(c, opt, origMap, idx, resetremovals); err != nil {
   648  		return nil, err
   649  	}
   650  	return idx, readtreeSaveIndex(c, opt, idx)
   651  }
   652  
   653  // Check if the merge would overwrite any modified files and return an error if so (unless --reset),
   654  // then update the file system.
   655  func checkMergeAndUpdate(c *Client, opt ReadTreeOptions, origidx map[IndexPath]*IndexEntry, newidx *Index, resetremovals []File) error {
   656  	sparsePatterns := parseSparsePatterns(c, &opt)
   657  	if !opt.NoSparseCheckout {
   658  		leavesFile := false
   659  		newSparse := false
   660  		for _, entry := range newidx.Objects {
   661  			if !checkSparseMatches(c, opt, entry.PathName, sparsePatterns) {
   662  				if orig, ok := origidx[entry.PathName]; ok && !orig.SkipWorktree() {
   663  					newSparse = true
   664  				}
   665  				entry.SetSkipWorktree(true)
   666  				if newidx.Version <= 2 {
   667  					newidx.Version = 3
   668  				}
   669  			} else {
   670  				leavesFile = true
   671  			}
   672  		}
   673  		for _, entry := range origidx {
   674  			if checkSparseMatches(c, opt, entry.PathName, sparsePatterns) {
   675  				// This isn't necessarily true, but if it is we don't error out in
   676  				// order to make let make the git test t1011.19 pass.
   677  				//
   678  				// t1011-read-tree-sparse-checkout works in mysterious ways.
   679  				leavesFile = true
   680  			}
   681  		}
   682  		if !leavesFile && newSparse {
   683  			return fmt.Errorf("Sparse checkout would leave no file in work tree")
   684  		}
   685  	}
   686  
   687  	// Check for invalid path names which are disallowed by git
   688  	disallow := func(path, piece string) bool {
   689  		casesensitive := true
   690  		if protectHFS(c) {
   691  			// HFS is case insensitive and ignores zero width
   692  			// non-joiners anywhere in the file path
   693  			casesensitive = false
   694  			path = strings.Replace(path, "\u200c", "", -1)
   695  			path = strings.TrimSpace(path)
   696  		}
   697  		if protectNTFS(c) {
   698  			// git treats "protectNTFS" as "protect the filesystem for
   699  			// windows", which means it also inherits weird dos filename
   700  			// restrictions such as 8.3 length filenames and ~ and
   701  			// no dots at the end of a path because that would just be
   702  			// an empty file extension.
   703  			casesensitive = false
   704  
   705  			re := regexp.MustCompile(`(?i)(^|\\|/)git~[0-9]+($|\\|/)`)
   706  			if re.MatchString(path) {
   707  				// Handle the case where the "." was removed and
   708  				// we're left with nothing, or where the the .git
   709  				// directory is 8.3 encoded.
   710  				return true
   711  			}
   712  			// Handle space or dots at end of a filename and backslashes
   713  			re = regexp.MustCompile(`(?i)(^|\\|/)\.git( |[.])*($|\\|/)`)
   714  			if re.MatchString(path) {
   715  				return true
   716  			}
   717  		}
   718  
   719  		if !casesensitive {
   720  			path = strings.ToLower(path)
   721  		}
   722  
   723  		if path == piece {
   724  			return true
   725  		}
   726  		if strings.HasSuffix(path, "/"+piece) || strings.HasPrefix(path, piece+"/") {
   727  			return true
   728  		}
   729  		if strings.Index(path, "/"+piece+"/") != -1 {
   730  			return true
   731  		}
   732  		return false
   733  	}
   734  	for _, entry := range newidx.Objects {
   735  		path := entry.PathName.String()
   736  
   737  		if disallow(path, ".") || disallow(path, "..") || disallow(path, ".git") {
   738  			return fmt.Errorf("Invalid path %v", path)
   739  		}
   740  	}
   741  	// Keep a list of index entries to be updated by CheckoutIndex.
   742  	files := make([]File, 0, len(newidx.Objects))
   743  
   744  	if opt.Merge || opt.Reset || opt.Update {
   745  		// Verify that merge won't overwrite anything that's been modified locally.
   746  		for _, entry := range newidx.Objects {
   747  			f, err := entry.PathName.FilePath(c)
   748  			if err != nil {
   749  				return err
   750  			}
   751  
   752  			if opt.Update && f.IsDir() {
   753  				untracked, err := LsFiles(c, LsFilesOptions{Others: true, Modified: true}, []File{f})
   754  				if err != nil {
   755  					return err
   756  				}
   757  				if len(untracked) > 0 {
   758  					return fmt.Errorf("error: Updating '%s%s' would lose untracked files in it", c.SuperPrefix, entry.PathName)
   759  				}
   760  			}
   761  			if entry.Stage() != Stage0 {
   762  				// Don't check unmerged entries. One will always
   763  				// conflict, which means that -u won't work
   764  				// if we check them.
   765  				// (We also don't add them to files, so they won't
   766  				// make it to checkoutindex
   767  				continue
   768  			}
   769  			if entry.SkipWorktree() {
   770  				continue
   771  			}
   772  			if opt.Update && !f.Exists() {
   773  				// It doesn't exist on the filesystem, so it should be checked out.
   774  				files = append(files, f)
   775  				continue
   776  			}
   777  			orig, ok := origidx[entry.PathName]
   778  			if !ok {
   779  				// If it wasn't in the original index, make sure
   780  				// we check it out after verifying there's not
   781  				// already something there.
   782  				if opt.Update && f.Exists() {
   783  					lsopts := LsFilesOptions{Others: true}
   784  					if opt.ExcludePerDirectory != "" {
   785  						lsopts.ExcludePerDirectory = []File{File(opt.ExcludePerDirectory)}
   786  					}
   787  					untracked, err := LsFiles(c, lsopts, []File{f})
   788  					if err != nil {
   789  						return err
   790  					}
   791  					if len(untracked) > 0 {
   792  						if !entry.PathName.IsClean(c, entry.Sha1) {
   793  							return fmt.Errorf("Untracked working tree file '%v' would be overwritten by merge", f)
   794  						}
   795  					}
   796  				}
   797  				file, err := entry.PathName.FilePath(c)
   798  				if err != nil {
   799  					return err
   800  				}
   801  				files = append(files, file)
   802  				continue
   803  			}
   804  
   805  			if orig.Sha1 == entry.Sha1 {
   806  				// Nothing was modified, so don't bother checking
   807  				// anything out
   808  				continue
   809  			}
   810  			if entry.PathName.IsClean(c, orig.Sha1) {
   811  				// it hasn't been modified locally, so we want to
   812  				// make sure the newidx version is checked out.
   813  				file, err := entry.PathName.FilePath(c)
   814  				if err != nil {
   815  					return err
   816  				}
   817  				files = append(files, file)
   818  				continue
   819  			} else {
   820  				// There are local unmodified changes on the filesystem
   821  				// from the original that would be lost by -u, so return
   822  				// an error unless --reset is specified.
   823  				if !opt.Reset {
   824  					return fmt.Errorf("%s has local changes. Can not merge.", entry.PathName)
   825  				} else {
   826  					// with --reset, checkout the file anyways.
   827  					file, err := entry.PathName.FilePath(c)
   828  					if err != nil {
   829  						return err
   830  					}
   831  					files = append(files, file)
   832  				}
   833  			}
   834  		}
   835  	}
   836  
   837  	if !opt.DryRun && (opt.Update || opt.Reset) {
   838  		if err := CheckoutIndexUncommited(c, newidx, CheckoutIndexOptions{Quiet: true, Force: true, Prefix: opt.Prefix}, files); err != nil {
   839  			return err
   840  		}
   841  
   842  		// Convert to a map for constant time lookup in our loop..
   843  		newidxMap := newidx.GetMap()
   844  
   845  		// Before returning, delete anything that was in the old index, removed
   846  		// from the new index, and hasn't been changed on the filesystem.
   847  		for path, entry := range origidx {
   848  			if _, ok := newidxMap[path]; ok {
   849  				// It was already handled by checkout-index
   850  				continue
   851  			}
   852  			file, err := path.FilePath(c)
   853  			if err != nil {
   854  				// Don't error out since we've already
   855  				// mucked up other stuff, just carry
   856  				// on to the next file.
   857  				fmt.Fprintf(os.Stderr, "%v\n", err)
   858  				continue
   859  
   860  			}
   861  
   862  			// It was deleted from the new index, but was in the
   863  			// original index, so delete it if it hasn't been
   864  			// changed on the filesystem.
   865  			if path.IsClean(c, entry.Sha1) {
   866  				if err := removeFileClean(file); err != nil {
   867  					return err
   868  				}
   869  			} else if !opt.NoSparseCheckout {
   870  				if !checkSparseMatches(c, opt, path, sparsePatterns) {
   871  					if file.Exists() {
   872  						if err := removeFileClean(file); err != nil {
   873  							return err
   874  						}
   875  					}
   876  				}
   877  			}
   878  		}
   879  
   880  		// Update stat information for things changed by CheckoutIndex, and remove anything
   881  		// with the SkipWorktree bit set.
   882  		for _, entry := range newidx.Objects {
   883  			f, err := entry.PathName.FilePath(c)
   884  			if err != nil {
   885  				return err
   886  			}
   887  			if f.Exists() {
   888  				if entry.SkipWorktree() {
   889  					if entry.PathName.IsClean(c, entry.Sha1) {
   890  						if err := removeFileClean(f); err != nil {
   891  							return err
   892  						}
   893  					}
   894  					continue
   895  				}
   896  				if err := entry.RefreshStat(c); err != nil {
   897  					return err
   898  				}
   899  			}
   900  		}
   901  
   902  		if opt.Reset {
   903  			for _, file := range resetremovals {
   904  				// It may have been removed by the removal loop above
   905  				if file.Exists() {
   906  					if err := file.Remove(); err != nil {
   907  						return err
   908  					}
   909  				}
   910  			}
   911  		}
   912  	}
   913  	return nil
   914  }
   915  
   916  func removeFileClean(f File) error {
   917  	if err := f.Remove(); err != nil {
   918  		return err
   919  	}
   920  	// If there's nothing left in the directory, remove
   921  	dir := filepath.Dir(f.String())
   922  	files, err := ioutil.ReadDir(dir)
   923  	if err != nil {
   924  		return err
   925  	}
   926  	if len(files) == 0 {
   927  		if err := os.RemoveAll(dir); err != nil {
   928  			return err
   929  		}
   930  	}
   931  	return nil
   932  }