Repository navigation
Expand file tree
/
Copy pathlastcommit.go
More file actions
162 lines (150 loc) · 4.02 KB
/
Copy pathlastcommit.go
File metadata and controls
162 lines (150 loc) · 4.02 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
package hglib
import (
"fmt"
"strings"
"sync"
)
// ResolveLastCommitAtDir returns, for every direct entry of dir at c, the node
// id of the newest changeset that touched it, looking only at the ancestors of
// c. For a file entry that is the newest changeset that changed the path; for
// a directory entry it is the newest changeset that changed anything below it.
//
// The answer is a pure function of (repository, changeset, directory), so it is
// memoized. Merge changesets are skipped, mirroring how git log --name-only
// behaves: the change came in through one of the parents, and that parent is
// part of the same walk.
func (r *LocalHgRepository) ResolveLastCommitAtDir(c *CommitObject, dir string) (map[string]string, error) {
if c == nil {
return nil, fmt.Errorf("no changeset given")
}
if c.Rev < 0 || c.Rev >= r.Changelog.Len() {
return nil, fmt.Errorf("changeset %s is not part of this repository", c.Id)
}
dir = normalizeManifestPath(dir)
key := r.RootPath + "\x00" + c.Id + "\x00" + dir
if v, ok := lastCommitCacheGet(key); ok {
return copyStringMap(v), nil
}
// A previous process may have left the listing on disk.
if v, ok := r.loadLastCommitFromDisk(c.Id, dir); ok {
lastCommitCachePut(key, v)
return copyStringMap(v), nil
}
m, err := r.GetManifest(c)
if err != nil {
return nil, err
}
children := m.Children(dir)
if len(children) == 0 {
return map[string]string{}, nil
}
want := make(map[string]bool, len(children))
for _, item := range children {
want[item.Name] = true
}
ancestors := make([]bool, c.Rev+1)
stack := make([]int, 0, 16)
stack = append(stack, c.Rev)
for len(stack) > 0 {
rev := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if rev < 0 || rev >= len(ancestors) || ancestors[rev] {
continue
}
ancestors[rev] = true
cc, err := r.GetCommitByRev(rev)
if err != nil {
return nil, err
}
if cc.P1 >= 0 {
stack = append(stack, cc.P1)
}
if cc.P2 >= 0 {
stack = append(stack, cc.P2)
}
}
res := make(map[string]string, len(children))
for rev := c.Rev; rev >= 0 && len(res) < len(children); rev-- {
if !ancestors[rev] {
continue
}
cc, err := r.GetCommitByRev(rev)
if err != nil {
return nil, err
}
if cc.P2 >= 0 {
continue
}
for _, file := range cc.Files {
name := lastCommitDirectChild(file, dir)
if name == "" || !want[name] {
continue
}
if _, ok := res[name]; !ok {
res[name] = cc.Id
}
}
}
r.saveLastCommitToDisk(c.Id, dir, res)
lastCommitCachePut(key, res)
return copyStringMap(res), nil
}
func lastCommitDirectChild(p, dir string) string {
p = strings.TrimPrefix(p, "/")
if dir != "" {
if !strings.HasPrefix(p, dir+"/") {
return ""
}
p = p[len(dir)+1:]
}
if p == "" {
return ""
}
if i := strings.IndexByte(p, '/'); i >= 0 {
p = p[:i]
}
return p
}
func copyStringMap(m map[string]string) map[string]string {
res := make(map[string]string, len(m))
for k, v := range m {
res[k] = v
}
return res
}
const lastCommitCacheCapacity = 512
var lastCommitCache = struct {
mu sync.Mutex
items map[string]map[string]string
order []string
}{
items: make(map[string]map[string]string),
}
func lastCommitCacheGet(key string) (map[string]string, bool) {
lastCommitCache.mu.Lock()
defer lastCommitCache.mu.Unlock()
v, ok := lastCommitCache.items[key]
return v, ok
}
func lastCommitCachePut(key string, value map[string]string) {
lastCommitCache.mu.Lock()
defer lastCommitCache.mu.Unlock()
if _, ok := lastCommitCache.items[key]; ok {
lastCommitCache.items[key] = value
return
}
lastCommitCache.items[key] = value
lastCommitCache.order = append(lastCommitCache.order, key)
for len(lastCommitCache.order) > lastCommitCacheCapacity {
old := lastCommitCache.order[0]
lastCommitCache.order = lastCommitCache.order[1:]
delete(lastCommitCache.items, old)
}
}
// ClearLastCommitCache drops every memoized directory listing.
func ClearLastCommitCache() {
lastCommitCache.mu.Lock()
defer lastCommitCache.mu.Unlock()
lastCommitCache.items = make(map[string]map[string]string)
lastCommitCache.order = nil
}