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

     1  package git
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/driusan/dgit/zlib"
    15  )
    16  
    17  type Sha1 [20]byte
    18  type CommitID Sha1
    19  type TreeID Sha1
    20  type BlobID Sha1
    21  
    22  type Treeish interface {
    23  	TreeID(c *Client) (TreeID, error)
    24  }
    25  
    26  type Commitish interface {
    27  	CommitID(c *Client) (CommitID, error)
    28  }
    29  
    30  func CommitIDFromString(s string) (CommitID, error) {
    31  	s1, err := Sha1FromString(s)
    32  	return CommitID(s1), err
    33  }
    34  
    35  func Sha1FromString(s string) (Sha1, error) {
    36  	b, err := hex.DecodeString(strings.TrimSpace(s))
    37  	if err != nil {
    38  		return Sha1{}, err
    39  	}
    40  	return Sha1FromSlice(b)
    41  }
    42  
    43  func Sha1FromSlice(s []byte) (Sha1, error) {
    44  	if len(s) != 20 {
    45  		return Sha1{}, fmt.Errorf("Invalid Sha1 %x (Size: %d)", s, len(s))
    46  	}
    47  	var val Sha1
    48  	for i, b := range s {
    49  		val[i] = b
    50  	}
    51  	return val, nil
    52  }
    53  
    54  func (s Sha1) String() string {
    55  	return fmt.Sprintf("%x", string(s[:]))
    56  }
    57  
    58  func (s TreeID) String() string {
    59  	return Sha1(s).String()
    60  }
    61  
    62  func (s CommitID) String() string {
    63  	return Sha1(s).String()
    64  }
    65  
    66  func (s CommitID) CommitID(c *Client) (CommitID, error) {
    67  	return s, nil
    68  }
    69  
    70  // Writes the object to w in compressed form
    71  func (s Sha1) CompressedWriter(c *Client, w io.Writer) error {
    72  	obj, err := c.GetObject(s)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	zw := zlib.NewWriter(w)
    77  	defer zw.Close()
    78  	tee := io.TeeReader(bytes.NewReader(obj.GetContent()), zw)
    79  	ioutil.ReadAll(tee)
    80  	return nil
    81  }
    82  
    83  func (s Sha1) UncompressedSize(c *Client) uint64 {
    84  	_, sz, err := c.GetObjectMetadata(s)
    85  	if err != nil {
    86  		return 0
    87  	}
    88  	return sz
    89  }
    90  
    91  func (id Sha1) PackEntryType(c *Client) PackEntryType {
    92  	switch id.Type(c) {
    93  	case "commit":
    94  		return OBJ_COMMIT
    95  	case "tree":
    96  		return OBJ_TREE
    97  	case "blob":
    98  		return OBJ_BLOB
    99  	case "tag":
   100  		return OBJ_TAG
   101  	default:
   102  		panic("Unknown Object type")
   103  	}
   104  }
   105  
   106  // Returns the git type of the object this Sha1 represents
   107  func (id Sha1) Type(c *Client) string {
   108  	t, _, err := c.GetObjectMetadata(id)
   109  	if err != nil {
   110  		panic(err)
   111  		return ""
   112  	}
   113  	return t
   114  }
   115  
   116  // Returns all direct parents of commit c.
   117  func (cmt CommitID) Parents(c *Client) ([]CommitID, error) {
   118  	obj, err := c.GetObject(Sha1(cmt))
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	val := obj.GetContent()
   123  	reader := bytes.NewBuffer(val)
   124  	var parents []CommitID
   125  	for line, err := reader.ReadBytes('\n'); err == nil; line, err = reader.ReadBytes('\n') {
   126  		line = bytes.TrimSuffix(line, []byte{'\n'})
   127  		if len(line) == 0 {
   128  			return parents, nil
   129  		}
   130  		if bytes.HasPrefix(line, []byte("parent ")) {
   131  			pc := string(bytes.TrimPrefix(line, []byte("parent ")))
   132  			parent, err := CommitIDFromString(pc)
   133  			if err != nil {
   134  				return nil, err
   135  			}
   136  			parents = append(parents, parent)
   137  		}
   138  	}
   139  	return parents, nil
   140  }
   141  
   142  func (child CommitID) IsAncestor(c *Client, parent Commitish) bool {
   143  	p, err := parent.CommitID(c)
   144  	if err != nil {
   145  		return false
   146  	}
   147  
   148  	ancestorMap, err := p.AncestorMap(c)
   149  	if err != nil {
   150  		return false
   151  	}
   152  
   153  	_, ok := ancestorMap[child]
   154  	return ok
   155  }
   156  
   157  var ancestorMapCache map[CommitID]map[CommitID]struct{}
   158  
   159  // AncestorMap returns a map of empty structs (which can be interpreted as a set)
   160  // of ancestors of a CommitID.
   161  //
   162  // It's useful if you want to know all the ancestors of s, but don't particularly
   163  // care about their order. Since commits parents can never be changed, multiple
   164  // calls to AncestorMap are cached and the cost of calculating the ancestory tree
   165  // is only incurred the first time.
   166  func (s CommitID) AncestorMap(c *Client) (map[CommitID]struct{}, error) {
   167  	if cached, ok := ancestorMapCache[s]; ok {
   168  		return cached, nil
   169  	}
   170  	m := make(map[CommitID]struct{})
   171  	parents, err := s.Parents(c)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	var empty struct{}
   176  
   177  	m[s] = empty
   178  
   179  	for _, val := range parents {
   180  		m[val] = empty
   181  	}
   182  
   183  	for _, p := range parents {
   184  		grandparents, err := p.AncestorMap(c)
   185  		if err != nil {
   186  			return nil, err
   187  		}
   188  		for k, _ := range grandparents {
   189  			m[k] = empty
   190  		}
   191  	}
   192  
   193  	if ancestorMapCache == nil {
   194  		ancestorMapCache = make(map[CommitID]map[CommitID]struct{})
   195  	}
   196  	ancestorMapCache[s] = m
   197  
   198  	return m, nil
   199  
   200  }
   201  
   202  func (cmt CommitID) GetCommitterDate(c *Client) (time.Time, error) {
   203  	obj, err := c.GetCommitObject(cmt)
   204  	if err != nil {
   205  		return time.Time{}, err
   206  	}
   207  	committerStr := obj.GetHeader("committer")
   208  
   209  	if committerStr == "" {
   210  		return time.Time{}, fmt.Errorf("Commit %s does not have a committer", cmt)
   211  	}
   212  
   213  	committerPieces := strings.Split(committerStr, " ")
   214  	if len(committerPieces) < 3 {
   215  		return time.Time{}, fmt.Errorf("Could not parse committer %s", committerStr)
   216  
   217  	}
   218  
   219  	unixTime, err := strconv.ParseInt(committerPieces[len(committerPieces)-2], 10, 64)
   220  	if err != nil {
   221  		return time.Time{}, err
   222  	}
   223  	t := time.Unix(unixTime, 0)
   224  	timeZone := committerPieces[len(committerPieces)-1]
   225  	if loc, ok := tzCache[timeZone]; ok {
   226  		return t.In(loc), nil
   227  	}
   228  	tzHours, err := strconv.ParseInt(timeZone[:len(timeZone)-2], 10, 64)
   229  	if err != nil {
   230  		panic(err)
   231  	}
   232  	loc := time.FixedZone(timeZone, int(tzHours*60*60))
   233  	if tzCache == nil {
   234  		tzCache = make(map[string]*time.Location)
   235  
   236  	}
   237  	tzCache[timeZone] = loc
   238  	date := t.In(loc)
   239  
   240  	return date, nil
   241  }
   242  
   243  var tzCache map[string]*time.Location
   244  
   245  func (cmt CommitID) GetDate(c *Client) (time.Time, error) {
   246  	if cached, ok := ancestorDateCache[cmt]; ok {
   247  		return cached, nil
   248  	}
   249  
   250  	if ancestorDateCache == nil {
   251  		ancestorDateCache = make(map[CommitID]time.Time)
   252  	}
   253  
   254  	obj, err := c.GetCommitObject(cmt)
   255  	if err != nil {
   256  		return time.Time{}, err
   257  	}
   258  	authorStr := obj.GetHeader("author")
   259  
   260  	if authorStr == "" {
   261  		return time.Time{}, fmt.Errorf("Commit %s does not have an author", cmt)
   262  	}
   263  
   264  	// authorStr is in the format:
   265  	//    John Smith <jsmith@example.com> unixtime timezone
   266  	//
   267  	// we just split on space and rejoin the use the second last element
   268  	// to get the date/time. It's not very elegant, and it discards time
   269  	// zone information since time.Unix() doesn't allow us to specify time
   270  	// zone and there's no way to set it on a time after the fact.
   271  	authorPieces := strings.Split(authorStr, " ")
   272  	if len(authorPieces) < 3 {
   273  		return time.Time{}, fmt.Errorf("Could not parse author %s", authorStr)
   274  
   275  	}
   276  
   277  	unixTime, err := strconv.ParseInt(authorPieces[len(authorPieces)-2], 10, 64)
   278  	if err != nil {
   279  		return time.Time{}, err
   280  	}
   281  	t := time.Unix(unixTime, 0)
   282  	timeZone := authorPieces[len(authorPieces)-1]
   283  	if loc, ok := tzCache[timeZone]; ok {
   284  		return t.In(loc), nil
   285  	}
   286  	tzHours, err := strconv.ParseInt(timeZone[:len(timeZone)-2], 10, 64)
   287  	if err != nil {
   288  		panic(err)
   289  	}
   290  	loc := time.FixedZone(timeZone, int(tzHours*60*60))
   291  	if tzCache == nil {
   292  		tzCache = make(map[string]*time.Location)
   293  
   294  	}
   295  	tzCache[timeZone] = loc
   296  	date := t.In(loc)
   297  
   298  	ancestorDateCache[cmt] = date
   299  
   300  	return date, nil
   301  
   302  }
   303  
   304  func (cmt CommitID) GetCommitMessage(c *Client) (CommitMessage, error) {
   305  	obj, err := c.GetCommitObject(cmt)
   306  	if err != nil {
   307  		return "", err
   308  	}
   309  	for i, c := range obj.content {
   310  		if c == '\n' && obj.content[i+1] == '\n' {
   311  			return CommitMessage(string(obj.content[i+2:])), nil
   312  		}
   313  	}
   314  	return "", nil
   315  }
   316  
   317  // Returns the author of the commit (with no time information attached) to
   318  // the person object.
   319  func (cmt CommitID) GetAuthor(c *Client) (Person, error) {
   320  	obj, err := c.GetCommitObject(cmt)
   321  	if err != nil {
   322  		return Person{}, err
   323  	}
   324  	authorStr := obj.GetHeader("author")
   325  
   326  	if authorStr == "" {
   327  		return Person{}, fmt.Errorf("Could not parse author %s from commit %s", authorStr, cmt)
   328  	}
   329  
   330  	// authorStr is in the format:
   331  	//    John Smith <jsmith@example.com> unixtime timezone
   332  	authorPieces := strings.Split(authorStr, " ")
   333  	if len(authorPieces) < 3 {
   334  		return Person{}, fmt.Errorf("Commit %s does not have an author", cmt)
   335  	}
   336  
   337  	// FIXME: This should use
   338  	email := authorPieces[len(authorPieces)-3]
   339  	email = email[1 : len(email)-1] // strip off the < >
   340  	author := strings.Join(authorPieces[:len(authorPieces)-3], " ")
   341  	return Person{author, email, nil}, nil
   342  
   343  }
   344  
   345  // Returns the committer of the commit.
   346  func (cmt CommitID) GetCommitter(c *Client) (Person, error) {
   347  	obj, err := c.GetCommitObject(cmt)
   348  	if err != nil {
   349  		return Person{}, err
   350  	}
   351  	committerStr := obj.GetHeader("committer")
   352  
   353  	if committerStr == "" {
   354  		return Person{}, fmt.Errorf("Could not parse committer %s from commit %s", committerStr, cmt)
   355  	}
   356  
   357  	// committerStr is in the format:
   358  	//    John Smith <jsmith@example.com> unixtime timezone
   359  	committerPieces := strings.Split(committerStr, " ")
   360  	if len(committerPieces) < 3 {
   361  		return Person{}, fmt.Errorf("Commit %s does not have a committer", cmt)
   362  	}
   363  
   364  	// FIXME: This should use
   365  	email := committerPieces[len(committerPieces)-3]
   366  	email = email[1 : len(email)-1] // strip off the < >
   367  	committer := strings.Join(committerPieces[:len(committerPieces)-3], " ")
   368  	return Person{committer, email, nil}, nil
   369  
   370  }
   371  
   372  func (s CommitID) Ancestors(c *Client) ([]CommitID, error) {
   373  	ancestors, err := s.ancestors(c)
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  	if ancestorDateCache == nil {
   378  		ancestorDateCache = make(map[CommitID]time.Time)
   379  
   380  	}
   381  
   382  	sort.Slice(ancestors, func(i, j int) bool {
   383  		var iDate, jDate time.Time
   384  		var ok bool
   385  		if ancestors[i].IsAncestor(c, ancestors[j]) {
   386  			return false
   387  		}
   388  		if ancestors[j].IsAncestor(c, ancestors[i]) {
   389  			return true
   390  		}
   391  		if iDate, ok = ancestorDateCache[ancestors[i]]; !ok {
   392  			var err error
   393  			iDate, err = ancestors[i].GetDate(c)
   394  			if err != nil {
   395  				panic(err)
   396  			}
   397  			ancestorDateCache[ancestors[i]] = iDate
   398  		}
   399  		if jDate, ok = ancestorDateCache[ancestors[j]]; !ok {
   400  			var err error
   401  			jDate, err = ancestors[j].GetDate(c)
   402  			if err != nil {
   403  				panic(err)
   404  			}
   405  			ancestorDateCache[ancestors[j]] = jDate
   406  		}
   407  		return jDate.Before(iDate)
   408  	})
   409  	return ancestors, nil
   410  }
   411  
   412  var ancestorCache map[CommitID][]CommitID
   413  var ancestorDateCache map[CommitID]time.Time
   414  
   415  func (s CommitID) ancestors(c *Client) (commits []CommitID, err error) {
   416  	if cached, ok := ancestorCache[s]; ok {
   417  		return cached, nil
   418  	}
   419  
   420  	parents, err := s.Parents(c)
   421  	if err != nil {
   422  		return nil, err
   423  	}
   424  	commits = append(commits, s)
   425  
   426  	for _, p := range parents {
   427  		grandparents, err := p.ancestors(c)
   428  		if err != nil {
   429  			return nil, err
   430  		}
   431  
   432  	duplicateCheck:
   433  		for _, gp := range grandparents {
   434  			for _, cmt := range commits {
   435  				if cmt == gp {
   436  					continue duplicateCheck
   437  				}
   438  			}
   439  			commits = append(commits, gp)
   440  		}
   441  	}
   442  
   443  	if ancestorCache == nil {
   444  		ancestorCache = make(map[CommitID][]CommitID)
   445  	}
   446  
   447  	ancestorCache[s] = commits
   448  	return
   449  }
   450  
   451  func NearestCommonParent(c *Client, com, other Commitish) (CommitID, error) {
   452  	s, err := com.CommitID(c)
   453  	if err != nil {
   454  		return CommitID{}, err
   455  	}
   456  	ancestors, err := s.Ancestors(c)
   457  	if err != nil {
   458  		return CommitID{}, err
   459  	}
   460  	for _, commit := range ancestors {
   461  		// This is a horrible algorithm. TODO: Do something better than O(n^3)
   462  		if commit.IsAncestor(c, other) {
   463  			return commit, nil
   464  		}
   465  	}
   466  	// Nothing in common isn't an error, it just means the nearest common parent
   467  	// is 0 (the empty commit)
   468  	return CommitID{}, nil
   469  }
   470  
   471  func (c CommitID) GetAllObjects(cl *Client) ([]Sha1, error) {
   472  	return c.GetAllObjectsExcept(cl, nil)
   473  }
   474  
   475  // returns a list of all objects in c excluding those in excludeList. It also populates excludeList
   476  // with any objects encountered.
   477  func (c CommitID) GetAllObjectsExcept(cl *Client, excludeList map[Sha1]struct{}) ([]Sha1, error) {
   478  	var objects []Sha1
   479  	tree, err := c.TreeID(cl)
   480  	if err != nil {
   481  		return nil, err
   482  	}
   483  	if excludeList != nil {
   484  		if _, ok := excludeList[Sha1(tree)]; ok {
   485  			return nil, nil
   486  		}
   487  	}
   488  	objects = append(objects, Sha1(tree))
   489  	children, err := tree.GetAllObjectsExcept(cl, excludeList, "", true, false)
   490  	if err != nil {
   491  		return nil, err
   492  	}
   493  	for _, s := range children {
   494  		objects = append(objects, s.Sha1)
   495  	}
   496  	excludeList[Sha1(c)] = struct{}{}
   497  	return objects, nil
   498  
   499  }
   500  
   501  func (c CommitID) getRefNamesList(cl *Client) (string, error) {
   502  	refs, err := ShowRef(cl, ShowRefOptions{}, []string{})
   503  	if err != nil {
   504  		return "", err
   505  	}
   506  
   507  	headRefSpec, err := SymbolicRefGet(cl, SymbolicRefOptions{}, "HEAD")
   508  	if err != nil {
   509  		return "", err
   510  	}
   511  
   512  	refsForCommit := ""
   513  	for _, ref := range refs {
   514  		if len(refsForCommit) != 0 {
   515  			refsForCommit = refsForCommit + ", "
   516  		}
   517  
   518  		if ref.Value == Sha1(c) {
   519  
   520  			// TODO relate any other top-level refs to their linked ref, not just HEAD
   521  			if headRefSpec.String() == ref.Name {
   522  				refsForCommit = refsForCommit + "HEAD -> "
   523  			}
   524  			refsForCommit = refsForCommit + ref.RefName()
   525  		}
   526  	}
   527  
   528  	return refsForCommit, nil
   529  }
   530  
   531  func (c CommitID) FormatMedium(cl *Client) (string, error) {
   532  	output := ""
   533  
   534  	author, err := c.GetAuthor(cl)
   535  	if err != nil {
   536  		return "", err
   537  	}
   538  	output = output + fmt.Sprintf("commit %s", c)
   539  	refNameList, _ := c.getRefNamesList(cl)
   540  	if refNameList != "" {
   541  		output = output + fmt.Sprintf(" (%s)", refNameList)
   542  	}
   543  	output = output + "\n"
   544  	if parents, err := c.Parents(cl); len(parents) > 1 && err == nil {
   545  		output = output + "Merge: "
   546  		for i, p := range parents {
   547  			output = output + fmt.Sprintf("%s", p)
   548  			if i != len(parents)-1 {
   549  				output = output + " "
   550  			}
   551  		}
   552  		output = output + "\n"
   553  	}
   554  	date, err := c.GetDate(cl)
   555  	if err != nil {
   556  		return "", err
   557  	}
   558  	output = output + fmt.Sprintf("Author: %v\nDate:   %v\n\n", author, date.Format("Mon Jan 2 15:04:05 2006 -0700"))
   559  
   560  	msg, err := c.GetCommitMessage(cl)
   561  	if err != nil {
   562  		return "", err
   563  	}
   564  	lines := strings.Split(strings.TrimSpace(msg.String()), "\n")
   565  	for _, l := range lines {
   566  		output = output + fmt.Sprintf("    %v\n", l)
   567  	}
   568  	output = output + "\n"
   569  	return output, nil
   570  }
   571  
   572  func (c CommitID) Format(cl *Client, format string) (string, error) {
   573  	output := format
   574  
   575  	// Newline
   576  	if strings.Contains(output, "%n") {
   577  		output = strings.Replace(output, "%n", "\n", -1)
   578  	}
   579  
   580  	// Full commit hash
   581  	if strings.Contains(output, "%H") {
   582  		output = strings.Replace(output, "%H", c.String(), -1)
   583  	}
   584  
   585  	// Committer date as unix timestamp
   586  	if strings.Contains(output, "%ct") {
   587  		date, err := c.GetCommitterDate(cl)
   588  		if err != nil {
   589  			return "", err
   590  		}
   591  		output = strings.Replace(output, "%ct", strconv.FormatInt(date.Unix(), 10), -1)
   592  	}
   593  
   594  	// Author date as unix timestamp
   595  	if strings.Contains(output, "%at") {
   596  		date, err := c.GetDate(cl)
   597  		if err != nil {
   598  			return "", err
   599  		}
   600  		output = strings.Replace(output, "%at", strconv.FormatInt(date.Unix(), 10), -1)
   601  	}
   602  
   603  	// Show the non-stylized ref names beside any relevant commit
   604  	if strings.Contains(output, "%D") {
   605  		refNameList, _ := c.getRefNamesList(cl)
   606  		output = strings.Replace(output, "%D", refNameList, -1)
   607  	}
   608  
   609  	// TODO Add more formatting options (there are many)
   610  
   611  	return output, nil
   612  }
   613  
   614  // A TreeEntry represents an entry inside of a Treeish.
   615  type TreeEntry struct {
   616  	Sha1     Sha1
   617  	FileMode EntryMode
   618  }
   619  
   620  // Returns a map of all paths in the Tree. If recurse is true, it will recurse
   621  // into subtrees. If excludeself is true, it will *not* include it's own Sha1.
   622  // (Only really meaningful with recurse)
   623  func (t TreeID) GetAllObjects(cl *Client, prefix IndexPath, recurse, excludeself bool) (map[IndexPath]TreeEntry, error) {
   624  	return t.GetAllObjectsExcept(cl, nil, prefix, recurse, excludeself)
   625  }
   626  
   627  // parseRawtreeLine parses a single line from a raw tree object starting at
   628  // entryStart. It returns the TreeEntry and the number of bytes in treecontent
   629  // that it occupies.
   630  func parseRawTreeLine(entryStart int, treecontent []byte) (IndexPath, TreeEntry, int, error) {
   631  	// The format of each tree entry is:
   632  	// 	[permission] [name] \0 [20 bytes of Sha1]
   633  	// so we first search for a nul byte which means the next 20 bytes
   634  	// are the SHA, and then we can calculate the name and permissions based
   635  	// on the entryStart and the nil.
   636  	for i := entryStart; i < len(treecontent); i++ {
   637  		if treecontent[i] == 0 {
   638  			// Add 1 when converting the value of the sha1 because
   639  			// because i is currently set to the nil
   640  			sha, err := Sha1FromSlice(treecontent[i+1 : i+21])
   641  			if err != nil {
   642  				return "", TreeEntry{}, 0, err
   643  			}
   644  
   645  			// Split up the perm from the name based on the whitespace
   646  			split := bytes.SplitN(treecontent[entryStart:i], []byte{' '}, 2)
   647  			perm := split[0]
   648  			name := split[1]
   649  
   650  			var mode EntryMode
   651  			switch string(perm) {
   652  			case "40755": // sometimes in git9 repositories
   653  				mode = modeGit9Tree
   654  			case "40000": // valid git tree
   655  				mode = ModeTree
   656  			case "100644":
   657  				mode = ModeBlob
   658  			case "100755":
   659  				mode = ModeExec
   660  			case "120000":
   661  				mode = ModeSymlink
   662  			case "160000":
   663  				mode = ModeCommit
   664  			default:
   665  				return "", TreeEntry{}, 0, fmt.Errorf("Unsupported mode %v in tree", string(perm))
   666  			}
   667  			entryEnd := i + 20 + 1
   668  			return IndexPath(name), TreeEntry{
   669  				Sha1:     sha,
   670  				FileMode: mode,
   671  			}, entryEnd - entryStart, nil
   672  		}
   673  	}
   674  	return "", TreeEntry{}, 0, fmt.Errorf("Missing nil byte in tree entry")
   675  
   676  }
   677  
   678  // GetAllObjectsExcept is like GetAllObjects, except that it excludes those objects in excludeList. It also
   679  // populates excludeList with any objects encountered.
   680  func (t TreeID) GetAllObjectsExcept(cl *Client, excludeList map[Sha1]struct{}, prefix IndexPath, recurse, excludeself bool) (map[IndexPath]TreeEntry, error) {
   681  	if excludeList != nil {
   682  		excludeList[Sha1(t)] = struct{}{}
   683  	}
   684  	o, err := cl.GetObject(Sha1(t))
   685  	if err != nil {
   686  		return nil, err
   687  	}
   688  
   689  	if o.GetType() != "tree" {
   690  		return nil, fmt.Errorf("%s is not a tree object", t)
   691  	}
   692  
   693  	treecontent := o.GetContent()
   694  	val := make(map[IndexPath]TreeEntry)
   695  
   696  	i := 0
   697  	for i < len(treecontent) {
   698  		name, entry, size, err := parseRawTreeLine(i, treecontent)
   699  		if err != nil {
   700  			return nil, err
   701  		}
   702  		i += size
   703  
   704  		if excludeList != nil {
   705  			if _, ok := excludeList[entry.Sha1]; ok {
   706  				continue
   707  			}
   708  		}
   709  		val[IndexPath(name)] = entry
   710  
   711  		if entry.FileMode == ModeTree && recurse {
   712  			childTree := TreeID(entry.Sha1)
   713  
   714  			children, err := childTree.GetAllObjectsExcept(cl, excludeList, "", recurse, excludeself)
   715  			if err != nil {
   716  				return nil, err
   717  			}
   718  			for child, childval := range children {
   719  				val[IndexPath(name)+"/"+child] = childval
   720  			}
   721  		}
   722  
   723  		if excludeList != nil {
   724  			excludeList[entry.Sha1] = struct{}{}
   725  		}
   726  	}
   727  	if excludeList != nil {
   728  		excludeList[Sha1(t)] = struct{}{}
   729  	}
   730  	return val, nil
   731  }
   732  
   733  func (c CommitID) TreeID(cl *Client) (TreeID, error) {
   734  	obj, err := cl.GetCommitObject(c)
   735  	if err != nil {
   736  		return TreeID{}, err
   737  	}
   738  	treeStr := obj.GetHeader("tree")
   739  	s, err := Sha1FromString(treeStr)
   740  	return TreeID(s), err
   741  }
   742  
   743  // Ensures the Tree implements Treeish
   744  func (t TreeID) TreeID(cl *Client) (TreeID, error) {
   745  	// Validate that it's a tree
   746  	if Sha1(t).Type(cl) != "tree" {
   747  		return TreeID{}, fmt.Errorf("Invalid tree: %v", t)
   748  	}
   749  	return t, nil
   750  }
   751  
   752  // Converts the Tree into an IndexEntries, to simplify comparisons between
   753  // Trees and Indexes
   754  func GetIndexMap(c *Client, t Treeish) (IndexMap, error) {
   755  	indexentries, err := expandGitTreeIntoIndexes(c, t, true, false, false)
   756  	if err != nil {
   757  		return nil, err
   758  	}
   759  	// Create a fake index, to use the GetMap() function
   760  	idx := &Index{Objects: indexentries}
   761  	return idx.GetMap(), nil
   762  }