forked from
tangled.org/core
Monorepo for Tangled — https://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 times, err := g.calculateCommitTimeIn(ctx, subtree, parent, 2*time.Second)
52 if err != nil {
53 return nts
54 }
55
56 for _, e := range subtree.Entries {
57 sz, _ := subtree.Size(e.Name)
58 fpath := path.Join(parent, e.Name)
59
60 var lastCommit *types.LastCommitInfo
61 if t, ok := times[fpath]; ok {
62 lastCommit = &types.LastCommitInfo{
63 Hash: t.hash,
64 Message: t.message,
65 When: t.when,
66 }
67 }
68
69 nts = append(nts, types.NiceTree{
70 Name: e.Name,
71 Mode: e.Mode.String(),
72 Size: sz,
73 LastCommit: lastCommit,
74 })
75
76 }
77
78 return nts
79}
80
81var (
82 TerminateWalk error = errors.New("terminate walk")
83)
84
85type callback = func(node object.TreeEntry, parent *object.Tree, fullPath string) error
86
87func (g *GitRepo) Walk(
88 ctx context.Context,
89 root string,
90 cb callback,
91) error {
92 c, err := g.r.CommitObject(g.h)
93 if err != nil {
94 return fmt.Errorf("commit object: %w", err)
95 }
96
97 tree, err := c.Tree()
98 if err != nil {
99 return fmt.Errorf("file tree: %w", err)
100 }
101
102 subtree := tree
103 if root != "" {
104 subtree, err = tree.Tree(root)
105 if err != nil {
106 return fmt.Errorf("sub tree: %w", err)
107 }
108 }
109
110 return g.walkHelper(ctx, root, subtree, cb)
111}
112
113func (g *GitRepo) walkHelper(
114 ctx context.Context,
115 root string,
116 currentTree *object.Tree,
117 cb callback,
118) error {
119 for _, e := range currentTree.Entries {
120 // check if context hits deadline before processing
121 select {
122 case <-ctx.Done():
123 return ctx.Err()
124 default:
125 }
126
127 if e.Mode.IsFile() {
128 if err := cb(e, currentTree, root); errors.Is(err, TerminateWalk) {
129 return err
130 }
131 }
132
133 // e is a directory
134 if e.Mode == filemode.Dir {
135 subtree, err := currentTree.Tree(e.Name)
136 if err != nil {
137 return fmt.Errorf("sub tree %s: %w", e.Name, err)
138 }
139
140 fullPath := path.Join(root, e.Name)
141
142 err = g.walkHelper(ctx, fullPath, subtree, cb)
143 if err != nil {
144 return err
145 }
146 }
147 }
148
149 return nil
150}