feat: add webhook, repo delete/search, admin, pr clean/resolve/review-comments
Second pass of tea-parity work:
- fgj webhook {list,create,update,delete}: full CRUD over repo webhooks.
Create supports all standard hook types (gitea, slack, discord, etc.),
event selection, content type, secret, branch filter, auth header.
Update is partial — flags you omit leave existing config unchanged.
- fgj repo delete: type-to-confirm deletion; --yes skips for scripts;
refuses without a TTY unless --yes is passed.
- fgj repo search: SDK SearchRepos with query, topic/description,
private/archived, --type (source/fork/mirror), owner, sort/order.
- fgj admin user list: admin-gated user enumeration.
- fgj pr clean: delete the local branch from 'pr checkout'. Refuses
if the PR is still open (use --force) or if the branch is currently
checked out.
- fgj pr review-comments: list inline review comments across every
review on a PR (ListPullReviews + ListPullReviewComments per review).
- fgj pr resolve / unresolve: mark review comments as (un)resolved.
Uses raw POST since SDK v0.22.1 predates these endpoints; requires
Forgejo 8.x+ / Gitea 1.22+ server-side.
All share the standard parseRepo + config.Load + NewClientFromConfig
pattern; list commands support --json / --jq.
This commit is contained in:
parent
17ca49d0c5
commit
adccd6f6f7
6 changed files with 871 additions and 0 deletions
98
cmd/pr_clean.go
Normal file
98
cmd/pr_clean.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"forgejo.zerova.net/public/fgj-sid/internal/api"
|
||||
"forgejo.zerova.net/public/fgj-sid/internal/config"
|
||||
"forgejo.zerova.net/public/fgj-sid/internal/git"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var prCleanCmd = &cobra.Command{
|
||||
Use: "clean <number>",
|
||||
Short: "Delete the local branch created by 'pr checkout'",
|
||||
Long: `Remove the local branch that was checked out for a pull request.
|
||||
|
||||
For safety, the PR must be closed (merged or declined). If the branch is
|
||||
currently checked out, switch away first — this command refuses to delete
|
||||
your active branch.
|
||||
|
||||
Pass --force to delete the local branch even if the PR is still open.`,
|
||||
Example: ` # Clean up after a merged PR
|
||||
fgj pr clean 42
|
||||
|
||||
# Force-delete local branch for an open PR
|
||||
fgj pr clean 42 --force`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runPRClean,
|
||||
}
|
||||
|
||||
func init() {
|
||||
prCmd.AddCommand(prCleanCmd)
|
||||
addRepoFlags(prCleanCmd)
|
||||
prCleanCmd.Flags().Bool("force", false, "Delete the local branch even if the PR is still open")
|
||||
}
|
||||
|
||||
func runPRClean(cmd *cobra.Command, args []string) error {
|
||||
prNumber, err := parseIssueArg(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid pull request number: %w", err)
|
||||
}
|
||||
|
||||
repoFlag, _ := cmd.Flags().GetString("repo")
|
||||
owner, name, err := parseRepo(repoFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
pr, _, err := client.GetPullRequest(owner, name, prNumber)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get pull request: %w", err)
|
||||
}
|
||||
|
||||
if !force && string(pr.State) == "open" {
|
||||
return fmt.Errorf("PR #%d is still open; refuse to delete local branch without --force", prNumber)
|
||||
}
|
||||
|
||||
headBranch := pr.Head.Ref
|
||||
if headBranch == "" {
|
||||
return fmt.Errorf("PR #%d has no head branch to clean (it may have been deleted already)", prNumber)
|
||||
}
|
||||
|
||||
// Refuse to delete the current branch.
|
||||
current, err := git.GetCurrentBranch()
|
||||
if err == nil && current == headBranch {
|
||||
return fmt.Errorf("branch %q is currently checked out; switch to another branch first (e.g. 'git switch main')", headBranch)
|
||||
}
|
||||
|
||||
// Check local branch exists.
|
||||
if out, _ := exec.Command("git", "rev-parse", "--verify", "--quiet", "refs/heads/"+headBranch).Output(); len(strings.TrimSpace(string(out))) == 0 {
|
||||
fmt.Fprintf(ios.ErrOut, "Local branch %q not found; nothing to clean.\n", headBranch)
|
||||
return nil
|
||||
}
|
||||
|
||||
delCmd := exec.Command("git", "branch", "-D", headBranch)
|
||||
delCmd.Stdout = ios.Out
|
||||
delCmd.Stderr = ios.ErrOut
|
||||
if err := delCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to delete local branch %q: %w", headBranch, err)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Deleted local branch %q\n", cs.SuccessIcon(), headBranch)
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue