-
Notifications
You must be signed in to change notification settings - Fork 1
/
repo.go
126 lines (98 loc) · 3.17 KB
/
repo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package main
import (
"bufio"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"github.com/git-lfs/gitobj"
)
// Visit repository and return directed acyclic graph
func Visit(path string) Graph {
var nodes = []Node{}
var edges = []Edge{}
nodes, edges = visitObjects(path, nodes, edges)
nodes, edges = visitRefs(path, nodes, edges)
nodes, edges = visitHead(path, nodes, edges)
return Graph{Nodes: nodes, Edges: edges}
}
func addEdgesFromTree(id string, tree *gitobj.Tree) (edges []Edge) {
for _, entry := range tree.Entries {
edges = append(edges, Edge{From: id, To: hex.EncodeToString(entry.Oid)})
}
return
}
func addEdgesFromCommit(id string, commit *gitobj.Commit) (edges []Edge) {
edges = append(edges, Edge{From: id, To: hex.EncodeToString(commit.TreeID)})
for _, parent := range commit.ParentIDs {
edges = append(edges, Edge{From: id, To: hex.EncodeToString(parent)})
}
return
}
func visitRefs(root string, nodes []Node, edges []Edge) ([]Node, []Edge) {
filepath.Walk(root+"/refs/heads", func(path string, info os.FileInfo, e error) error {
if e != nil {
return e
}
if info.Mode().IsRegular() {
nodes, edges = visitRef(root, path, nodes, edges, "head")
}
return nil
})
var files, _ = filepath.Glob(root + "/refs/remotes/*/*")
for _, file := range files {
nodes, edges = visitRef(root, file, nodes, edges, "remote")
}
files, _ = filepath.Glob(root + "/refs/tags/*")
for _, file := range files {
nodes, edges = visitRef(root, file, nodes, edges, "tag")
}
return nodes, edges
}
func visitRef(path string, file string, nodes []Node, edges []Edge, t string) ([]Node, []Edge) {
id := refID(file[len(path)+1:])
nodes = append(nodes, Node{ID: id, Type: t})
to := refID(readFirstLine(file))
edges = append(edges, Edge{From: id, To: to})
return nodes, edges
}
func readFirstLine(path string) string {
file, _ := os.Open(path)
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Scan()
return scanner.Text()
}
func visitObjects(path string, nodes []Node, edges []Edge) ([]Node, []Edge) {
repo, _ := gitobj.FromFilesystem(filepath.Join(path, "objects"), "")
defer repo.Close()
objects, _ := filepath.Glob(path + "/objects/??/*")
for _, object := range objects {
node, e, _ := visitObject(repo, objectID(object))
nodes = append(nodes, node)
edges = append(edges, e...)
}
return nodes, edges
}
func visitObject(repo *gitobj.ObjectDatabase, id string) (node Node, edges []Edge, err error) {
sha, _ := hex.DecodeString(id)
object, err := repo.Object(sha)
if err != nil {
return Node{}, edges, err
}
switch v := object.(type) {
case *gitobj.Tree:
edges = append(edges, addEdgesFromTree(id, v)...)
return Node{Type: "tree", ID: id}, edges, nil
case *gitobj.Commit:
edges = append(edges, addEdgesFromCommit(id, v)...)
return Node{Type: "commit", ID: id}, edges, nil
case *gitobj.Blob:
return Node{Type: "blob", ID: id}, edges, nil
}
return Node{}, edges, fmt.Errorf("Unkown object type for sha-ish %s", id)
}
func visitHead(path string, nodes []Node, edges []Edge) ([]Node, []Edge) {
to := refID(readFirstLine(filepath.Join(path, "HEAD")))
return append(nodes, Node{ID: "HEAD", Type: "HEAD"}), append(edges, Edge{From: "HEAD", To: to})
}