CLI: changeset commands default to JARL_CHANGESET, which is checked first

jarl changeset show/diff/apply/discard take the changeset from -C or
JARL_CHANGESET when no ID is given, and remove takes [ID] SEQ. A changeset
from JARL_CHANGESET is checked before anything uses it (staging included):
it must exist, be open and be the caller's own, so a stale export can't
stage into an applied changeset or into another user's draft that later
got its ID. Commands say on stderr which changeset they use, and apply and
discard remind to unset JARL_CHANGESET. -C and ID arguments are used as
given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-11 23:53:26 +01:00
1 parent e2e5007f94
commit cee11e7c86
7 files changed
+278 -43

No files matched your search

+67 -28
View File
@@ -120,8 +120,12 @@ func byIDOrMatch(op string, d map[string]any) api.ChangeRequest {
// changeset given with --changeset.
func submitBatch(c *client.Client, description string, reqs []api.ChangeRequest) error {
var resp api.MutationResponse
if c.Changeset > 0 {
if err := c.Do(ctx(), "POST", fmt.Sprintf("/changesets/%d/ops", c.Changeset), nil, map[string]any{"ops": reqs}, &resp); err != nil {
cs, err := c.UseChangeset(ctx())
if err != nil {
return err
}
if cs > 0 {
if err := c.Do(ctx(), "POST", fmt.Sprintf("/changesets/%d/ops", cs), nil, map[string]any{"ops": reqs}, &resp); err != nil {
return err
}
return report(&resp)
@@ -230,8 +234,9 @@ func changesetCmd() *cobra.Command {
Example: ` export JARL_CHANGESET=$(jarl changeset create "move web servers" -q)
jarl record add example.com www2 A 192.0.2.20
jarl record delete example.com www A
jarl changeset diff $JARL_CHANGESET
jarl changeset apply $JARL_CHANGESET`,
jarl changeset diff # JARL_CHANGESET's
jarl changeset apply
unset JARL_CHANGESET`,
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newClient()
if err != nil {
@@ -245,21 +250,25 @@ func changesetCmd() *cobra.Command {
fmt.Println(cs.ID)
return nil
}
fmt.Printf("created changeset %d\nstage changes with --changeset %d (or export JARL_CHANGESET=%d)\n", cs.ID, cs.ID, cs.ID)
fmt.Printf("created changeset %d\nstage changes with --changeset %d (or export JARL_CHANGESET=%d), then 'jarl changeset apply'\n", cs.ID, cs.ID, cs.ID)
return nil
},
}
show := &cobra.Command{
Use: "show ID",
Short: "Show a changeset's operations",
Args: cobra.ExactArgs(1),
Use: "show [ID]",
Short: "Show a changeset's operations (default: -C or JARL_CHANGESET)",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newClient()
if err != nil {
return err
}
id, _, err := changesetArg(c, args, false)
if err != nil {
return err
}
var cs api.Changeset
if err := c.Get(ctx(), "/changesets/"+args[0], nil, &cs); err != nil {
if err := c.Get(ctx(), "/changesets/"+id, nil, &cs); err != nil {
return err
}
return output(cs, func(w io.Writer) {
@@ -280,16 +289,20 @@ func changesetCmd() *cobra.Command {
},
}
diff := &cobra.Command{
Use: "diff ID",
Short: "Validate an open changeset and show what applying it would change",
Args: cobra.ExactArgs(1),
Use: "diff [ID]",
Short: "Validate an open changeset and show what applying it would change (default: -C or JARL_CHANGESET)",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newClient()
if err != nil {
return err
}
id, fromEnv, err := changesetArg(c, args, true)
if err != nil {
return err
}
var raw json.RawMessage
if err := c.Get(ctx(), "/changesets/"+args[0]+"/preview", nil, &raw); err != nil {
if err := c.Get(ctx(), "/changesets/"+id+"/preview", nil, &raw); err != nil {
return err
}
if flagOutput != "table" {
@@ -312,56 +325,82 @@ func changesetCmd() *cobra.Command {
fmt.Println()
fmt.Print(r.Diff)
if r.Valid {
fmt.Println("\nvalid: apply with 'jarl changeset apply " + args[0] + "'")
how := "jarl changeset apply " + id
if fromEnv {
how = "jarl changeset apply"
}
fmt.Println("\nvalid: apply with '" + how + "'")
return nil
}
return fmt.Errorf("the changeset can't be applied as it is")
},
}
apply := &cobra.Command{
Use: "apply ID",
Short: "Apply an open changeset",
Args: cobra.ExactArgs(1),
Use: "apply [ID]",
Short: "Apply an open changeset (default: -C or JARL_CHANGESET)",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newClient()
if err != nil {
return err
}
id, fromEnv, err := changesetArg(c, args, true)
if err != nil {
return err
}
var resp api.MutationResponse
if err := c.Do(ctx(), "POST", "/changesets/"+args[0]+"/apply", nil, map[string]any{"force": c.Force, "dry_run": c.DryRun}, &resp); err != nil {
if err := c.Do(ctx(), "POST", "/changesets/"+id+"/apply", nil, map[string]any{"force": c.Force, "dry_run": c.DryRun}, &resp); err != nil {
return err
}
return report(&resp)
if err := report(&resp); err != nil {
return err
}
if fromEnv && resp.Applied {
envChangesetDone(id, "applied")
}
return nil
},
}
discard := &cobra.Command{
Use: "discard ID",
Short: "Throw away an open changeset",
Args: cobra.ExactArgs(1),
Use: "discard [ID]",
Short: "Throw away an open changeset (default: -C or JARL_CHANGESET)",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newClient()
if err != nil {
return err
}
if err := c.Do(ctx(), "POST", "/changesets/"+args[0]+"/discard", nil, nil, nil); err != nil {
id, fromEnv, err := changesetArg(c, args, true)
if err != nil {
return err
}
if err := c.Do(ctx(), "POST", "/changesets/"+id+"/discard", nil, nil, nil); err != nil {
return err
}
if !flagQuiet {
fmt.Println("discarded changeset", args[0])
fmt.Println("discarded changeset", id)
}
if fromEnv {
envChangesetDone(id, "discarded")
}
return nil
},
}
remove := &cobra.Command{
Use: "remove ID SEQ",
Short: "Remove one operation from an open changeset",
Args: cobra.ExactArgs(2),
Use: "remove [ID] SEQ",
Short: "Remove one operation from an open changeset (default: -C or JARL_CHANGESET)",
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newClient()
if err != nil {
return err
}
return c.Do(ctx(), "DELETE", "/changesets/"+args[0]+"/ops/"+args[1], nil, nil, nil)
seq := args[len(args)-1]
id, _, err := changesetArg(c, args[:len(args)-1], true)
if err != nil {
return err
}
return c.Do(ctx(), "DELETE", "/changesets/"+id+"/ops/"+seq, nil, nil, nil)
},
}
revert := &cobra.Command{