Skip to content

Commit afb88ea

Browse files
committed
Add support for fixing/clean tracking refs
1 parent a5143e0 commit afb88ea

3 files changed

Lines changed: 107 additions & 6 deletions

File tree

internal/command/fix.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import "github.com/spf13/cobra"
44

55
func init() {
66
fixCmd := &cobra.Command{
7-
Use: "fix",
8-
Args: cobra.NoArgs,
7+
Use: "fix [<branch> <base>]",
8+
Args: validBranchNames(stacker, 0, 2),
99
RunE: func(cmd *cobra.Command, args []string) error {
10-
return stacker.Fix(cmd.Context())
10+
return stacker.Fix(cmd.Context(), args...)
1111
},
1212
DisableFlagsInUseLine: true,
1313

internal/stacker/operation.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package stacker
33
import (
44
"fmt"
55
"sort"
6+
"strings"
67

78
"github.com/cloneable/stacker/internal/git"
89
)
@@ -153,6 +154,21 @@ func (o *operation) createSymref(name, target git.RefName, reason string) {
153154
}
154155
}
155156

157+
func (o *operation) deleteSymref(name git.RefName) {
158+
if o == nil || o.err != nil {
159+
return
160+
}
161+
_, err := o.git.Exec(
162+
"symbolic-ref",
163+
"--delete",
164+
name.String(),
165+
)
166+
if err != nil {
167+
o.err = fmt.Errorf("deleteSymref: %w", err)
168+
return
169+
}
170+
}
171+
156172
func (o *operation) createRef(name git.RefName, commit git.ObjectName) {
157173
if o == nil || o.err != nil {
158174
return
@@ -325,3 +341,21 @@ func (o *operation) trackedBranches() []git.BranchName {
325341
sort.Slice(branches, func(i, j int) bool { return branches[i] < branches[j] })
326342
return branches
327343
}
344+
345+
func (o *operation) forkpoint(base, branch git.RefName) git.ObjectName {
346+
if o == nil || o.err != nil {
347+
return ""
348+
}
349+
res, err := o.git.Exec(
350+
"merge-base",
351+
"--fork-point",
352+
base.String(),
353+
branch.String(),
354+
)
355+
if err != nil {
356+
o.err = fmt.Errorf("forkpoint: %w", err)
357+
return ""
358+
}
359+
// TODO: handle not found
360+
return git.ObjectName(strings.TrimSpace(res.Stdout.String()))
361+
}

internal/stacker/stacker.go

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,78 @@ func (s *Stacker) Sync(ctx context.Context) error {
126126
return op.Err()
127127
}
128128

129-
func (s *Stacker) Fix(ctx context.Context) error {
129+
func (s *Stacker) Fix(ctx context.Context, branches ...string) error {
130130
op := op(s.git)
131131

132-
// TODO: look for any refs/stacker/*/* branches
133-
// TODO: no such local branch? -> delete tracking ref
132+
// TODO: this is hacky. refactor.
133+
if len(branches) == 2 {
134+
branch := op.parseBranchName(branches[0])
135+
baseBranch := op.parseBranchName(branches[1])
136+
if baseSymrefName := branch.StackerBaseRefName(); op.hasRef(baseSymrefName) {
137+
baseSymref := op.ref(baseSymrefName)
138+
if baseSymref.SymRefTarget() != baseBranch.RefName() {
139+
return op.Failf("base branch already defined: %v", baseSymref.SymRefTarget())
140+
}
141+
} else {
142+
op.createSymref(branch.StackerBaseRefName(), baseBranch.RefName(), "stacker: set base branch")
143+
}
144+
if startRefName := branch.StackerStartRefName(); op.hasRef(startRefName) {
145+
// TODO: check if base or ancestor of base
146+
} else {
147+
forkpoint := op.forkpoint(baseBranch.RefName(), branch.RefName())
148+
op.createRef(branch.StackerStartRefName(), forkpoint)
149+
}
150+
return nil
151+
} else if len(branches) != 0 {
152+
return op.Failf("invalid arguments: %v", branches)
153+
}
154+
155+
op.snapshot()
156+
for _, branch := range op.trackedBranches() {
157+
if !op.hasRef(branch.RefName()) {
158+
if r := branch.StackerBaseRefName(); op.hasRef(r) {
159+
op.deleteSymref(r)
160+
}
161+
if r := branch.StackerStartRefName(); op.hasRef(r) {
162+
ref := op.ref(r)
163+
op.deleteRef(r, ref.ObjectName())
164+
}
165+
if r := branch.StackerRemoteRefName(); op.hasRef(r) {
166+
ref := op.ref(r)
167+
op.deleteRef(r, ref.ObjectName())
168+
}
169+
}
170+
}
171+
172+
op.snapshot()
173+
for _, branch := range op.trackedBranches() {
174+
// for each existing branch that's somehow still being tracked:
175+
baseSymrefName := branch.StackerBaseRefName()
176+
startRefName := branch.StackerStartRefName()
177+
if op.hasRef(baseSymrefName) {
178+
// there's a base symref
179+
if !op.hasRef(startRefName) {
180+
// but no start ref
181+
baseSymref := op.ref(branch.StackerBaseRefName())
182+
if !op.hasRef(baseSymref.SymRefTarget()) {
183+
// TODO: base branch doesn't exist (anymore)
184+
continue
185+
}
186+
// figure out forkpoint from what the base symref points to and the branch
187+
// TODO: forkpoint can fail
188+
forkpoint := op.forkpoint(baseSymref.SymRefTarget(), branch.RefName())
189+
// write the commit as start ref
190+
op.createRef(branch.StackerStartRefName(), forkpoint)
191+
}
192+
} else {
193+
// there's no base symref
194+
if op.hasRef(startRefName) {
195+
// but there's a start ref
196+
// TODO: check for branch at that commit? consult reflog?
197+
}
198+
}
199+
}
200+
134201
// TODO: no /base/, but /start/ -> look for branch head at /start/, set /base/
135202
// TODO: no /start/, but /base/ -> use git merge-base to find fork point
136203
// TODO: no /start/ nor /base/ -> do nothing, offer explicit way to track

0 commit comments

Comments
 (0)