Skip to content

Commit 0bf7c8f

Browse files
authored
fix(branch): reject extra args with --rename/-m and add cycle detection (#718)
## Summary Fixes #717 - Reject unexpected extra positional arguments when `--rename`/`-m` flag is used with `av branch`, preventing silent misuse where `-m 'message'` triggers a rename instead of branch creation - Add visited-set cycle detection to `SubsequentBranches` and `SubsequentBranchesFiltered` so a corrupted branch graph (e.g., self-referencing parent) logs a warning instead of crashing with a stack overflow ## Test plan - [ ] `av branch foo -m 'bar'` now returns a clear error instead of silently renaming - [ ] `av branch --rename new-name` still works correctly - [ ] `av branch old:new -m` still works (colon syntax for rename) - [ ] Existing tests pass (`go test ./...`)
1 parent a5cbc55 commit 0bf7c8f

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

cmd/av/branch.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ internal tracking metadata that defines the order of branches within a stack.`),
7171
}
7272

7373
if branchFlags.Rename {
74+
if len(args) > 1 {
75+
return errors.New("unexpected extra arguments with --rename/-m flag\n" +
76+
" To rename a branch: av branch --rename <new-name>\n" +
77+
" To create a branch: av branch <name> (without -m)")
78+
}
7479
return branchMove(ctx, repo, db, branchName, branchFlags.Force)
7580
}
7681
if branchFlags.Split {

internal/meta/branch.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,22 @@ func PreviousBranches(tx ReadTx, name string) ([]string, error) {
122122
// If the tree is not a straight line (which isn't explicitly supported!), the
123123
// branches will be returned in depth-first traversal order.
124124
func SubsequentBranches(tx ReadTx, name string) []string {
125+
visited := map[string]bool{name: true}
126+
return subsequentBranches(tx, name, visited)
127+
}
128+
129+
func subsequentBranches(tx ReadTx, name string, visited map[string]bool) []string {
125130
logrus.Debugf("finding subsequent branches for %q", name)
126131
var res []string
127132
children := Children(tx, name)
128133
for _, child := range children {
134+
if visited[child.Name] {
135+
logrus.Warnf("cycle detected in branch graph: %q already visited while traversing children of %q", child.Name, name)
136+
continue
137+
}
138+
visited[child.Name] = true
129139
res = append(res, child.Name)
130-
res = append(res, SubsequentBranches(tx, child.Name)...)
140+
res = append(res, subsequentBranches(tx, child.Name, visited)...)
131141
}
132142
return res
133143
}
@@ -136,17 +146,27 @@ func SubsequentBranches(tx ReadTx, name string) []string {
136146
// name in "dependency order", optionally skipping branches that are excluded
137147
// from sync --all and their descendants.
138148
func SubsequentBranchesFiltered(tx ReadTx, name string, skipExcluded bool) []string {
149+
visited := map[string]bool{name: true}
150+
return subsequentBranchesFiltered(tx, name, skipExcluded, visited)
151+
}
152+
153+
func subsequentBranchesFiltered(tx ReadTx, name string, skipExcluded bool, visited map[string]bool) []string {
139154
logrus.Debugf("finding subsequent branches for %q (skipExcluded=%v)", name, skipExcluded)
140155
var res []string
141156
children := Children(tx, name)
142157
for _, child := range children {
158+
if visited[child.Name] {
159+
logrus.Warnf("cycle detected in branch graph: %q already visited while traversing children of %q", child.Name, name)
160+
continue
161+
}
143162
if skipExcluded && child.ExcludeFromSyncAll {
144163
// Skip this branch and its entire subtree
145164
logrus.Debugf("skipping excluded branch %q and its descendants", child.Name)
146165
continue
147166
}
167+
visited[child.Name] = true
148168
res = append(res, child.Name)
149-
res = append(res, SubsequentBranchesFiltered(tx, child.Name, skipExcluded)...)
169+
res = append(res, subsequentBranchesFiltered(tx, child.Name, skipExcluded, visited)...)
150170
}
151171
return res
152172
}

0 commit comments

Comments
 (0)