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