Skip to content

Commit 759b21d

Browse files
noahAlephNotation
authored andcommitted
feat(run-commit): add --ref flag for repo:tag references
Before this change, 'vers run-commit <arg>' always sent the argument as {"commit_id": ...} in the API request. Passing a repo:tag reference like 'my-app:latest' failed with 422 Unprocessable Entity because the server tried to parse it as a UUID. --ref switches the underlying payload to {"ref": ...}, which the API resolves against tags in the caller's own org. The positive case was verified end-to-end against pi-agent:latest. Also added a friendly error when the argument looks like a repo:tag (contains ':') and --ref was omitted, pointing the user at the right flag instead of hitting a generic 422 from the API.
1 parent e9ff14c commit 759b21d

2 files changed

Lines changed: 74 additions & 6 deletions

File tree

cmd/run_commit.go

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package cmd
22

33
import (
44
"context"
5+
"fmt"
56
"os"
7+
"strings"
68

79
"github.com/hdresearch/vers-cli/internal/handlers"
810
"github.com/hdresearch/vers-cli/internal/jobs"
@@ -16,27 +18,53 @@ var (
1618
runCommitJSON bool
1719
runCommitFormat string
1820
runCommitWait bool
21+
runCommitIsRef bool
1922
)
2023

2124
// runCommitCmd represents the run-commit command
2225
var runCommitCmd = &cobra.Command{
23-
Use: "run-commit [commit-key]",
26+
Use: "run-commit [commit-key | repo:tag]",
2427
Short: "Start a development environment from a commit",
25-
Long: `Start a Vers development environment from an existing commit using its commit key.
28+
Long: `Start a Vers development environment from an existing commit.
29+
30+
The argument is treated as a commit ID (UUID) by default. Pass --ref to interpret
31+
it as a repository reference in "repo_name:tag_name" format instead — the API
32+
will resolve the tag to the commit it currently points at within your own org.
33+
34+
Examples:
35+
vers run-commit c123456789abcdef
36+
vers run-commit my-app:latest --ref
37+
vers run-commit my-app:latest --ref --vm-alias dev --wait
2638
2739
Use --json for machine-readable output.
2840
Use --wait to block until the VM is running.`,
2941
Args: cobra.ExactArgs(1),
3042
RunE: func(cmd *cobra.Command, args []string) error {
3143
commitKey := args[0]
44+
45+
// Friendly nudge for the common gotcha: user passed something that
46+
// looks like a repo:tag ref without --ref.
47+
if !runCommitIsRef && looksLikeRepoRef(commitKey) {
48+
return fmt.Errorf(
49+
"'%s' looks like a repo:tag reference; did you mean '--ref'?\n"+
50+
" vers run-commit %s --ref",
51+
commitKey, commitKey,
52+
)
53+
}
54+
3255
cfg, err := runconfig.Load()
3356
if err != nil {
3457
return err
3558
}
3659
applyFlagOverrides(cmd, cfg)
3760
apiCtx, cancel := context.WithTimeout(context.Background(), application.Timeouts.APILong)
3861
defer cancel()
39-
req := handlers.RunCommitReq{CommitKey: commitKey, VMAlias: commitVmAlias, Wait: runCommitWait}
62+
req := handlers.RunCommitReq{
63+
CommitKey: commitKey,
64+
VMAlias: commitVmAlias,
65+
Wait: runCommitWait,
66+
IsRef: runCommitIsRef,
67+
}
4068
var jobID string
4169
if runCommitWait {
4270
jobID, _ = jobs.Submit(jobs.Submission{
@@ -70,6 +98,31 @@ Use --wait to block until the VM is running.`,
7098
},
7199
}
72100

101+
// looksLikeRepoRef returns true for strings in "word:word" shape where the
102+
// parts contain only characters that are legal in repo/tag names. Used only
103+
// to guess user intent and suggest --ref; never to actually dispatch.
104+
func looksLikeRepoRef(s string) bool {
105+
i := strings.IndexByte(s, ':')
106+
if i <= 0 || i == len(s)-1 {
107+
return false
108+
}
109+
legal := func(c byte) bool {
110+
return (c >= 'a' && c <= 'z') ||
111+
(c >= 'A' && c <= 'Z') ||
112+
(c >= '0' && c <= '9') ||
113+
c == '-' || c == '_' || c == '.'
114+
}
115+
for j := 0; j < len(s); j++ {
116+
if j == i {
117+
continue
118+
}
119+
if !legal(s[j]) {
120+
return false
121+
}
122+
}
123+
return true
124+
}
125+
73126
func init() {
74127
rootCmd.AddCommand(runCommitCmd)
75128

@@ -78,4 +131,5 @@ func init() {
78131
runCommitCmd.Flags().StringVar(&runCommitFormat, "format", "", "Output format (json) [deprecated: use --json]")
79132
_ = runCommitCmd.Flags().MarkDeprecated("format", "use --json instead")
80133
runCommitCmd.Flags().BoolVar(&runCommitWait, "wait", false, "Wait until VM is running")
134+
runCommitCmd.Flags().BoolVar(&runCommitIsRef, "ref", false, "Interpret the argument as a repo:tag reference instead of a commit ID")
81135
}

internal/handlers/run_commit.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,30 @@ import (
1111
)
1212

1313
type RunCommitReq struct {
14+
// CommitKey is either a commit ID (UUID) or, when IsRef is true, a
15+
// repository reference in "repo_name:tag_name" format.
1416
CommitKey string
1517
VMAlias string
1618
Wait bool
19+
// IsRef switches the underlying API payload from {"commit_id": ...} to
20+
// {"ref": ...}, which enables resolving own-org repository tags like
21+
// "my-app:latest" instead of raw commit UUIDs.
22+
IsRef bool
1723
}
1824

1925
func HandleRunCommit(ctx context.Context, a *app.App, r RunCommitReq) (presenters.RunCommitView, error) {
20-
body := vers.VmRestoreFromCommitParams{
21-
VmFromCommitRequest: vers.VmFromCommitRequestParam{
26+
var reqUnion vers.VmFromCommitRequestUnionParam
27+
if r.IsRef {
28+
reqUnion = vers.VmFromCommitRequestRefParam{
29+
Ref: vers.F(r.CommitKey),
30+
}
31+
} else {
32+
reqUnion = vers.VmFromCommitRequestCommitIDParam{
2233
CommitID: vers.F(r.CommitKey),
23-
},
34+
}
35+
}
36+
body := vers.VmRestoreFromCommitParams{
37+
VmFromCommitRequest: reqUnion,
2438
}
2539

2640
resp, err := a.Client.Vm.RestoreFromCommit(ctx, body)

0 commit comments

Comments
 (0)