From b06d902687d40fbf15c20a202775927af3c0c7c8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 24 Sep 2026 23:31:32 -0700 Subject: [PATCH] feat(pulls): support branch-validated stack reordering Assisted-by: Codet:GPT-5-Codex --- cmd/pulls/stack.go | 3 +- cmd/pulls/stack/reorder.go | 53 +++++++++++++++++++++++++++++++++ cmd/pulls/stack/reorder_test.go | 17 +++++++++++ modules/task/pull_stack.go | 13 ++++++++ modules/task/pull_stack_test.go | 18 +++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 cmd/pulls/stack/reorder.go create mode 100644 cmd/pulls/stack/reorder_test.go diff --git a/cmd/pulls/stack.go b/cmd/pulls/stack.go index 89af4e61..5802b43e 100644 --- a/cmd/pulls/stack.go +++ b/cmd/pulls/stack.go @@ -15,13 +15,14 @@ import ( var CmdPullsStack = cli.Command{ Name: "stack", Usage: "Manage pull request stacks", - Description: "List, show, push, restack, and merge pull request stacks", + Description: "List, show, push, reorder, restack, and merge pull request stacks", Action: runPullsStackDefault, Commands: []*cli.Command{ &stack.CmdStackList, &stack.CmdStackShow, &stack.CmdStackPush, &stack.CmdStackRestack, + &stack.CmdStackReorder, &stack.CmdStackMerge, }, } diff --git a/cmd/pulls/stack/reorder.go b/cmd/pulls/stack/reorder.go new file mode 100644 index 00000000..9331bd52 --- /dev/null +++ b/cmd/pulls/stack/reorder.go @@ -0,0 +1,53 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package stack + +import ( + stdctx "context" + "fmt" + + "gitea.dev/tea/cmd/flags" + "gitea.dev/tea/modules/context" + "gitea.dev/tea/modules/task" + "gitea.dev/tea/modules/utils" + + "github.com/urfave/cli/v3" +) + +// CmdStackReorder stores a validated, already-linked PR branch order. +var CmdStackReorder = cli.Command{ + Name: "reorder", Usage: "Reorder PRs after their branches have been rebased", + ArgsUsage: " [more pull indices]", + Flags: flags.AllDefaultFlags, Action: RunStackReorder, +} + +func RunStackReorder(requestCtx stdctx.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 3 { + return fmt.Errorf("stack id and at least two pull numbers are required") + } + stackID, err := utils.ArgToIndex(cmd.Args().Get(0)) + if err != nil { + return err + } + indices := make([]int64, 0, cmd.Args().Len()-1) + for i := 1; i < cmd.Args().Len(); i++ { + index, err := utils.ArgToIndex(cmd.Args().Get(i)) + if err != nil { + return err + } + indices = append(indices, index) + } + ctx, err := context.InitCommand(cmd) + if err != nil { + return err + } + if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil { + return err + } + if _, err := task.ReorderPullStack(requestCtx, ctx.Login, ctx.Owner, ctx.Repo, stackID, indices); err != nil { + return err + } + fmt.Fprintf(ctx.Writer, "reordered stack #%d\n", stackID) + return nil +} diff --git a/cmd/pulls/stack/reorder_test.go b/cmd/pulls/stack/reorder_test.go new file mode 100644 index 00000000..35462c3b --- /dev/null +++ b/cmd/pulls/stack/reorder_test.go @@ -0,0 +1,17 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package stack + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStackReorderRequiresCompleteOrder(t *testing.T) { + cmd := CmdStackReorder + err := cmd.Run(context.Background(), []string{"reorder", "5", "2"}) + assert.ErrorContains(t, err, "at least two pull numbers") +} diff --git a/modules/task/pull_stack.go b/modules/task/pull_stack.go index 6b20e2c8..caec302d 100644 --- a/modules/task/pull_stack.go +++ b/modules/task/pull_stack.go @@ -120,6 +120,19 @@ func AddPullStackEntry(ctx stdctx.Context, login *config.Login, owner, repo stri return &s, nil } +// ReorderPullStack records the order of PR branches that are already linked. +func ReorderPullStack(ctx stdctx.Context, login *config.Login, owner, repo string, id int64, pullIndices []int64) (*stack.Stack, error) { + var s stack.Stack + err := postStackJSON(ctx, login, fmt.Sprintf("/repos/%s/%s/stacks/%d/reorder", owner, repo, id), + struct { + PullIndices []int64 `json:"pull_indices"` + }{PullIndices: pullIndices}, http.StatusOK, &s) + if err != nil { + return nil, err + } + return &s, nil +} + // RestackPullStack queues a restack starting at the stack's base branch. func RestackPullStack(ctx stdctx.Context, login *config.Login, owner, repo string, id int64) error { client := api.NewClient(login) diff --git a/modules/task/pull_stack_test.go b/modules/task/pull_stack_test.go index 1a5e9849..094987b1 100644 --- a/modules/task/pull_stack_test.go +++ b/modules/task/pull_stack_test.go @@ -134,6 +134,24 @@ func TestAddPullStackEntry(t *testing.T) { assert.Equal(t, int64(5), stack.ID) } +func TestReorderPullStack(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/api/v1/repos/owner/repo/stacks/5/reorder", r.URL.Path) + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, `{"pull_indices":[2,3]}`, string(body)) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"id":5,"repo_id":2,"owner_id":3,"title":"feat: base","base_branch":"main","state":"open","auto_restack":true,"entries":[]}`)) + })) + defer server.Close() + result, err := ReorderPullStack(t.Context(), &config.Login{ + Name: "test", URL: server.URL, Token: "secret-token", + }, "owner", "repo", 5, []int64{2, 3}) + require.NoError(t, err) + assert.Equal(t, int64(5), result.ID) +} + func TestRestackPullStack(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method)