submit: Use collision-resolved upstream bases (#1430)
autofix.ci / autofix (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Generate test matrix (push) Waiting to run
CI / Test / ${{ matrix.name }} (push) Blocked by required conditions
CI / OK (push) Blocked by required conditions
GitHub Pages / build (push) Waiting to run
GitHub Pages / deploy (push) Blocked by required conditions

When `stack submit` encounters remote branches with the same names as
local stack members, it allocates collision-free upstream names.
Upper pull requests still used the occupied local names as their bases,
so the first submission produced an incorrect pull request chain.

Carry each selected upstream name through the batch and prefer it over
the preloaded graph snapshot when submitting the next branch.
This preserves graph reuse while creating the correct base chain on the
first submission.

Fixes #1394
This commit is contained in:
Abhinav Gupta
2026-08-30 14:52:41 -07:00
committed by GitHub
parent b102141287
commit 6f7dd01691
3 changed files with 140 additions and 3 deletions
@@ -0,0 +1,3 @@
kind: Fixed
body: 'stack submit: Pull requests are now created with the correct base branches when existing remote branches have the same names as the local stack.'
time: 2026-08-30T12:34:54.354962-07:00
+19 -3
View File
@@ -236,19 +236,29 @@ func (h *Handler) SubmitBatch(ctx context.Context, req *BatchRequest) error {
return err
}
resolvedUpstreams := make(map[string]string)
var branchesToComment []string
for _, branch := range req.Branches {
branchInfo, ok := graph.Lookup(branch)
if !ok {
return fmt.Errorf("submit branch %s: lookup branch: %w", branch, state.ErrNotExist)
}
// Shallow copy the options because submitBranch may modify them.
opts := *opts
status, err := h.submitBranch(
ctx,
graph,
branch,
&submitOptions{Options: &opts},
&submitOptions{
Options: &opts,
upstreamBase: resolvedUpstreams[branchInfo.Base],
},
)
if err != nil {
return fmt.Errorf("submit branch %s: %w", branch, err)
}
resolvedUpstreams[branch] = status.upstreamBranch
if status.Submitted {
branchesToComment = append(branchesToComment, branch)
}
@@ -347,12 +357,15 @@ type submitStatus struct {
// If yes, comments will be added or updated
// based on the NavComment option.
Submitted bool
upstreamBranch string
}
type submitOptions struct {
*Options
Title, Body string
Title, Body string
upstreamBase string
}
func (h *Handler) submitBranch(
@@ -405,6 +418,9 @@ func (h *Handler) submitBranch(
if err != nil {
return status, fmt.Errorf("resolve upstream branch: %w", err)
}
defer func() {
status.upstreamBranch = upstreamBranch
}()
// Similarly, if the branch's base has a different name upstream,
// use that name instead.
@@ -414,7 +430,7 @@ func (h *Handler) submitBranch(
if !ok {
return status, fmt.Errorf("lookup base branch: %w", state.ErrNotExist)
}
upstreamBase = cmp.Or(baseBranch.UpstreamBranch, branch.Base)
upstreamBase = cmp.Or(opts.upstreamBase, baseBranch.UpstreamBranch, branch.Base)
}
var existingChange *forge.FindChangeItem
@@ -0,0 +1,118 @@
# 'stack submit' uses collision-resolved upstream names as PR bases.
#
# https://github.com/abhinav/git-spice/issues/1394
as 'Test <test@example.com>'
at '2026-08-30T19:28:02Z'
# Setup.
cd repo
git init
git commit --allow-empty -m 'Initial commit'
# Set up a fake GitHub remote with the desired branch names already taken.
shamhub-setup
shamhub register alice
shamhub new origin alice/example.git
git push origin main main:float-1 main:float-2 main:float-3
gs repo init
env SHAMHUB_USERNAME=alice
gs auth login
# Create the local float-1 -> float-2 -> float-3 stack.
git add float-1.txt
gs branch create float-1 -m 'Add float 1'
git add float-2.txt
gs branch create float-2 -m 'Add float 2'
git add float-3.txt
gs branch create float-3 -m 'Add float 3'
# Each local branch needs a collision-free upstream name.
# Upper PRs must target those resolved names during the same submission.
gs stack submit --fill
stderr 'float-1: Using upstream name ''float-1-2'' instead'
stderr 'float-2: Using upstream name ''float-2-2'' instead'
stderr 'float-3: Using upstream name ''float-3-2'' instead'
shamhub dump changes
cmpenvJSON stdout $WORK/golden/changes.json
-- repo/float-1.txt --
Float 1
-- repo/float-2.txt --
Float 2
-- repo/float-3.txt --
Float 3
-- golden/changes.json --
[
{
"number": 1,
"html_url": "$SHAMHUB_URL/alice/example/change/1",
"state": "open",
"title": "Add float 1",
"body": "",
"base": {
"repository": {
"owner": "alice",
"name": "example"
},
"ref": "main",
"sha": "41127951a5bcb2b7258946d758cbe8549dde8fd2"
},
"head": {
"repository": {
"owner": "alice",
"name": "example"
},
"ref": "float-1-2",
"sha": "19b5f4c2b2d7362f0d02649e1351083ebc7a3d42"
}
},
{
"number": 2,
"html_url": "$SHAMHUB_URL/alice/example/change/2",
"state": "open",
"title": "Add float 2",
"body": "",
"base": {
"repository": {
"owner": "alice",
"name": "example"
},
"ref": "float-1-2",
"sha": "19b5f4c2b2d7362f0d02649e1351083ebc7a3d42"
},
"head": {
"repository": {
"owner": "alice",
"name": "example"
},
"ref": "float-2-2",
"sha": "bb5562b20af515520df4bcc17277fe7cbda2221b"
}
},
{
"number": 3,
"html_url": "$SHAMHUB_URL/alice/example/change/3",
"state": "open",
"title": "Add float 3",
"body": "",
"base": {
"repository": {
"owner": "alice",
"name": "example"
},
"ref": "float-2-2",
"sha": "bb5562b20af515520df4bcc17277fe7cbda2221b"
},
"head": {
"repository": {
"owner": "alice",
"name": "example"
},
"ref": "float-3-2",
"sha": "d1a14cc8a8ecd178c66856d80489bb09a3e79ccf"
}
}
]