Five parallel tea-parity additions (~1100 LOC):
- fgj time {list,add,delete,reset} (aliases: times, t)
Tracked time entries. 'list' with no arg uses ListMyTrackedTimes
across all your repos; with an issue number uses ListIssueTrackedTimes.
'add' accepts Go duration strings (30m, 1h30m). 'delete' removes a
single entry by id; 'reset' clears all times on an issue. Confirmation
on delete/reset unless --yes or no TTY.
- fgj branch {protect,unprotect}
Branch protection rules. 'protect' idempotently creates-or-edits via
Get/Create/EditBranchProtection with --require-approvals,
--require-signed-commits, --dismiss-stale-approvals,
--block-on-rejected-reviews, --block-on-outdated-branch,
--push-whitelist, --merge-whitelist, --require-status-checks.
Empty whitelist flags leave existing rule fields untouched.
'unprotect' deletes; 404 is a friendly no-op.
- fgj release asset {list,create,delete} (alias: assets)
Granular attachment management. Resolves the release by tag or
"latest" using the existing helpers in cmd/release.go. 'create'
validates all paths up front then uploads each. 'delete' accepts
numeric ids OR filenames (cross-references the attachment list).
Per-asset confirmation unless --yes.
- fgj milestone issues {add,remove} (alias: i)
Associate/disassociate issues with a milestone. Milestone accepted
as title or numeric id (reuses resolveMilestone from milestone.go).
'remove' passes EditIssueOption{Milestone: &zero} — the Gitea/Forgejo
convention for clearing the association. Continues on per-issue
failure and exits 1 if any failed.
- fgj notification {unread,pin,unpin}
Complement the existing list/read. Factory pattern over
ReadNotification(id, NotifyStatus) with three distinct constants.
All five files are self-contained: each has its own init() attaching
to the existing parent cobra.Command (branchCmd, milestoneCmd,
notificationCmd, releaseCmd) without modifying any other file. Built
by parallel sub-agents; all compile, vet, and test clean.
164 lines
4.2 KiB
Go
164 lines
4.2 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"code.gitea.io/sdk/gitea"
|
|
"forgejo.zerova.net/public/fgj-sid/internal/api"
|
|
"forgejo.zerova.net/public/fgj-sid/internal/config"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var milestoneIssuesCmd = &cobra.Command{
|
|
Use: "issues",
|
|
Aliases: []string{"i"},
|
|
Short: "Manage issues associated with a milestone",
|
|
Long: "Associate or disassociate issues with a milestone.",
|
|
}
|
|
|
|
var milestoneIssuesAddCmd = &cobra.Command{
|
|
Use: "add <title-or-id> <issue-number>...",
|
|
Short: "Add issues to a milestone",
|
|
Long: "Associate one or more issues with a milestone.",
|
|
Example: ` # Add issues #5 and #7 to milestone "v1.0"
|
|
fgj milestone issues add "v1.0" 5 7
|
|
|
|
# Add issue #12 to milestone with ID 3 in a specific repo
|
|
fgj milestone issues add 3 12 -R owner/repo`,
|
|
Args: cobra.MinimumNArgs(2),
|
|
RunE: runMilestoneIssuesAdd,
|
|
}
|
|
|
|
var milestoneIssuesRemoveCmd = &cobra.Command{
|
|
Use: "remove <title-or-id> <issue-number>...",
|
|
Short: "Remove issues from a milestone",
|
|
Long: "Disassociate one or more issues from a milestone.",
|
|
Example: ` # Remove issues #5 and #7 from milestone "v1.0"
|
|
fgj milestone issues remove "v1.0" 5 7
|
|
|
|
# Remove issue #12 from milestone with ID 3 in a specific repo
|
|
fgj milestone issues remove 3 12 -R owner/repo`,
|
|
Args: cobra.MinimumNArgs(2),
|
|
RunE: runMilestoneIssuesRemove,
|
|
}
|
|
|
|
func init() {
|
|
milestoneCmd.AddCommand(milestoneIssuesCmd)
|
|
milestoneIssuesCmd.AddCommand(milestoneIssuesAddCmd)
|
|
milestoneIssuesCmd.AddCommand(milestoneIssuesRemoveCmd)
|
|
|
|
addRepoFlags(milestoneIssuesAddCmd)
|
|
addRepoFlags(milestoneIssuesRemoveCmd)
|
|
}
|
|
|
|
func runMilestoneIssuesAdd(cmd *cobra.Command, args []string) error {
|
|
repo, _ := cmd.Flags().GetString("repo")
|
|
|
|
owner, name, err := parseRepo(repo)
|
|
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
|
|
}
|
|
|
|
ios.StartSpinner("Fetching milestone...")
|
|
ms, err := resolveMilestone(client, owner, name, args[0])
|
|
ios.StopSpinner()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
milestoneID := ms.ID
|
|
cs := ios.ColorScheme()
|
|
hadError := false
|
|
|
|
for _, arg := range args[1:] {
|
|
issueNum, parseErr := parseIssueArg(arg)
|
|
if parseErr != nil {
|
|
fmt.Fprintf(ios.ErrOut, "invalid issue number %q: %v\n", arg, parseErr)
|
|
hadError = true
|
|
continue
|
|
}
|
|
|
|
_, _, editErr := client.EditIssue(owner, name, issueNum, gitea.EditIssueOption{
|
|
Milestone: &milestoneID,
|
|
})
|
|
if editErr != nil {
|
|
fmt.Fprintf(ios.ErrOut, "failed to add issue #%d to milestone %q: %v\n", issueNum, ms.Title, editErr)
|
|
hadError = true
|
|
continue
|
|
}
|
|
|
|
fmt.Fprintf(ios.Out, "%s Added issue #%d to milestone %q\n", cs.SuccessIcon(), issueNum, ms.Title)
|
|
}
|
|
|
|
if hadError {
|
|
return fmt.Errorf("one or more issues could not be added to milestone %q", ms.Title)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runMilestoneIssuesRemove(cmd *cobra.Command, args []string) error {
|
|
repo, _ := cmd.Flags().GetString("repo")
|
|
|
|
owner, name, err := parseRepo(repo)
|
|
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
|
|
}
|
|
|
|
ios.StartSpinner("Fetching milestone...")
|
|
ms, err := resolveMilestone(client, owner, name, args[0])
|
|
ios.StopSpinner()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cs := ios.ColorScheme()
|
|
hadError := false
|
|
var zero int64 = 0
|
|
|
|
for _, arg := range args[1:] {
|
|
issueNum, parseErr := parseIssueArg(arg)
|
|
if parseErr != nil {
|
|
fmt.Fprintf(ios.ErrOut, "invalid issue number %q: %v\n", arg, parseErr)
|
|
hadError = true
|
|
continue
|
|
}
|
|
|
|
// Setting Milestone to a pointer-to-zero clears the milestone association
|
|
// in Gitea's API (PATCH /repos/{owner}/{repo}/issues/{num} with {"milestone": 0}).
|
|
_, _, editErr := client.EditIssue(owner, name, issueNum, gitea.EditIssueOption{
|
|
Milestone: &zero,
|
|
})
|
|
if editErr != nil {
|
|
fmt.Fprintf(ios.ErrOut, "failed to remove issue #%d from milestone %q: %v\n", issueNum, ms.Title, editErr)
|
|
hadError = true
|
|
continue
|
|
}
|
|
|
|
fmt.Fprintf(ios.Out, "%s Removed issue #%d from milestone %q\n", cs.SuccessIcon(), issueNum, ms.Title)
|
|
}
|
|
|
|
if hadError {
|
|
return fmt.Errorf("one or more issues could not be removed from milestone %q", ms.Title)
|
|
}
|
|
return nil
|
|
}
|