feat(cli): mred version update subcommand

`mred version update ID [--status open|closed] [--due YYYY-MM-DD]
[--name NAME]` flips milestone status, dates, and names; at least one
flag is required (usage error otherwise) and only provided flags are
sent. Text mode prints "updated version #N", -o json re-fetches and
emits the stored version, exit codes follow the 0/1/2 convention.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
This commit is contained in:
2026-08-29 08:12:16 -05:00
parent 26824c3715
commit 37118f0db9
3 changed files with 164 additions and 1 deletions
+45 -1
View File
@@ -356,7 +356,7 @@ func readDesc(path string) (string, error) {
func cmdVersion(args []string, stdout, stderr io.Writer) int {
if len(args) == 0 {
return usageErr(stderr, "version: expected list|create (see mred help)")
return usageErr(stderr, "version: expected list|create|update (see mred help)")
}
sub, rest := args[0], args[1:]
switch sub {
@@ -364,6 +364,8 @@ func cmdVersion(args []string, stdout, stderr io.Writer) int {
return cmdVersionList(rest, stdout, stderr)
case "create":
return cmdVersionCreate(rest, stdout, stderr)
case "update":
return cmdVersionUpdate(rest, stdout, stderr)
default:
return usageErr(stderr, "version: unknown subcommand %q", sub)
}
@@ -432,6 +434,48 @@ func dueSuffix(due string) string {
return "due " + due
}
func cmdVersionUpdate(args []string, stdout, stderr io.Writer) int {
fs, cfgPath, out := newFlags("version update", stderr)
status := fs.String("status", "", "open|closed")
due := fs.String("due", "", "due date YYYY-MM-DD")
name := fs.String("name", "", "new version name")
pos, perr := parseArgs(fs, args)
if perr != nil || len(pos) != 1 {
return usageErr(stderr, "version update: usage: mred version update ID [--status open|closed] [--due DATE] [--name NAME]")
}
id, err := strconv.Atoi(pos[0])
if err != nil {
return usageErr(stderr, "version update: ID must be numeric, got %q", pos[0])
}
if *status == "" && *due == "" && *name == "" {
return usageErr(stderr, "version update: nothing to update: pass at least one of --status, --due, --name")
}
if *status != "" && *status != "open" && *status != "closed" {
return usageErr(stderr, "version update: --status must be open|closed")
}
c, err := client(*cfgPath)
if err != nil {
return usageErr(stderr, "%v", err)
}
// The server answers 204 with an empty body; UpdateVersion never
// parses it. -o json re-fetches the version afterwards.
if err := c.UpdateVersion(context.Background(), id, redmine.VersionParams{
Name: *name, DueDate: *due, Status: *status,
}); err != nil {
return apiErr(stderr, err)
}
if *out == "json" {
v, err := c.GetVersion(context.Background(), id)
if err != nil {
return apiErr(stderr, err)
}
emitJSON(stdout, map[string]any{"version": v})
return 0
}
fmt.Fprintf(stdout, "updated version #%d\n", id)
return 0
}
// --- category -----------------------------------------------------------------
func cmdCategory(args []string, stdout, stderr io.Writer) int {