-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgit.go
More file actions
185 lines (171 loc) · 4.96 KB
/
git.go
File metadata and controls
185 lines (171 loc) · 4.96 KB
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package main
import (
"errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)
type Tree struct {
Repo string
Id string
Name string
Trees []*Tree
Blobs []*Blob
}
func (t *Tree) String() string {
return fmt.Sprintf("tree: %v %v", t.Id[:8], t.Name)
}
type Blob struct {
Repo string
Id string
Name string
}
func (b *Blob) String() string {
return fmt.Sprintf("blob: %v %v", b.Id[:8], b.Name)
}
// gitDir checks whether the _d_ is git directory, or not.
// if not found the path, it will return false.
// any other error makes it fatal.
func gitDir(d string) bool {
cmd := exec.Command("git", "rev-parse", "--git-dir")
cmd.Dir = d
out, err := cmd.CombinedOutput()
if err != nil {
if os.IsNotExist(err) {
return false
}
log.Fatalf("(%v) %s", err, out)
}
return string(out) == ".\n"
}
func lastUpdate(repo string) string {
cmd := exec.Command("git", "log", "--pretty=format:%ar", "-1")
cmd.Dir = repo
out, err := cmd.CombinedOutput()
if err != nil {
return ""
}
return string(out)
}
// commitTree find tree id from the commit id.
// the commit id _c_ will always rev-parsed.
func commitTree(repo, c string) (string, error) {
cmd := exec.Command("git", "cat-file", "-t", c)
cmd.Dir = filepath.Join(repoRoot, repo)
out, err := cmd.CombinedOutput()
if err != nil {
return "", errors.New(fmt.Sprintf("(%v) %s", err, out))
}
if string(out) != "commit\n" {
return "", errors.New(fmt.Sprintf("%v is not a commit id", c))
}
cmd = exec.Command("git", "rev-parse", c)
cmd.Dir = filepath.Join(repoRoot, repo)
out, err = cmd.CombinedOutput()
if err != nil {
return "", errors.New(fmt.Sprintf("(%v) %s", err, out))
}
id := out[:len(out)-1] // strip "\n"
cmd = exec.Command("git", "cat-file", "-p", string(id))
cmd.Dir = filepath.Join(repoRoot, repo)
out, err = cmd.CombinedOutput()
if err != nil {
return "", errors.New(fmt.Sprintf("(%v) %s", err, out))
}
// find tree object id
t := strings.Split(string(out), "\n")[0]
if !strings.HasPrefix(t, "tree ") {
return "", errors.New(`commit object content not starts with "tree "`)
}
return strings.Split(t, " ")[1], nil
}
// gitTree returns parsed *Tree object of given tree id.
// the *Tree object contains all the child data.
func gitTree(repo, t string, maxdepth int) (*Tree, error) {
cmd := exec.Command("git", "cat-file", "-t", t)
cmd.Dir = filepath.Join(repoRoot, repo)
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("(%v) %s", err, out)
}
if string(out) != "tree\n" {
return nil, fmt.Errorf("%v is not a tree id of %v", t, repo)
}
return parseTree(repo, t, "", 1, maxdepth), nil
}
// parseTree parses tree hierarchy with given tree id until reaches the max depth
// If the maxdepth is negative number, it will parse all the tree.
// It will return results with a top tree.
//
// TODO: what is proper procedure if the maxdepth is 0?
func parseTree(repo, id string, name string, curdepth, maxdepth int) *Tree {
top := &Tree{Repo: repo, Id: id, Name: name}
cmd := exec.Command("git", "cat-file", "-p", string(id))
cmd.Dir = filepath.Join(repoRoot, repo)
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("(%v) %s", err, out)
}
for _, l := range strings.Split(string(out), "\n") {
// the line looks like this.
// 100644 blob e6e777ec163436193a336a561cfbf57c3b06ccaa README.md
// 100644 tree 8094086457b9e41a0c10ee3fef479056542da579 someDir
if l == "" {
// maybe last line.
continue
}
ll := strings.Split(l, "\t")
cinfos := strings.Split(ll[0], " ")
ctype := cinfos[1]
cid := cinfos[2]
cname := ll[1]
if ctype == "tree" {
if maxdepth < 0 || curdepth < maxdepth {
top.Trees = append(top.Trees, parseTree(repo, cid, cname, curdepth+1, maxdepth))
}
} else {
top.Blobs = append(top.Blobs, &Blob{Repo: repo, Id: cid, Name: cname})
}
}
return top
}
func blobContent(repo, b string) ([]byte, error) {
cmd := exec.Command("git", "cat-file", "-t", b)
cmd.Dir = filepath.Join(repoRoot, repo)
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("(%v) %s", err, out)
}
if string(out) != "blob\n" {
return nil, fmt.Errorf("repo '%v' don't have blob '%v'", repo, b)
}
cmd = exec.Command("git", "cat-file", "-p", b)
cmd.Dir = filepath.Join(repoRoot, repo)
c, _ := cmd.Output()
return c, nil
}
// initialCommitID will return initial commit id of the repo.
// it will return empty string if the repo don't have any commit yet.
func initialCommitID(repo string) string {
cmd := exec.Command("git", "rev-list", "--all", "--reverse")
cmd.Dir = filepath.Join(repoRoot, repo)
out, err := cmd.CombinedOutput()
if err != nil {
log.Print("%v: (%v) %s", cmd, err, out)
return ""
}
return strings.Split(string(out), "\n")[0]
}
func currentBranch(repo string) string {
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
cmd.Dir = filepath.Join(repoRoot, repo)
out, err := cmd.CombinedOutput()
if err != nil {
log.Print("%v: (%v) %s", cmd, err, out)
return ""
}
return strings.TrimSuffix(string(out), "\n")
}