feat: times, branch protection, release assets, milestone issues, notification states
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.
This commit is contained in:
parent
4eeef2ceca
commit
d15deaf064
5 changed files with 1124 additions and 0 deletions
164
cmd/milestone_issues.go
Normal file
164
cmd/milestone_issues.go
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue