Skip to content
This repository was archived by the owner on Apr 12, 2019. It is now read-only.

Commit 827f97a

Browse files
ethantkoeniglunny
authored andcommitted
Fix tree entry parsing (#110)
* Fix tree entry parsing * nits * populate TreeEntry.ptree
1 parent 6798d0f commit 827f97a

File tree

3 files changed

+140
-82
lines changed

3 files changed

+140
-82
lines changed

parse.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2018 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package git
6+
7+
import (
8+
"bytes"
9+
"fmt"
10+
"strconv"
11+
)
12+
13+
// ParseTreeEntries parses the output of a `git ls-tree` command.
14+
func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
15+
return parseTreeEntries(data, nil)
16+
}
17+
18+
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
19+
entries := make([]*TreeEntry, 0, 10)
20+
for pos := 0; pos < len(data); {
21+
// expect line to be of the form "<mode> <type> <sha>\t<filename>"
22+
entry := new(TreeEntry)
23+
entry.ptree = ptree
24+
if pos+6 > len(data) {
25+
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
26+
}
27+
switch string(data[pos : pos+6]) {
28+
case "100644":
29+
entry.mode = EntryModeBlob
30+
entry.Type = ObjectBlob
31+
pos += 12 // skip over "100644 blob "
32+
case "100755":
33+
entry.mode = EntryModeExec
34+
entry.Type = ObjectBlob
35+
pos += 12 // skip over "100755 blob "
36+
case "120000":
37+
entry.mode = EntryModeSymlink
38+
entry.Type = ObjectBlob
39+
pos += 12 // skip over "120000 blob "
40+
case "160000":
41+
entry.mode = EntryModeCommit
42+
entry.Type = ObjectCommit
43+
pos += 14 // skip over "160000 object "
44+
case "040000":
45+
entry.mode = EntryModeTree
46+
entry.Type = ObjectTree
47+
pos += 12 // skip over "040000 tree "
48+
default:
49+
return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6]))
50+
}
51+
52+
if pos+40 > len(data) {
53+
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
54+
}
55+
id, err := NewIDFromString(string(data[pos : pos+40]))
56+
if err != nil {
57+
return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
58+
}
59+
entry.ID = id
60+
pos += 41 // skip over sha and trailing space
61+
62+
end := pos + bytes.IndexByte(data[pos:], '\n')
63+
if end < pos {
64+
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
65+
}
66+
67+
// In case entry name is surrounded by double quotes(it happens only in git-shell).
68+
if data[pos] == '"' {
69+
entry.name, err = strconv.Unquote(string(data[pos:end]))
70+
if err != nil {
71+
return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
72+
}
73+
} else {
74+
entry.name = string(data[pos:end])
75+
}
76+
77+
pos = end + 1
78+
entries = append(entries, entry)
79+
}
80+
return entries, nil
81+
}

parse_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2018 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package git
6+
7+
import (
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestParseTreeEntries(t *testing.T) {
14+
testCases := []struct {
15+
Input string
16+
Expected []*TreeEntry
17+
}{
18+
{
19+
Input: "",
20+
Expected: []*TreeEntry{},
21+
},
22+
{
23+
Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c\texample/file2.txt\n",
24+
Expected: []*TreeEntry{
25+
{
26+
mode: EntryModeBlob,
27+
Type: ObjectBlob,
28+
ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
29+
name: "example/file2.txt",
30+
},
31+
},
32+
},
33+
{
34+
Input: "120000 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c\t\"example/\\n.txt\"\n" +
35+
"040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8\texample\n",
36+
Expected: []*TreeEntry{
37+
{
38+
ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"),
39+
Type: ObjectBlob,
40+
mode: EntryModeSymlink,
41+
name: "example/\n.txt",
42+
},
43+
{
44+
ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"),
45+
Type: ObjectTree,
46+
mode: EntryModeTree,
47+
name: "example",
48+
},
49+
},
50+
},
51+
}
52+
53+
for _, testCase := range testCases {
54+
entries, err := ParseTreeEntries([]byte(testCase.Input))
55+
assert.NoError(t, err)
56+
assert.EqualValues(t, testCase.Expected, entries)
57+
}
58+
}

tree.go

+1-82
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
package git
66

77
import (
8-
"bytes"
9-
"fmt"
108
"strings"
119
)
1210

@@ -30,84 +28,6 @@ func NewTree(repo *Repository, id SHA1) *Tree {
3028
}
3129
}
3230

33-
var escapeChar = []byte("\\")
34-
35-
// UnescapeChars reverses escaped characters.
36-
func UnescapeChars(in []byte) []byte {
37-
if bytes.Index(in, escapeChar) == -1 {
38-
return in
39-
}
40-
41-
endIdx := len(in) - 1
42-
isEscape := false
43-
out := make([]byte, 0, endIdx+1)
44-
for i := range in {
45-
if in[i] == '\\' && !isEscape {
46-
isEscape = true
47-
continue
48-
}
49-
isEscape = false
50-
out = append(out, in[i])
51-
}
52-
return out
53-
}
54-
55-
// parseTreeData parses tree information from the (uncompressed) raw
56-
// data from the tree object.
57-
func parseTreeData(tree *Tree, data []byte) ([]*TreeEntry, error) {
58-
entries := make([]*TreeEntry, 0, 10)
59-
l := len(data)
60-
pos := 0
61-
for pos < l {
62-
entry := new(TreeEntry)
63-
entry.ptree = tree
64-
step := 6
65-
switch string(data[pos : pos+step]) {
66-
case "100644":
67-
entry.mode = EntryModeBlob
68-
entry.Type = ObjectBlob
69-
case "100755":
70-
entry.mode = EntryModeExec
71-
entry.Type = ObjectBlob
72-
case "120000":
73-
entry.mode = EntryModeSymlink
74-
entry.Type = ObjectBlob
75-
case "160000":
76-
entry.mode = EntryModeCommit
77-
entry.Type = ObjectCommit
78-
79-
step = 8
80-
case "040000":
81-
entry.mode = EntryModeTree
82-
entry.Type = ObjectTree
83-
default:
84-
return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+step]))
85-
}
86-
pos += step + 6 // Skip string type of entry type.
87-
88-
step = 40
89-
id, err := NewIDFromString(string(data[pos : pos+step]))
90-
if err != nil {
91-
return nil, err
92-
}
93-
entry.ID = id
94-
pos += step + 1 // Skip half of SHA1.
95-
96-
step = bytes.IndexByte(data[pos:], '\n')
97-
98-
// In case entry name is surrounded by double quotes(it happens only in git-shell).
99-
if data[pos] == '"' {
100-
entry.name = string(UnescapeChars(data[pos+1 : pos+step-1]))
101-
} else {
102-
entry.name = string(data[pos : pos+step])
103-
}
104-
105-
pos += step + 1
106-
entries = append(entries, entry)
107-
}
108-
return entries, nil
109-
}
110-
11131
// SubTree get a sub tree by the sub dir path
11232
func (t *Tree) SubTree(rpath string) (*Tree, error) {
11333
if len(rpath) == 0 {
@@ -142,12 +62,11 @@ func (t *Tree) ListEntries() (Entries, error) {
14262
if t.entriesParsed {
14363
return t.entries, nil
14464
}
145-
t.entriesParsed = true
14665

14766
stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path)
14867
if err != nil {
14968
return nil, err
15069
}
151-
t.entries, err = parseTreeData(t, stdout)
70+
t.entries, err = parseTreeEntries(stdout, t)
15271
return t.entries, err
15372
}

0 commit comments

Comments
 (0)