add agentws prune
Agents leave their managed worktrees behind, and `agentws rm` takes one path at a time with no idea whether a branch's work is safely upstream, so clearing an accumulation by hand risks destroying unmerged commits. - classify every managed worktree: dirty, PR open, upstream, or unproven - remove only what is safe; delete the local branch only when work is upstream - prove "upstream" with merge-base and git cherry, so squash merges count - match a PR by head.label, which survives the branch deletion a merge does - dry run by default; --yes applies, --keep-branches spares every branch - read the Gitea path from origin's URL rather than assuming the owner
This commit is contained in:
@@ -3,6 +3,7 @@ package agent
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -219,6 +220,76 @@ func TestFetchStateFailsOnNon404StatusError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Gitea rewrites head.ref to "refs/pull/<n>/head" once the PR's branch is
|
||||
// deleted, which merging does in these repos. Matching a branch against
|
||||
// head.ref alone therefore finds nothing for every merged PR; head.label keeps
|
||||
// the original name.
|
||||
func TestPRHeadBranch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ref, label string
|
||||
want string
|
||||
}{
|
||||
{"merged, branch deleted", "refs/pull/12/head", "benvin/merged", "benvin/merged"},
|
||||
{"open PR", "benvin/open", "benvin/open", "benvin/open"},
|
||||
{"fully qualified ref", "refs/heads/benvin/x", "", "benvin/x"},
|
||||
{"no label falls back to ref", "benvin/y", "", "benvin/y"},
|
||||
{"cross-repo label", "benvin/z", "someone:benvin/z", "benvin/z"},
|
||||
{"nothing usable", "refs/pull/12/head", "", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var pr PullRequest
|
||||
pr.Head.Ref = tt.ref
|
||||
pr.Head.Label = tt.label
|
||||
if got := PRHeadBranch(pr); got != tt.want {
|
||||
t.Errorf("PRHeadBranch(ref=%q,label=%q) = %q, want %q", tt.ref, tt.label, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPRsPaginates(t *testing.T) {
|
||||
var pages []string
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/v1/repos/unkin/repo/pulls", func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
pages = append(pages, q.Get("page"))
|
||||
if q.Get("state") != "all" {
|
||||
t.Errorf("state = %q, want all", q.Get("state"))
|
||||
}
|
||||
if q.Get("page") == "1" {
|
||||
full := make([]string, 0, prPageSize)
|
||||
for i := 0; i < prPageSize; i++ {
|
||||
full = append(full, fmt.Sprintf(`{"number":%d,"state":"closed","merged":true,"head":{"ref":"refs/pull/%d/head","label":"benvin/b%d"}}`, i+1, i+1, i+1))
|
||||
}
|
||||
_, _ = io.WriteString(w, "["+strings.Join(full, ",")+"]")
|
||||
return
|
||||
}
|
||||
_, _ = io.WriteString(w, `[{"number":99,"state":"open","head":{"ref":"benvin/last","label":"benvin/last"}}]`)
|
||||
})
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
c := &GiteaClient{BaseURL: srv.URL, HTTP: srv.Client()}
|
||||
prs, err := c.ListPRs("unkin/repo", "all")
|
||||
if err != nil {
|
||||
t.Fatalf("ListPRs: %v", err)
|
||||
}
|
||||
if len(prs) != prPageSize+1 {
|
||||
t.Fatalf("got %d PRs, want %d", len(prs), prPageSize+1)
|
||||
}
|
||||
if len(pages) != 2 || pages[0] != "1" || pages[1] != "2" {
|
||||
t.Errorf("pages requested = %v, want [1 2]", pages)
|
||||
}
|
||||
if got := PRHeadBranch(prs[0]); got != "benvin/b1" {
|
||||
t.Errorf("first PR head branch = %q, want benvin/b1", got)
|
||||
}
|
||||
if !prs[len(prs)-1].IsOpen() {
|
||||
t.Error("last PR should be open")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGiteaAPIError(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/v1/repos/unkin/repo/pulls", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user