14 // Tree encapsulates a Git tree object.
16 // Entries is the list of entries held by this tree.
20 // Type implements Object.ObjectType by returning the correct object type for
21 // Trees, TreeObjectType.
22 func (t *Tree) Type() ObjectType { return TreeObjectType }
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.
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)
33 var entries []*TreeEntry
35 modes, err := buf.ReadString(' ')
43 modes = strings.TrimSuffix(modes, " ")
45 mode, _ := strconv.ParseInt(modes, 8, 32)
47 fname, err := buf.ReadString('\x00')
52 fname = strings.TrimSuffix(fname, "\x00")
55 if _, err = io.ReadFull(buf, sha[:]); err != nil {
60 entries = append(entries, &TreeEntry{
63 Filemode: int32(mode),
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.
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"
79 for _, entry := range t.Entries {
80 fmode := strconv.FormatInt(int64(entry.Filemode), 8)
82 ne, err := fmt.Fprintf(to, entryTmpl,
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.
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)
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)
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)
117 if other, ok := unseen[key]; ok {
118 entries = append(entries, other)
121 oid := make([]byte, len(entry.Oid))
124 entries = append(entries, &TreeEntry{
125 Filemode: entry.Filemode,
132 // For all the items we haven't replaced into the new set, append them
134 for _, remaining := range unseen {
135 entries = append(entries, remaining)
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.
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".
146 // Trees can be potentially large, so trade this spacial complexity for
147 // an O(n*log(n)) sort.
148 sort.Sort(SubtreeOrder(entries))
150 return &Tree{Entries: entries}
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
156 func (t *Tree) Equal(other *Tree) bool {
157 if (t == nil) != (other == nil) {
162 if len(t.Entries) != len(other.Entries) {
166 for i := 0; i < len(t.Entries); i++ {
168 e2 := other.Entries[i]
178 // TreeEntry encapsulates information about a single tree entry in a tree
180 type TreeEntry struct {
181 // Name is the entry name relative to the tree in which this entry is
184 // Oid is the object ID for this tree entry.
186 // Filemode is the filemode of this tree entry on disk.
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) {
198 return e.Name == other.Name &&
199 bytes.Equal(e.Oid, other.Oid) &&
200 e.Filemode == other.Filemode
205 // Type is the type of entry (either blob: BlobObjectType, or a sub-tree:
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
216 if e.Filemode == 0xe000 {
217 // Mode 0xe000, or a gitlink, has no formal filesystem
218 // (`syscall.S_IF<t>`) equivalent.
220 // Safeguard that catch here, or otherwise panic.
221 return CommitObjectType
223 panic(fmt.Sprintf("git/odb: unknown object type: %o",
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.
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 "/".
237 // See: https://github.com/git/git/blob/v2.13.0/fsck.c#L492-L525 for more
239 type SubtreeOrder []*TreeEntry
241 // Len implements sort.Interface.Len() and return the length of the underlying
243 func (s SubtreeOrder) Len() int { return len(s) }
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] }
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".
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)
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.
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) {
274 if entry.Type() == TreeObjectType {
275 return entry.Name + "/"
277 return entry.Name + "\x00"