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

     1  package git
     2  
     3  import (
     4  	"crypto/sha1"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"sort"
    13  	"strings"
    14  )
    15  
    16  var InvalidIndex error = errors.New("Invalid index")
    17  
    18  // index file is defined as Network byte Order (Big Endian)
    19  
    20  // 12 byte header:
    21  // 4 bytes: D I R C (stands for "Dir cache")
    22  // 4-byte version number (can be 2, 3 or 4)
    23  // 32bit number of index entries
    24  type fixedGitIndex struct {
    25  	Signature          [4]byte // 4
    26  	Version            uint32  // 8
    27  	NumberIndexEntries uint32  // 12
    28  }
    29  
    30  type Index struct {
    31  	fixedGitIndex // 12
    32  	Objects       []*IndexEntry
    33  }
    34  
    35  type V3IndexExtensions struct {
    36  	Flags uint16
    37  }
    38  
    39  type IndexEntry struct {
    40  	FixedIndexEntry
    41  	*V3IndexExtensions
    42  	PathName IndexPath
    43  }
    44  
    45  func (ie IndexEntry) Stage() Stage {
    46  	return Stage((ie.FixedIndexEntry.Flags >> 12) & 0x3)
    47  }
    48  
    49  func (ie IndexEntry) SkipWorktree() bool {
    50  	if ie.ExtendedFlag() == false || ie.V3IndexExtensions == nil {
    51  		return false
    52  	}
    53  	return (ie.V3IndexExtensions.Flags>>14)&0x1 == 1
    54  }
    55  func (ie *IndexEntry) SetSkipWorktree(value bool) {
    56  	if value {
    57  		// If it's being set, we need to set the extended
    58  		// flag. If it's not being set, we don't care, but
    59  		// we don't change it in case intend-to-add is set.
    60  		ie.FixedIndexEntry.SetExtendedFlag(true)
    61  	}
    62  
    63  	if ie.V3IndexExtensions == nil {
    64  		ie.V3IndexExtensions = &V3IndexExtensions{}
    65  	}
    66  	if value == true {
    67  		ie.V3IndexExtensions.Flags |= (0x1 << 14)
    68  	} else {
    69  		ie.V3IndexExtensions.Flags &^= (0x1 << 14)
    70  	}
    71  }
    72  
    73  func NewIndex() *Index {
    74  	return &Index{
    75  		fixedGitIndex: fixedGitIndex{
    76  			Signature:          [4]byte{'D', 'I', 'R', 'C'},
    77  			Version:            2,
    78  			NumberIndexEntries: 0,
    79  		},
    80  		Objects: make([]*IndexEntry, 0),
    81  	}
    82  }
    83  
    84  type FixedIndexEntry struct {
    85  	Ctime     uint32 // 16
    86  	Ctimenano uint32 // 20
    87  
    88  	Mtime int64 // 24
    89  
    90  	Dev uint32 // 32
    91  	Ino uint32 // 36
    92  
    93  	Mode EntryMode // 40
    94  
    95  	Uid uint32 // 44
    96  	Gid uint32 // 48
    97  
    98  	Fsize uint32 // 52
    99  
   100  	Sha1 Sha1 // 72
   101  
   102  	Flags uint16 // 74
   103  }
   104  
   105  func (i FixedIndexEntry) ExtendedFlag() bool {
   106  	return ((i.Flags >> 14) & 0x1) == 1
   107  }
   108  func (i *FixedIndexEntry) SetExtendedFlag(val bool) {
   109  	if val {
   110  		i.Flags |= (0x1 << 14)
   111  	} else {
   112  		i.Flags &^= (0x1 << 14)
   113  	}
   114  }
   115  
   116  // Refreshes the stat information for this entry using the file
   117  // file
   118  func (i *FixedIndexEntry) RefreshStat(f File) error {
   119  	log.Printf("Refreshing stat info for %v\n", f)
   120  	// FIXME: Add other stat info here too, but these are the
   121  	// most important ones and the onlye ones that the os package
   122  	// exposes in a cross-platform way.
   123  	stat, err := f.Lstat()
   124  	if err != nil {
   125  		return err
   126  	}
   127  	fmtime, err := f.MTime()
   128  	if err != nil {
   129  		return err
   130  	}
   131  	i.Mtime = fmtime
   132  	i.Fsize = uint32(stat.Size())
   133  	i.Ctime, i.Ctimenano = f.CTime()
   134  	i.Ino = f.INode()
   135  	return nil
   136  }
   137  
   138  // Refreshes the stat information for this entry in the index against
   139  // the stat info on the filesystem for things that we know about.
   140  func (i *FixedIndexEntry) CompareStat(f File) error {
   141  	log.Printf("Comparing stat info for %v\n", f)
   142  	// FIXME: Add other stat info here too, but these are the
   143  	// most important ones and the onlye ones that the os package
   144  	// exposes in a cross-platform way.
   145  	stat, err := f.Lstat()
   146  	if err != nil {
   147  		return err
   148  	}
   149  	fmtime, err := f.MTime()
   150  	if err != nil {
   151  		return err
   152  	}
   153  	if i.Mtime != fmtime {
   154  		return fmt.Errorf("MTime does not match for %v", f)
   155  	}
   156  	if f.IsSymlink() {
   157  		dst, err := os.Readlink(string(f))
   158  		if err != nil {
   159  			return err
   160  		}
   161  		if int(i.Fsize) != len(dst) {
   162  			return fmt.Errorf("Size does not match for symlink %v", f)
   163  		}
   164  	} else {
   165  		if i.Fsize != uint32(stat.Size()) {
   166  			return fmt.Errorf("Size does not match for %v", f)
   167  		}
   168  	}
   169  	ctime, ctimenano := f.CTime()
   170  	if i.Ctime != ctime || i.Ctimenano != ctimenano {
   171  		return fmt.Errorf("CTime does not match for %v", f)
   172  	}
   173  	if i.Ino != f.INode() {
   174  		return fmt.Errorf("INode does not match for %v", f)
   175  	}
   176  	return nil
   177  }
   178  
   179  // Refreshes the stat information for an index entry by comparing
   180  // it against the path in the index.
   181  func (ie *IndexEntry) RefreshStat(c *Client) error {
   182  	f, err := ie.PathName.FilePath(c)
   183  	if err != nil {
   184  		return err
   185  	}
   186  	return ie.FixedIndexEntry.RefreshStat(f)
   187  }
   188  
   189  // Reads the index file from the GitDir and returns a Index object.
   190  // If the index file does not exist, it will return a new empty Index.
   191  func (d GitDir) ReadIndex() (*Index, error) {
   192  	var file *os.File
   193  	var err error
   194  	if ifile := os.Getenv("GIT_INDEX_FILE"); ifile != "" {
   195  		log.Println("Using index file", ifile)
   196  		file, err = os.Open(ifile)
   197  	} else {
   198  
   199  		file, err = d.Open("index")
   200  	}
   201  
   202  	if err != nil {
   203  		if os.IsNotExist(err) {
   204  			// Is the file doesn't exist, treat it
   205  			// as a new empty index.
   206  			return &Index{
   207  				fixedGitIndex{
   208  					[4]byte{'D', 'I', 'R', 'C'},
   209  					2, // version 2
   210  					0, // no entries
   211  				},
   212  				make([]*IndexEntry, 0),
   213  			}, nil
   214  		}
   215  		return nil, err
   216  	}
   217  	defer file.Close()
   218  
   219  	var i fixedGitIndex
   220  	binary.Read(file, binary.BigEndian, &i)
   221  	if i.Signature != [4]byte{'D', 'I', 'R', 'C'} {
   222  		return nil, InvalidIndex
   223  	}
   224  	if i.Version < 2 || i.Version > 4 {
   225  		return nil, InvalidIndex
   226  	}
   227  	log.Println("Index version", i.Version)
   228  
   229  	var idx uint32
   230  	indexes := make([]*IndexEntry, i.NumberIndexEntries, i.NumberIndexEntries)
   231  	for idx = 0; idx < i.NumberIndexEntries; idx += 1 {
   232  		index, err := ReadIndexEntry(file, i.Version)
   233  		if err != nil {
   234  			log.Println(err)
   235  		} else {
   236  			log.Println("Read entry for ", index.PathName)
   237  			indexes[idx] = index
   238  		}
   239  	}
   240  	return &Index{i, indexes}, nil
   241  }
   242  
   243  func ReadIndexEntry(file *os.File, indexVersion uint32) (*IndexEntry, error) {
   244  	log.Printf("Reading index entry from %v assuming index version %d\n", file.Name(), indexVersion)
   245  	if indexVersion < 2 || indexVersion > 3 {
   246  		return nil, fmt.Errorf("Unsupported index version.")
   247  	}
   248  	var f FixedIndexEntry
   249  	var name []byte
   250  	if err := binary.Read(file, binary.BigEndian, &f); err != nil {
   251  		return nil, err
   252  	}
   253  
   254  	var v3e *V3IndexExtensions
   255  	if f.ExtendedFlag() {
   256  		if indexVersion < 3 {
   257  			return nil, InvalidIndex
   258  		}
   259  		v3e = &V3IndexExtensions{}
   260  		if err := binary.Read(file, binary.BigEndian, v3e); err != nil {
   261  			return nil, err
   262  		}
   263  	}
   264  
   265  	var nameLength uint16
   266  	nameLength = f.Flags & 0x0FFF
   267  
   268  	if nameLength&0xFFF != 0xFFF {
   269  		name = make([]byte, nameLength, nameLength)
   270  		n, err := file.Read(name)
   271  		if err != nil {
   272  			panic("I don't know what to do")
   273  		}
   274  		if n != int(nameLength) {
   275  			panic(fmt.Sprintf("Error reading the name read %d (got :%v)", n, string(name[:n])))
   276  		}
   277  
   278  		// I don't understand where this +4 comes from, but it seems to work
   279  		// out with how the c git implementation calculates the padding..
   280  		//
   281  		// The definition of the index file format at:
   282  		// https://github.com/git/git/blob/master/Documentation/technical/index-format.txt
   283  		// claims that there should be "1-8 nul bytes as necessary to pad the entry to a multiple of eight
   284  		// bytes while keeping the name NUL-terminated."
   285  		//
   286  		// The fixed size of the header is 82 bytes if you add up all the types.
   287  		// the length of the name is nameLength bytes, so according to the spec
   288  		// this *should* be 8 - ((82 + nameLength) % 8) bytes of padding.
   289  		// But reading existant index files, there seems to be an extra 4 bytes
   290  		// incorporated into the index size calculation.
   291  		sz := uint16(82)
   292  
   293  		if f.ExtendedFlag() {
   294  			// Add 2 bytes if the extended flag is set for the V3 extensions
   295  			sz += 2
   296  		}
   297  		expectedOffset := 8 - ((sz + nameLength + 4) % 8)
   298  		file.Seek(int64(expectedOffset), 1)
   299  	} else {
   300  
   301  		nbyte := make([]byte, 1, 1)
   302  
   303  		// This algorithm isn't very efficient, reading one byte at a time, but we
   304  		// reserve a big space for name to make it slightly more efficient, since
   305  		// we know it's a large name
   306  		name = make([]byte, 0, 8192)
   307  		for _, err := file.Read(nbyte); nbyte[0] != 0; _, err = file.Read(nbyte) {
   308  			if err != nil {
   309  				return nil, err
   310  			}
   311  			name = append(name, nbyte...)
   312  		}
   313  		off, err := file.Seek(0, io.SeekCurrent)
   314  		if err != nil {
   315  			return nil, err
   316  		}
   317  		// The mystery 4 appears again.
   318  		padding := 8 - ((off + 4) % 8)
   319  		if _, err := file.Seek(padding, io.SeekCurrent); err != nil {
   320  			return nil, err
   321  		}
   322  	}
   323  	return &IndexEntry{f, v3e, IndexPath(name)}, nil
   324  }
   325  
   326  // A Stage represents a git merge stage in the index.
   327  type Stage uint8
   328  
   329  // Valid merge stages.
   330  const (
   331  	Stage0 = Stage(iota)
   332  	Stage1
   333  	Stage2
   334  	Stage3
   335  )
   336  
   337  func (g *Index) SetSkipWorktree(c *Client, path IndexPath, value bool) error {
   338  	for _, entry := range g.Objects {
   339  		if entry.PathName == path {
   340  			if entry.Stage() != Stage0 {
   341  				return fmt.Errorf("Can not set skip worktree on unmerged paths")
   342  			}
   343  			entry.SetSkipWorktree(value)
   344  			break
   345  		}
   346  	}
   347  	if g.Version <= 2 && value {
   348  		g.Version = 3
   349  	}
   350  	return nil
   351  }
   352  
   353  // Adds an entry to the index with Sha1 s and stage stage during a merge.
   354  // If an entry already exists for this pathname/stage, it will be overwritten,
   355  // otherwise it will be added if createEntry is true, and return an error if not.
   356  //
   357  // As a special case, if something is added as Stage0, then Stage1-3 entries
   358  // will be removed.
   359  func (g *Index) AddStage(c *Client, path IndexPath, mode EntryMode, s Sha1, stage Stage, size uint32, mtime int64, opts UpdateIndexOptions) error {
   360  	if stage == Stage0 {
   361  		defer g.RemoveUnmergedStages(c, path)
   362  	}
   363  
   364  	replaceEntriesCheck := func() error {
   365  		if stage != Stage0 {
   366  			return nil
   367  		}
   368  		// If replace is true then we search for any entries that
   369  		//  should be replaced with this one.
   370  		newObjects := make([]*IndexEntry, 0, len(g.Objects))
   371  		for _, e := range g.Objects {
   372  			if strings.HasPrefix(string(e.PathName), string(path)+"/") {
   373  				if !opts.Replace {
   374  					return fmt.Errorf("There is an existing file %s under %s, should it be replaced?", e.PathName, path)
   375  				}
   376  				continue
   377  			} else if strings.HasPrefix(string(path), string(e.PathName)+"/") {
   378  				if !opts.Replace {
   379  					return fmt.Errorf("There is a parent file %s above %s, should it be replaced?", e.PathName, path)
   380  				}
   381  				continue
   382  			}
   383  
   384  			newObjects = append(newObjects, e)
   385  		}
   386  
   387  		g.Objects = newObjects
   388  
   389  		return nil
   390  	}
   391  
   392  	// Update the existing stage, if it exists.
   393  	for _, entry := range g.Objects {
   394  		if entry.PathName == path && entry.Stage() == stage {
   395  			if err := replaceEntriesCheck(); err != nil {
   396  				return err
   397  			}
   398  
   399  			file, _ := path.FilePath(c)
   400  			if file.Exists() && stage == Stage0 {
   401  				// FIXME: mtime/fsize/etc and ctime should either all be
   402  				// from the filesystem, or all come from the caller
   403  				// For now we just refresh the stat, and then overwrite with
   404  				// the stuff from the caller.
   405  				log.Println("Refreshing stat for", path)
   406  				if err := entry.RefreshStat(c); err != nil {
   407  					return err
   408  				}
   409  			}
   410  			entry.Sha1 = s
   411  			entry.Mtime = mtime
   412  			entry.Fsize = size
   413  			return nil
   414  		}
   415  	}
   416  
   417  	if !opts.Add {
   418  		return fmt.Errorf("%v not found in index", path)
   419  	}
   420  	// There was no path/stage combo already in the index. Add it.
   421  
   422  	// According to the git documentation:
   423  	// Flags is
   424  	//    A 16-bit 'flags' field split into (high to low bits)
   425  	//
   426  	//       1-bit assume-valid flag
   427  	//
   428  	//       1-bit extended flag (must be zero in version 2)
   429  	//
   430  	//       2-bit stage (during merge)
   431  	//       12-bit name length if the length is less than 0xFFF; otherwise 0xFFF
   432  	//     is stored in this field.
   433  
   434  	// So we'll construct the flags based on what we know.
   435  
   436  	var flags = uint16(stage) << 12 // start with the stage.
   437  	// Add the name length.
   438  	if len(path) >= 0x0FFF {
   439  		flags |= 0x0FFF
   440  	} else {
   441  		flags |= (uint16(len(path)) & 0x0FFF)
   442  	}
   443  
   444  	if err := replaceEntriesCheck(); err != nil {
   445  		return err
   446  	}
   447  	newentry := &IndexEntry{
   448  		FixedIndexEntry{
   449  			0, //uint32(csec),
   450  			0, //uint32(cnano),
   451  			mtime,
   452  			0, //uint32(stat.Dev),
   453  			0, //uint32(stat.Ino),
   454  			mode,
   455  			0, //stat.Uid,
   456  			0, //stat.Gid,
   457  			size,
   458  			s,
   459  			flags,
   460  		},
   461  		&V3IndexExtensions{},
   462  		path,
   463  	}
   464  	newentry.RefreshStat(c)
   465  
   466  	g.Objects = append(g.Objects, newentry)
   467  	g.NumberIndexEntries += 1
   468  	sort.Sort(ByPath(g.Objects))
   469  	return nil
   470  }
   471  
   472  // Remove any unmerged (non-stage 0) stage from the index for the given path
   473  func (g *Index) RemoveUnmergedStages(c *Client, path IndexPath) error {
   474  	// There are likely 3 things being deleted, so make a new slice
   475  	newobjects := make([]*IndexEntry, 0, len(g.Objects))
   476  	for _, entry := range g.Objects {
   477  		stage := entry.Stage()
   478  		if entry.PathName == path && stage == Stage0 {
   479  			newobjects = append(newobjects, entry)
   480  		} else if entry.PathName == path && stage != Stage0 {
   481  			// do not add it, it's the wrong stage.
   482  		} else {
   483  			// It's a different Pathname, keep it.
   484  			newobjects = append(newobjects, entry)
   485  		}
   486  	}
   487  	g.Objects = newobjects
   488  	g.NumberIndexEntries = uint32(len(newobjects))
   489  	return nil
   490  }
   491  
   492  // Adds a file to the index, without writing it to disk.
   493  // To write it to disk after calling this, use GitIndex.WriteIndex
   494  //
   495  // This will do the following:
   496  // 1. write git object blob of file contents to .git/objects
   497  // 2. normalize os.File name to path relative to gitRoot
   498  // 3. search GitIndex for normalized name
   499  //	if GitIndexEntry found
   500  //		update GitIndexEntry to point to the new object blob
   501  // 	else
   502  // 		add new GitIndexEntry if not found and createEntry is true, error otherwise
   503  //
   504  func (g *Index) AddFile(c *Client, file File, opts UpdateIndexOptions) error {
   505  	name, err := file.IndexPath(c)
   506  	if err != nil {
   507  		return err
   508  	}
   509  
   510  	mtime, err := file.MTime()
   511  	if err != nil {
   512  		return err
   513  	}
   514  
   515  	fsize := uint32(0)
   516  	fstat, err := file.Lstat()
   517  	if err == nil {
   518  		fsize = uint32(fstat.Size())
   519  	}
   520  	if fstat.IsDir() {
   521  		return fmt.Errorf("Must add a file, not a directory.")
   522  	}
   523  	var mode EntryMode
   524  
   525  	var hash Sha1
   526  	if file.IsSymlink() {
   527  		mode = ModeSymlink
   528  		contents, err := os.Readlink(string(file))
   529  		if err != nil {
   530  			return err
   531  		}
   532  		hash1, err := c.WriteObject("blob", []byte(contents))
   533  		if err != nil {
   534  			fmt.Fprintf(os.Stderr, "Error storing object: %s", err)
   535  		}
   536  		hash = hash1
   537  	} else {
   538  		mode = ModeBlob
   539  		contents, err := ioutil.ReadFile(string(file))
   540  		if err != nil {
   541  			return err
   542  		}
   543  		hash1, err := c.WriteObject("blob", contents)
   544  		if err != nil {
   545  			fmt.Fprintf(os.Stderr, "Error storing object: %s", err)
   546  			return err
   547  		}
   548  		hash = hash1
   549  	}
   550  
   551  	return g.AddStage(
   552  		c,
   553  		name,
   554  		mode,
   555  		hash,
   556  		Stage0,
   557  		fsize,
   558  		mtime,
   559  		opts,
   560  	)
   561  }
   562  
   563  type IndexStageEntry struct {
   564  	IndexPath
   565  	Stage
   566  }
   567  
   568  func (i *Index) GetStageMap() map[IndexStageEntry]*IndexEntry {
   569  	r := make(map[IndexStageEntry]*IndexEntry)
   570  	for _, entry := range i.Objects {
   571  		r[IndexStageEntry{entry.PathName, entry.Stage()}] = entry
   572  	}
   573  	return r
   574  }
   575  
   576  type UnmergedPath struct {
   577  	Stage1, Stage2, Stage3 *IndexEntry
   578  }
   579  
   580  func (i *Index) GetUnmerged() map[IndexPath]*UnmergedPath {
   581  	r := make(map[IndexPath]*UnmergedPath)
   582  	for _, entry := range i.Objects {
   583  		if entry.Stage() != Stage0 {
   584  			e, ok := r[entry.PathName]
   585  			if !ok {
   586  				e = &UnmergedPath{}
   587  				r[entry.PathName] = e
   588  			}
   589  			switch entry.Stage() {
   590  			case Stage1:
   591  				e.Stage1 = entry
   592  			case Stage2:
   593  				e.Stage2 = entry
   594  			case Stage3:
   595  				e.Stage3 = entry
   596  			}
   597  		}
   598  	}
   599  	return r
   600  }
   601  
   602  // Remove the first instance of file from the index. (This will usually
   603  // be stage 0.)
   604  func (g *Index) RemoveFile(file IndexPath) {
   605  	for i, entry := range g.Objects {
   606  		if entry.PathName == file {
   607  			g.Objects = append(g.Objects[:i], g.Objects[i+1:]...)
   608  			g.NumberIndexEntries -= 1
   609  			return
   610  		}
   611  	}
   612  }
   613  
   614  // This will write a new index file to w by doing the following:
   615  // 1. Sort the objects in g.Index to ascending order based on name and update
   616  //    g.NumberIndexEntries
   617  // 2. Write g.fixedGitIndex to w
   618  // 3. for each entry in g.Objects, write it to w.
   619  // 4. Write the Sha1 of the contents of what was written
   620  func (g Index) WriteIndex(file io.Writer) error {
   621  	sort.Sort(ByPath(g.Objects))
   622  	g.NumberIndexEntries = uint32(len(g.Objects))
   623  	s := sha1.New()
   624  	w := io.MultiWriter(file, s)
   625  	binary.Write(w, binary.BigEndian, g.fixedGitIndex)
   626  	for _, entry := range g.Objects {
   627  		if err := binary.Write(w, binary.BigEndian, entry.FixedIndexEntry); err != nil {
   628  			return err
   629  		}
   630  		if entry.ExtendedFlag() {
   631  			if g.Version == 2 || entry.V3IndexExtensions == nil {
   632  				return InvalidIndex
   633  			}
   634  			if err := binary.Write(w, binary.BigEndian, *entry.V3IndexExtensions); err != nil {
   635  				return err
   636  			}
   637  		}
   638  		if err := binary.Write(w, binary.BigEndian, []byte(entry.PathName)); err != nil {
   639  			return err
   640  		}
   641  		sz := 82
   642  		if entry.ExtendedFlag() {
   643  			sz += 2
   644  		}
   645  		padding := 8 - ((sz + len(entry.PathName) + 4) % 8)
   646  		p := make([]byte, padding)
   647  		if err := binary.Write(w, binary.BigEndian, p); err != nil {
   648  			return err
   649  		}
   650  	}
   651  	binary.Write(w, binary.BigEndian, s.Sum(nil))
   652  	return nil
   653  }
   654  
   655  // Looks up the Sha1 of path currently stored in the index.
   656  // Will return the 0 Sha1 if not found.
   657  func (g Index) GetSha1(path IndexPath) Sha1 {
   658  	for _, entry := range g.Objects {
   659  		if entry.PathName == path {
   660  			return entry.Sha1
   661  		}
   662  	}
   663  	return Sha1{}
   664  }
   665  
   666  // Implement the sort interface on *GitIndexEntry, so that
   667  // it's easy to sort by name.
   668  type ByPath []*IndexEntry
   669  
   670  func (g ByPath) Len() int      { return len(g) }
   671  func (g ByPath) Swap(i, j int) { g[i], g[j] = g[j], g[i] }
   672  func (g ByPath) Less(i, j int) bool {
   673  	if g[i].PathName == g[j].PathName {
   674  		return g[i].Stage() < g[j].Stage()
   675  	}
   676  	ibytes := []byte(g[i].PathName)
   677  	jbytes := []byte(g[j].PathName)
   678  	for k := range ibytes {
   679  		if k >= len(jbytes) {
   680  			// We reached the end of j and there was stuff
   681  			// leftover in i, so i > j
   682  			return false
   683  		}
   684  
   685  		// If a character is not equal, return if it's
   686  		// less or greater
   687  		if ibytes[k] < jbytes[k] {
   688  			return true
   689  		} else if ibytes[k] > jbytes[k] {
   690  			return false
   691  		}
   692  	}
   693  	// Everything equal up to the end of i, and there is stuff
   694  	// left in j, so i < j
   695  	return true
   696  }
   697  
   698  // Replaces the index of Client with the the tree from the provided Treeish.
   699  // if PreserveStatInfo is true, the stat information in the index won't be
   700  // modified for existing entries.
   701  func (g *Index) ResetIndex(c *Client, tree Treeish) error {
   702  	newEntries, err := expandGitTreeIntoIndexes(c, tree, true, false, false)
   703  	if err != nil {
   704  		return err
   705  	}
   706  	g.NumberIndexEntries = uint32(len(newEntries))
   707  	g.Objects = newEntries
   708  	return nil
   709  }
   710  
   711  func (g Index) String() string {
   712  	ret := ""
   713  
   714  	for _, i := range g.Objects {
   715  		ret += fmt.Sprintf("%v %v %v\n", i.Mode, i.Sha1, i.PathName)
   716  	}
   717  	return ret
   718  }
   719  
   720  type IndexMap map[IndexPath]*IndexEntry
   721  
   722  func (i *Index) GetMap() IndexMap {
   723  	r := make(IndexMap)
   724  	for _, entry := range i.Objects {
   725  		r[entry.PathName] = entry
   726  	}
   727  	return r
   728  }
   729  
   730  func (im IndexMap) Contains(path IndexPath) bool {
   731  	if _, ok := im[path]; ok {
   732  		return true
   733  	}
   734  
   735  	// Check of there is a directory named path in the IndexMap
   736  	return im.HasDir(path)
   737  }
   738  
   739  func (im IndexMap) HasDir(path IndexPath) bool {
   740  	for _, im := range im {
   741  		if strings.HasPrefix(string(im.PathName), string(path+"/")) {
   742  			return true
   743  		}
   744  	}
   745  	return false
   746  }