Monorepo for Tangled
tangled.org
1package git
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "path"
8 "time"
9
10 "github.com/go-git/go-git/v5/plumbing/filemode"
11 "github.com/go-git/go-git/v5/plumbing/object"
12 "tangled.org/core/types"
13)
14
15func (g *GitRepo) FileTree(ctx context.Context, path string) ([]types.NiceTree, error) {
16 c, err := g.r.CommitObject(g.h)
17 if err != nil {
18 return nil, fmt.Errorf("commit object: %w", err)
19 }
20
21 files := []types.NiceTree{}
22 tree, err := c.Tree()
23 if err != nil {
24 return nil, fmt.Errorf("file tree: %w", err)
25 }
26
27 if path == "" {
28 files = g.makeNiceTree(ctx, tree, "")
29 } else {
30 o, err := tree.FindEntry(path)
31 if err != nil {
32 return nil, err
33 }
34
35 if !o.Mode.IsFile() {
36 subtree, err := tree.Tree(path)
37 if err != nil {
38 return nil, err
39 }
40
41 files = g.makeNiceTree(ctx, subtree, path)
42 }
43 }
44
45 return files, nil
46}
47
48func (g *GitRepo) makeNiceTree(ctx context.Context, subtree *object.Tree, parent string) []types.NiceTree {
49 nts := []types.NiceTree{}
50
51 entries := make([]string, len(subtree.Entries))
52 for i, e := range subtree.Entries {
53 entries[i] = e.Name
54 }
55
56 lastCommitDir := lastCommitDir{
57 dir: parent,
58 entries: entries,
59 }
60
61 times, err := g.lastCommitDirIn(ctx, lastCommitDir, 300*time.Millisecond)
62 if err != nil {
63 return nts
64 }
65
66 for _, e := range subtree.Entries {
67 var sz int64
68 blob, err := object.GetBlob(g.r.Storer, e.Hash)
69 if err == nil {
70 sz = blob.Size
71 }
72 fpath := path.Join(parent, e.Name)
73
74 var lastCommit *types.LastCommitInfo
75 if t, ok := times[fpath]; ok {
76 lastCommit = &types.LastCommitInfo{
77 Hash: t.hash,
78 Message: t.message,
79 When: t.when,
80 }
81 }
82
83 nts = append(nts, types.NiceTree{
84 Name: e.Name,
85 Mode: e.Mode.String(),
86 Size: sz,
87 LastCommit: lastCommit,
88 })
89
90 }
91
92 return nts
93}
94
95var (
96 TerminateWalk error = errors.New("terminate walk")
97)
98
99type callback = func(node object.TreeEntry, parent *object.Tree, fullPath string) error
100
101func (g *GitRepo) Walk(
102 ctx context.Context,
103 root string,
104 cb callback,
105) error {
106 c, err := g.r.CommitObject(g.h)
107 if err != nil {
108 return fmt.Errorf("commit object: %w", err)
109 }
110
111 tree, err := c.Tree()
112 if err != nil {
113 return fmt.Errorf("file tree: %w", err)
114 }
115
116 subtree := tree
117 if root != "" {
118 subtree, err = tree.Tree(root)
119 if err != nil {
120 return fmt.Errorf("sub tree: %w", err)
121 }
122 }
123
124 return g.walkHelper(ctx, root, subtree, cb)
125}
126
127func (g *GitRepo) walkHelper(
128 ctx context.Context,
129 root string,
130 currentTree *object.Tree,
131 cb callback,
132) error {
133 for _, e := range currentTree.Entries {
134 // check if context hits deadline before processing
135 select {
136 case <-ctx.Done():
137 return ctx.Err()
138 default:
139 }
140
141 if e.Mode.IsFile() {
142 if err := cb(e, currentTree, root); errors.Is(err, TerminateWalk) {
143 return err
144 }
145 }
146
147 // e is a directory
148 if e.Mode == filemode.Dir {
149 subtree, err := currentTree.Tree(e.Name)
150 if err != nil {
151 return fmt.Errorf("sub tree %s: %w", e.Name, err)
152 }
153
154 fullPath := path.Join(root, e.Name)
155
156 err = g.walkHelper(ctx, fullPath, subtree, cb)
157 if err != nil {
158 return err
159 }
160 }
161 }
162
163 return nil
164}