github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/git/odb/tree.go (about)

     1  package odb
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  	"syscall"
    12  )
    13  
    14  // Tree encapsulates a Git tree object.
    15  type Tree struct {
    16  	// Entries is the list of entries held by this tree.
    17  	Entries []*TreeEntry
    18  }
    19  
    20  // Type implements Object.ObjectType by returning the correct object type for
    21  // Trees, TreeObjectType.
    22  func (t *Tree) Type() ObjectType { return TreeObjectType }
    23  
    24  // Decode implements Object.Decode and decodes the uncompressed tree being
    25  // read. It returns the number of uncompressed bytes being consumed off of the
    26  // stream, which should be strictly equal to the size given.
    27  //
    28  // If any error was encountered along the way, that will be returned, along with
    29  // the number of bytes read up to that point.
    30  func (t *Tree) Decode(from io.Reader, size int64) (n int, err error) {
    31  	buf := bufio.NewReader(from)
    32  
    33  	var entries []*TreeEntry
    34  	for {
    35  		modes, err := buf.ReadString(' ')
    36  		if err != nil {
    37  			if err == io.EOF {
    38  				break
    39  			}
    40  			return n, err
    41  		}
    42  		n += len(modes)
    43  		modes = strings.TrimSuffix(modes, " ")
    44  
    45  		mode, _ := strconv.ParseInt(modes, 8, 32)
    46  
    47  		fname, err := buf.ReadString('\x00')
    48  		if err != nil {
    49  			return n, err
    50  		}
    51  		n += len(fname)
    52  		fname = strings.TrimSuffix(fname, "\x00")
    53  
    54  		var sha [20]byte
    55  		if _, err = io.ReadFull(buf, sha[:]); err != nil {
    56  			return n, err
    57  		}
    58  		n += 20
    59  
    60  		entries = append(entries, &TreeEntry{
    61  			Name:     fname,
    62  			Oid:      sha[:],
    63  			Filemode: int32(mode),
    64  		})
    65  	}
    66  
    67  	t.Entries = entries
    68  
    69  	return n, nil
    70  }
    71  
    72  // Encode encodes the tree's contents to the given io.Writer, "w". If there was
    73  // any error copying the tree's contents, that error will be returned.
    74  //
    75  // Otherwise, the number of bytes written will be returned.
    76  func (t *Tree) Encode(to io.Writer) (n int, err error) {
    77  	const entryTmpl = "%s %s\x00%s"
    78  
    79  	for _, entry := range t.Entries {
    80  		fmode := strconv.FormatInt(int64(entry.Filemode), 8)
    81  
    82  		ne, err := fmt.Fprintf(to, entryTmpl,
    83  			fmode,
    84  			entry.Name,
    85  			entry.Oid)
    86  
    87  		if err != nil {
    88  			return n, err
    89  		}
    90  
    91  		n = n + ne
    92  	}
    93  	return
    94  }
    95  
    96  // Merge performs a merge operation against the given set of `*TreeEntry`'s by
    97  // either replacing existing tree entries of the same name, or appending new
    98  // entries in sub-tree order.
    99  //
   100  // It returns a copy of the tree, and performs the merge in O(n*log(n)) time.
   101  func (t *Tree) Merge(others ...*TreeEntry) *Tree {
   102  	unseen := make(map[string]*TreeEntry)
   103  
   104  	// Build a cache of name+filemode to *TreeEntry.
   105  	for _, other := range others {
   106  		key := fmt.Sprintf("%s\x00%o", other.Name, other.Filemode)
   107  
   108  		unseen[key] = other
   109  	}
   110  
   111  	// Map the existing entries ("t.Entries") into a new set by either
   112  	// copying an existing entry, or replacing it with a new one.
   113  	entries := make([]*TreeEntry, 0, len(t.Entries))
   114  	for _, entry := range t.Entries {
   115  		key := fmt.Sprintf("%s\x00%o", entry.Name, entry.Filemode)
   116  
   117  		if other, ok := unseen[key]; ok {
   118  			entries = append(entries, other)
   119  			delete(unseen, key)
   120  		} else {
   121  			oid := make([]byte, len(entry.Oid))
   122  			copy(oid, entry.Oid)
   123  
   124  			entries = append(entries, &TreeEntry{
   125  				Filemode: entry.Filemode,
   126  				Name:     entry.Name,
   127  				Oid:      oid,
   128  			})
   129  		}
   130  	}
   131  
   132  	// For all the items we haven't replaced into the new set, append them
   133  	// to the entries.
   134  	for _, remaining := range unseen {
   135  		entries = append(entries, remaining)
   136  	}
   137  
   138  	// Call sort afterwords, as a tradeoff between speed and spacial
   139  	// complexity. As a future point of optimization, adding new elements
   140  	// (see: above) could be done as a linear pass of the "entries" set.
   141  	//
   142  	// In order to do that, we must have a constant-time lookup of both
   143  	// entries in the existing and new sets. This requires building a
   144  	// map[string]*TreeEntry for the given "others" as well as "t.Entries".
   145  	//
   146  	// Trees can be potentially large, so trade this spacial complexity for
   147  	// an O(n*log(n)) sort.
   148  	sort.Sort(SubtreeOrder(entries))
   149  
   150  	return &Tree{Entries: entries}
   151  }
   152  
   153  // Equal returns whether the receiving and given trees are equal, or in other
   154  // words, whether they are represented by the same SHA-1 when saved to the
   155  // object database.
   156  func (t *Tree) Equal(other *Tree) bool {
   157  	if (t == nil) != (other == nil) {
   158  		return false
   159  	}
   160  
   161  	if t != nil {
   162  		if len(t.Entries) != len(other.Entries) {
   163  			return false
   164  		}
   165  
   166  		for i := 0; i < len(t.Entries); i++ {
   167  			e1 := t.Entries[i]
   168  			e2 := other.Entries[i]
   169  
   170  			if !e1.Equal(e2) {
   171  				return false
   172  			}
   173  		}
   174  	}
   175  	return true
   176  }
   177  
   178  // TreeEntry encapsulates information about a single tree entry in a tree
   179  // listing.
   180  type TreeEntry struct {
   181  	// Name is the entry name relative to the tree in which this entry is
   182  	// contained.
   183  	Name string
   184  	// Oid is the object ID for this tree entry.
   185  	Oid []byte
   186  	// Filemode is the filemode of this tree entry on disk.
   187  	Filemode int32
   188  }
   189  
   190  // Equal returns whether the receiving and given TreeEntry instances are
   191  // identical in name, filemode, and OID.
   192  func (e *TreeEntry) Equal(other *TreeEntry) bool {
   193  	if (e == nil) != (other == nil) {
   194  		return false
   195  	}
   196  
   197  	if e != nil {
   198  		return e.Name == other.Name &&
   199  			bytes.Equal(e.Oid, other.Oid) &&
   200  			e.Filemode == other.Filemode
   201  	}
   202  	return true
   203  }
   204  
   205  // Type is the type of entry (either blob: BlobObjectType, or a sub-tree:
   206  // TreeObjectType).
   207  func (e *TreeEntry) Type() ObjectType {
   208  	switch e.Filemode & syscall.S_IFMT {
   209  	case syscall.S_IFREG:
   210  		return BlobObjectType
   211  	case syscall.S_IFDIR:
   212  		return TreeObjectType
   213  	case syscall.S_IFLNK:
   214  		return BlobObjectType
   215  	default:
   216  		if e.Filemode == 0xe000 {
   217  			// Mode 0xe000, or a gitlink, has no formal filesystem
   218  			// (`syscall.S_IF<t>`) equivalent.
   219  			//
   220  			// Safeguard that catch here, or otherwise panic.
   221  			return CommitObjectType
   222  		} else {
   223  			panic(fmt.Sprintf("git/odb: unknown object type: %o",
   224  				e.Filemode))
   225  		}
   226  	}
   227  }
   228  
   229  // SubtreeOrder is an implementation of sort.Interface that sorts a set of
   230  // `*TreeEntry`'s according to "subtree" order. This ordering is required to
   231  // write trees in a correct, readable format to the Git object database.
   232  //
   233  // The format is as follows: entries are sorted lexicographically in byte-order,
   234  // with subtrees (entries of Type() == git/odb.TreeObjectType) being sorted as
   235  // if their `Name` fields ended in a "/".
   236  //
   237  // See: https://github.com/git/git/blob/v2.13.0/fsck.c#L492-L525 for more
   238  // details.
   239  type SubtreeOrder []*TreeEntry
   240  
   241  // Len implements sort.Interface.Len() and return the length of the underlying
   242  // slice.
   243  func (s SubtreeOrder) Len() int { return len(s) }
   244  
   245  // Swap implements sort.Interface.Swap() and swaps the two elements at i and j.
   246  func (s SubtreeOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   247  
   248  // Less implements sort.Interface.Less() and returns whether the element at "i"
   249  // is compared as "less" than the element at "j". In other words, it returns if
   250  // the element at "i" should be sorted ahead of that at "j".
   251  //
   252  // It performs this comparison in lexicographic byte-order according to the
   253  // rules above (see SubtreeOrder).
   254  func (s SubtreeOrder) Less(i, j int) bool {
   255  	return s.Name(i) < s.Name(j)
   256  }
   257  
   258  // Name returns the name for a given entry indexed at "i", which is a C-style
   259  // string ('\0' terminated unless it's a subtree), optionally terminated with
   260  // '/' if it's a subtree.
   261  //
   262  // This is done because '/' sorts ahead of '\0', and is compatible with the
   263  // tree order in upstream Git.
   264  func (s SubtreeOrder) Name(i int) string {
   265  	if i < 0 || i >= len(s) {
   266  		return ""
   267  	}
   268  
   269  	entry := s[i]
   270  	if entry == nil {
   271  		return ""
   272  	}
   273  
   274  	if entry.Type() == TreeObjectType {
   275  		return entry.Name + "/"
   276  	}
   277  	return entry.Name + "\x00"
   278  }