feat: repo archive/unarchive + completion install
- fgj repo {archive,unarchive}: toggle a repo's archived state via
EditRepo with *bool Archived. Archive prompts for confirmation
(requires --yes in non-TTY envs); unarchive is reversible, no prompt.
Accepts positional owner/name or -R flag; -R wins when both given.
- fgj completion install [shell]: idempotent install of completion
scripts to shell-standard locations. Auto-detects shell from $SHELL
if omitted. Paths: bash → XDG (or brew prefix on macOS), zsh →
~/.zsh/completions/_fgj, fish → ~/.config/fish/completions/fgj.fish.
--system (bash only) prints the sudo command for /etc paths without
writing. --dry-run prints the target path without writing.
Compares existing-file contents before overwrite to stay idempotent.
Both files built by sub-agents in parallel. Build + vet + test clean.
This commit is contained in:
parent
2d69873f3e
commit
e3d7904929
3 changed files with 360 additions and 0 deletions
163
cmd/repo_archive.go
Normal file
163
cmd/repo_archive.go
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
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 repoArchiveCmd = &cobra.Command{
|
||||
Use: "archive [owner/name]",
|
||||
Short: "Archive a repository",
|
||||
Long: `Mark a repository as archived. Archived repositories remain visible but
|
||||
become read-only: pushes, issues, pull requests, and releases are disabled.
|
||||
|
||||
The target repo may be passed as a positional argument, via -R/--repo, or
|
||||
auto-detected from the current git context. If both positional and -R are
|
||||
given, the -R flag wins.`,
|
||||
Example: ` # Archive a specific repo (prompted confirmation)
|
||||
fgj repo archive owner/name
|
||||
|
||||
# Archive the current repo without prompting
|
||||
fgj repo archive --yes
|
||||
|
||||
# Archive using the -R flag
|
||||
fgj repo archive -R owner/name -y`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: runRepoArchive,
|
||||
}
|
||||
|
||||
var repoUnarchiveCmd = &cobra.Command{
|
||||
Use: "unarchive [owner/name]",
|
||||
Short: "Unarchive a repository",
|
||||
Long: `Clear the archived flag on a repository, restoring normal read-write
|
||||
behaviour.
|
||||
|
||||
The target repo may be passed as a positional argument, via -R/--repo, or
|
||||
auto-detected from the current git context. If both positional and -R are
|
||||
given, the -R flag wins.`,
|
||||
Example: ` # Unarchive a specific repo
|
||||
fgj repo unarchive owner/name
|
||||
|
||||
# Unarchive the current repo
|
||||
fgj repo unarchive`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: runRepoUnarchive,
|
||||
}
|
||||
|
||||
func init() {
|
||||
repoCmd.AddCommand(repoArchiveCmd)
|
||||
repoCmd.AddCommand(repoUnarchiveCmd)
|
||||
|
||||
addRepoFlags(repoArchiveCmd)
|
||||
repoArchiveCmd.Flags().BoolP("yes", "y", false, "Skip the confirmation prompt")
|
||||
addJSONFlags(repoArchiveCmd, "Output updated repository as JSON")
|
||||
|
||||
addRepoFlags(repoUnarchiveCmd)
|
||||
repoUnarchiveCmd.Flags().BoolP("yes", "y", false, "Skip the confirmation prompt (unused; kept for symmetry with archive)")
|
||||
addJSONFlags(repoUnarchiveCmd, "Output updated repository as JSON")
|
||||
}
|
||||
|
||||
// resolveRepoTarget returns owner/name honouring the "optional positional OR
|
||||
// -R flag" pattern used elsewhere in the CLI: -R wins when both are supplied.
|
||||
func resolveRepoTarget(cmd *cobra.Command, args []string) (string, string, error) {
|
||||
var repo string
|
||||
if len(args) > 0 {
|
||||
repo = args[0]
|
||||
}
|
||||
if r, _ := cmd.Flags().GetString("repo"); r != "" {
|
||||
repo = r
|
||||
}
|
||||
return parseRepo(repo)
|
||||
}
|
||||
|
||||
func runRepoArchive(cmd *cobra.Command, args []string) error {
|
||||
owner, name, err := resolveRepoTarget(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slug := fmt.Sprintf("%s/%s", owner, name)
|
||||
skipConfirm, _ := cmd.Flags().GetBool("yes")
|
||||
|
||||
if !skipConfirm {
|
||||
if !ios.IsStdinTTY() {
|
||||
return fmt.Errorf("refusing to archive %s without a TTY; pass -y/--yes to confirm non-interactively", slug)
|
||||
}
|
||||
prompt := fmt.Sprintf("Archive %s? This disables issues/PRs/pushes. [y/N]: ", slug)
|
||||
answer, err := promptLine(prompt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if answer != "y" && answer != "Y" && answer != "yes" && answer != "Yes" && answer != "YES" {
|
||||
fmt.Fprintln(ios.ErrOut, "Aborted.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
archived := true
|
||||
opt := gitea.EditRepoOption{Archived: &archived}
|
||||
|
||||
ios.StartSpinner("Archiving repository...")
|
||||
repository, _, err := client.EditRepo(owner, name, opt)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to archive %s: %w", slug, err)
|
||||
}
|
||||
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, repository)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Archived %s\n", cs.SuccessIcon(), slug)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRepoUnarchive(cmd *cobra.Command, args []string) error {
|
||||
owner, name, err := resolveRepoTarget(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slug := fmt.Sprintf("%s/%s", owner, name)
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
archived := false
|
||||
opt := gitea.EditRepoOption{Archived: &archived}
|
||||
|
||||
ios.StartSpinner("Unarchiving repository...")
|
||||
repository, _, err := client.EditRepo(owner, name, opt)
|
||||
ios.StopSpinner()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unarchive %s: %w", slug, err)
|
||||
}
|
||||
|
||||
if wantJSON(cmd) {
|
||||
return outputJSON(cmd, repository)
|
||||
}
|
||||
|
||||
cs := ios.ColorScheme()
|
||||
fmt.Fprintf(ios.Out, "%s Unarchived %s\n", cs.SuccessIcon(), slug)
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue