Skip to content

Commit 6c1fb8a

Browse files
committed
WIP on POC
0 parents  commit 6c1fb8a

File tree

9 files changed

+428
-0
lines changed

9 files changed

+428
-0
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.local
2+

Diff for: .vscode/settings.json

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"editor.tokenColorCustomizations": {
3+
"textMateRules": [
4+
{
5+
"scope": "googletest.failed",
6+
"settings": {
7+
"foreground": "#f00"
8+
}
9+
},
10+
{
11+
"scope": "googletest.passed",
12+
"settings": {
13+
"foreground": "#0f0"
14+
}
15+
},
16+
{
17+
"scope": "googletest.run",
18+
"settings": {
19+
"foreground": "#0f0"
20+
}
21+
}
22+
]
23+
}
24+
}

Diff for: bisect/bisect.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package bisect
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"os/exec"
8+
"strings"
9+
)
10+
11+
type BisectOpts struct {
12+
Jobs int
13+
Good string
14+
Bad string
15+
Cmd []string
16+
}
17+
18+
type bisect struct {
19+
jobs int
20+
good string
21+
bad string
22+
cmd []string
23+
runner Runner
24+
}
25+
26+
func NewBisect(opts BisectOpts) *bisect {
27+
runner := NewLocalRunner()
28+
return &bisect{
29+
jobs: opts.Jobs,
30+
good: opts.Good,
31+
bad: opts.Bad,
32+
runner: runner,
33+
}
34+
}
35+
36+
func (b bisect) Run(ctx context.Context) error {
37+
var gitLogOut bytes.Buffer
38+
gitLog := exec.CommandContext(ctx, "git", "log", "--format=%h", "--ancestry-path", fmt.Sprintf("%s~1..%s", b.good, b.bad))
39+
gitLog.Stdout = &gitLogOut
40+
if err := gitLog.Run(); err != nil {
41+
return fmt.Errorf("could not get the list of revisions: %v", err)
42+
}
43+
44+
revisions := strings.Split(gitLogOut.String(), "\n")
45+
runState := b.runner.Run(ctx, revisions, b.cmd)
46+
47+
for {
48+
select {
49+
case wip := <-runState.wip:
50+
fmt.Printf("WIP (%d): %s\n", len(wip), wip)
51+
case left := <-runState.left:
52+
fmt.Printf("Left (%d): %s\n", len(left), left)
53+
case err := <-runState.err:
54+
return err
55+
case found := <-runState.done:
56+
fmt.Println("Found revision: ", found)
57+
return nil
58+
case <-ctx.Done():
59+
return nil
60+
}
61+
}
62+
}

Diff for: bisect/bisect_state.go

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package bisect
2+
3+
import (
4+
"fmt"
5+
"math/rand"
6+
"sync"
7+
)
8+
9+
type smartRev struct {
10+
Rev string
11+
Cancel chan interface{}
12+
state *BisectState
13+
}
14+
15+
func (s *smartRev) Good() {
16+
s.state.markAsGood(s.Rev)
17+
}
18+
19+
func (s *smartRev) Bad() {
20+
s.state.markAsBad(s.Rev)
21+
}
22+
23+
type BisectState struct {
24+
revs []string // immutable
25+
26+
indexes map[string]int // written once but it's async
27+
indexesMu sync.RWMutex
28+
29+
start int
30+
startMu sync.RWMutex
31+
32+
end int
33+
endMu sync.RWMutex
34+
35+
// bisect tracker
36+
bisectSteps []int
37+
bisectIteration int
38+
bisectMu sync.Mutex
39+
40+
activeListenersMu sync.Mutex
41+
activeListeners []*smartRev
42+
}
43+
44+
func NewBisectState(revs []string) *BisectState {
45+
state := &BisectState{
46+
revs: revs,
47+
end: len(revs) - 1,
48+
indexes: make(map[string]int),
49+
bisectSteps: make([]int, len(revs)),
50+
}
51+
52+
state.initIndexesTable()
53+
state.initBisectSteps()
54+
55+
return state
56+
}
57+
58+
func (b *BisectState) initIndexesTable() {
59+
for i, rev := range b.revs {
60+
b.indexes[rev] = i
61+
}
62+
}
63+
64+
func (b *BisectState) initBisectSteps() {
65+
for i := range b.bisectSteps {
66+
b.bisectSteps[i] = i
67+
}
68+
69+
rand.Seed(1)
70+
rand.Shuffle(len(b.bisectSteps), func(i, j int) {
71+
b.bisectSteps[i], b.bisectSteps[j] = b.bisectSteps[j], b.bisectSteps[i]
72+
})
73+
}
74+
75+
func (b *BisectState) Next() *smartRev {
76+
b.bisectMu.Lock()
77+
defer b.bisectMu.Unlock()
78+
for ; b.bisectIteration < len(b.bisectSteps); b.bisectIteration++ {
79+
step := b.bisectSteps[b.bisectIteration]
80+
if step >= b.start && step <= b.end {
81+
// TODO implement ref cancellation
82+
rev := b.revs[step]
83+
b.bisectIteration++
84+
return &smartRev{
85+
Rev: rev,
86+
Cancel: make(chan interface{}),
87+
state: b,
88+
}
89+
}
90+
}
91+
return nil
92+
}
93+
94+
func (b *BisectState) getIndex(rev string) int {
95+
b.indexesMu.RLock()
96+
defer b.indexesMu.RUnlock()
97+
return b.indexes[rev]
98+
}
99+
100+
func (b *BisectState) markAsGood(rev string) error {
101+
i := b.getIndex(rev)
102+
103+
if i <= b.start {
104+
return nil
105+
}
106+
107+
if i >= b.end {
108+
return fmt.Errorf("TODO(come up with an error msg)")
109+
}
110+
111+
b.startMu.Lock()
112+
defer b.startMu.Unlock()
113+
b.start = i
114+
return nil
115+
}
116+
117+
func (b *BisectState) markAsBad(rev string) error {
118+
i := b.getIndex(rev)
119+
120+
if i >= b.end {
121+
return nil
122+
}
123+
124+
if i <= b.start {
125+
return fmt.Errorf("TODO(come up with an error msg)")
126+
}
127+
128+
b.endMu.Lock()
129+
defer b.endMu.Unlock()
130+
b.end = i
131+
return nil
132+
}
133+
134+
func (b *BisectState) FirstBadRev() *string {
135+
b.startMu.RLock()
136+
defer b.startMu.RUnlock()
137+
138+
b.endMu.RLock()
139+
defer b.endMu.RUnlock()
140+
141+
if b.end-b.start == 1 {
142+
return &b.revs[b.end]
143+
}
144+
145+
return nil
146+
}

Diff for: bisect/bisect_state_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package bisect
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestNextReturnsNilWhenDone(t *testing.T) {
8+
revs := []string{
9+
"ebaf211260",
10+
"1c05d39abc",
11+
"416b0374fb",
12+
}
13+
target := NewBisectState(revs)
14+
15+
for i := 0; i < len(revs); i++ {
16+
nextRev := target.Next()
17+
18+
if nextRev == nil {
19+
t.Fatal("expected a revision but instead got nil")
20+
}
21+
}
22+
23+
if target.Next() != nil {
24+
t.Fatal("unexpected revision after extracting all of the revs")
25+
}
26+
}

Diff for: bisect/local_runner.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package bisect
2+
3+
import (
4+
"context"
5+
"sync"
6+
"time"
7+
)
8+
9+
type LocalRunner struct {
10+
mu sync.RWMutex
11+
left []string
12+
wip []string
13+
lastBad string
14+
cmd []string
15+
}
16+
17+
func NewLocalRunner() *LocalRunner {
18+
return &LocalRunner{}
19+
}
20+
21+
func (l *LocalRunner) Run(ctx context.Context, revs []string, cmd []string) *RunnerState {
22+
state := NewStartedRunnerState(revs)
23+
l.cmd = cmd
24+
l.left = revs
25+
l.lastBad = revs[len(revs)-1]
26+
l.wip = []string{}
27+
go func() {
28+
l.start(ctx, state)
29+
}()
30+
go func() {
31+
ticker := time.NewTicker(time.Second / 24)
32+
for _ = range ticker.C {
33+
l.updateState(state)
34+
}
35+
}()
36+
return state
37+
}
38+
39+
func (l *LocalRunner) updateState(state *RunnerState) {
40+
if len(l.left) == 0 && len(l.wip) == 0 {
41+
state.done <- l.lastBad
42+
return
43+
}
44+
state.left <- l.left
45+
state.wip <- l.wip
46+
}
47+
48+
func (l *LocalRunner) bad(ctx context.Context, rev string) {
49+
l.mu.Lock()
50+
defer l.mu.Unlock()
51+
// TODO figure out how to mark a bad hit
52+
l.lastBad = rev
53+
}
54+
55+
func (l *LocalRunner) good(ctx context.Context, rev string) {
56+
l.mu.Lock()
57+
defer l.mu.Unlock()
58+
// TODO figure out how to mark a good hit
59+
}
60+
61+
func (l *LocalRunner) markWip(ctx context.Context, rev string) {
62+
l.mu.Lock()
63+
defer l.mu.Unlock()
64+
l.wip = append(l.wip, rev)
65+
}
66+
67+
func (l *LocalRunner) start(ctx context.Context, state *RunnerState) {
68+
// exec.CommandContext(ctx, "TODO", "TODO")
69+
70+
for i := len(l.left) - 1; i >= 0; i-- {
71+
l.mu.RLock()
72+
rev := l.left[i]
73+
l.mu.RUnlock()
74+
l.markWip(ctx, rev)
75+
if i > 5 {
76+
l.bad(ctx, rev)
77+
} else {
78+
l.good(ctx, rev)
79+
}
80+
_ = <-time.NewTimer(time.Second).C
81+
}
82+
}

Diff for: bisect/runner.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package bisect
2+
3+
import "context"
4+
5+
type RunnerState struct {
6+
left chan []string
7+
wip chan []string
8+
err chan error
9+
done chan string
10+
}
11+
12+
func NewStartedRunnerState(revs []string) *RunnerState {
13+
left := make(chan []string)
14+
wip := make(chan []string)
15+
err := make(chan error)
16+
done := make(chan string)
17+
defer func() {
18+
go func() {
19+
left <- revs
20+
wip <- []string{}
21+
}()
22+
}()
23+
24+
return &RunnerState{
25+
left: left,
26+
wip: wip,
27+
err: err,
28+
done: done,
29+
}
30+
}
31+
32+
type Runner interface {
33+
Run(ctx context.Context, revs []string, cmd []string) *RunnerState
34+
}

Diff for: go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/TheGrizzlyDev/git-analyse
2+
3+
go 1.16

0 commit comments

Comments
 (0)