-
Notifications
You must be signed in to change notification settings - Fork 3
/
git.go
172 lines (151 loc) · 3.97 KB
/
git.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
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
package main
import (
"fmt"
"html/template"
"log"
"strings"
"sync"
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/storage/memory"
)
// Repository wraps a go-git Repository object.
type Repository struct {
R *git.Repository
// Shortest possible but still unique commit hash length
UniqueLen int
// Not modified after initialization
ShortHashToCommit map[string]*object.Commit
// Separate cache for anything that wasn't included in go-git's
// CommitObjects() iterator, for whatever reason
FullRevToCommit map[string]*object.Commit
FullRevToCommitLock sync.RWMutex
}
type eHashTooShort struct{}
func (e eHashTooShort) Error() string {
return ""
}
func testShortLength(r *git.Repository, sl int) map[string]*object.Commit {
ret := make(map[string]*object.Commit)
commitIter, err := r.CommitObjects()
FatalIf(err)
err = commitIter.ForEach(func(c *object.Commit) error {
curFull := c.Hash
curShort := curFull.String()[:sl]
if _, ok := ret[curShort]; ok {
return eHashTooShort{}
}
ret[curShort] = c
return nil
})
if _, ok := err.(eHashTooShort); ok {
return nil
}
return ret
}
// NewRepository calls optimalClone(url), and wraps the result into our
// Repository type.
func NewRepository(url string) (ret Repository) {
r, err := optimalClone(url)
if err != nil {
log.Fatal(err)
}
log.Printf("Done.")
testlen := 0
for testlen < 40 && ret.ShortHashToCommit == nil {
testlen++
ret.ShortHashToCommit = testShortLength(r, testlen)
}
log.Printf("shortest unique hash length is %v characters", testlen)
ret.FullRevToCommit = make(map[string]*object.Commit)
ret.UniqueLen = testlen
ret.R = r
return
}
// GetCommit returns a potential Commit object for the given rev.
func (r *Repository) GetCommit(rev string) (*object.Commit, error) {
if len(rev) >= r.UniqueLen {
if commit, ok := r.ShortHashToCommit[rev[:r.UniqueLen]]; ok {
// Verify that the rest of the hash matches what we expect, and
// fall through to full rev resolution otherwise.
if rev == commit.Hash.String()[:len(rev)] {
return commit, nil
}
}
}
r.FullRevToCommitLock.RLock()
commit, ok := r.FullRevToCommit[rev]
r.FullRevToCommitLock.RUnlock()
if ok {
return commit, nil
}
hash, err := r.R.ResolveRevision(plumbing.Revision(rev))
if err != nil {
return nil, err
}
commit, err = r.R.CommitObject(*hash)
if err != nil {
return nil, err
}
r.FullRevToCommitLock.Lock()
r.FullRevToCommit[rev] = commit
r.FullRevToCommitLock.Unlock()
return commit, err
}
// GetLogAt returns an in-order log iterator for the given rev.
func (r *Repository) GetLogAt(c *object.Commit) (object.CommitIter, error) {
return r.R.Log(&git.LogOptions{
From: c.Hash, Order: git.LogOrderCommitterTime,
})
}
type commitInfo struct {
Time time.Time
Desc string
Hash plumbing.Hash
}
func makeCommitInfo(c *object.Commit) commitInfo {
desc := c.Message
if i := strings.IndexByte(desc, '\n'); i > 0 {
desc = desc[:i]
}
return commitInfo{
Time: c.Author.When,
Desc: desc,
Hash: c.Hash,
}
}
// CommitLink returns a nicely formatted link to rev in the ReC98 repository.
func CommitLink(rev string) template.HTML {
revEsc := template.HTMLEscapeString(rev)
return template.HTML(fmt.Sprintf(
`<a href="https://github.com/nmlgc/ReC98/commit/%s"><code>%s</code></a>`,
revEsc, revEsc,
))
}
func commits(iter object.CommitIter) chan commitInfo {
ret := make(chan commitInfo)
go func() {
err := iter.ForEach(func(c *object.Commit) error {
ret <- makeCommitInfo(c)
return nil
})
close(ret)
if err != nil {
log.Panicln(err)
}
}()
return ret
}
func optimalClone(url string) (*git.Repository, error) {
if strings.HasPrefix(url, "https://") {
// TODO: This takes 3 GB of memory?!
log.Printf("Cloning %s to memory...\n", url)
return git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
URL: url,
})
}
log.Printf("Opening %s...\n", url)
return git.PlainOpen(url)
}