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