1package repository
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "strconv"
8 "strings"
9)
10
11type TreeEntry struct {
12 ObjectType ObjectType
13 Hash Hash
14 Name string
15}
16
17type ObjectType int
18
19const (
20 Unknown ObjectType = iota
21 Blob // regular file (100644)
22 Tree // directory (040000)
23 Executable // executable file (100755)
24 Symlink // symbolic link (120000)
25 Submodule // git submodule (160000)
26)
27
28func ParseTreeEntry(line string) (TreeEntry, error) {
29 fields := strings.Fields(line)
30
31 if len(fields) < 4 {
32 return TreeEntry{}, fmt.Errorf("Invalid input to parse as a TreeEntry")
33 }
34
35 objType, err := ParseObjectType(fields[0], fields[1])
36
37 if err != nil {
38 return TreeEntry{}, err
39 }
40
41 hash := Hash(fields[2])
42 name := strings.Join(fields[3:], "")
43
44 return TreeEntry{
45 ObjectType: objType,
46 Hash: hash,
47 Name: name,
48 }, nil
49}
50
51// Format the entry as a git ls-tree compatible line
52func (entry TreeEntry) Format() string {
53 return fmt.Sprintf("%s %s\t%s\n", entry.ObjectType.Format(), entry.Hash, entry.Name)
54}
55
56func (ot ObjectType) Format() string {
57 switch ot {
58 case Blob:
59 return "100644 blob"
60 case Tree:
61 return "040000 tree"
62 case Executable:
63 return "100755 blob"
64 case Symlink:
65 return "120000 blob"
66 case Submodule:
67 return "160000 commit"
68 default:
69 panic("Unknown git object type")
70 }
71}
72
73func (ot ObjectType) MarshalGQL(w io.Writer) {
74 switch ot {
75 case Tree:
76 fmt.Fprint(w, strconv.Quote("TREE"))
77 case Blob, Executable:
78 fmt.Fprint(w, strconv.Quote("BLOB"))
79 case Symlink:
80 fmt.Fprint(w, strconv.Quote("SYMLINK"))
81 case Submodule:
82 fmt.Fprint(w, strconv.Quote("SUBMODULE"))
83 default:
84 panic(fmt.Sprintf("unknown ObjectType value %d", int(ot)))
85 }
86}
87
88func (ot *ObjectType) UnmarshalGQL(v any) error {
89 str, ok := v.(string)
90 if !ok {
91 return fmt.Errorf("enums must be strings")
92 }
93 switch str {
94 case "TREE":
95 *ot = Tree
96 case "BLOB":
97 *ot = Blob
98 case "SYMLINK":
99 *ot = Symlink
100 case "SUBMODULE":
101 *ot = Submodule
102 default:
103 return fmt.Errorf("%q is not a valid ObjectType", str)
104 }
105 return nil
106}
107
108func ParseObjectType(mode, objType string) (ObjectType, error) {
109 switch {
110 case mode == "100644" && objType == "blob":
111 return Blob, nil
112 case mode == "040000" && objType == "tree":
113 return Tree, nil
114 case mode == "100755" && objType == "blob":
115 return Executable, nil
116 case mode == "120000" && objType == "blob":
117 return Symlink, nil
118 case mode == "160000" && objType == "commit":
119 return Submodule, nil
120 default:
121 return Unknown, fmt.Errorf("Unknown git object type %s %s", mode, objType)
122 }
123}
124
125func prepareTreeEntries(entries []TreeEntry) bytes.Buffer {
126 var buffer bytes.Buffer
127
128 for _, entry := range entries {
129 buffer.WriteString(entry.Format())
130 }
131
132 return buffer
133}
134
135func readTreeEntries(s string) ([]TreeEntry, error) {
136 split := strings.Split(strings.TrimSpace(s), "\n")
137
138 casted := make([]TreeEntry, len(split))
139 for i, line := range split {
140 if line == "" {
141 continue
142 }
143
144 entry, err := ParseTreeEntry(line)
145
146 if err != nil {
147 return nil, err
148 }
149
150 casted[i] = entry
151 }
152
153 return casted, nil
154}
155
156// SearchTreeEntry search a TreeEntry by name from an array
157func SearchTreeEntry(entries []TreeEntry, name string) (TreeEntry, bool) {
158 for _, entry := range entries {
159 if entry.Name == name {
160 return entry, true
161 }
162 }
163 return TreeEntry{}, false
164}