@@ -82,12 +82,62 @@ func switchLocalContext(branchName string) error {
8282 return err
8383}
8484
85- func list (cliCtx * cli.Context ) error {
86- dblabClient , err := commands .ClientByCLIContext (cliCtx )
87- if err != nil {
88- return err
85+ // bareFormFlag is a flag of the bare `dblab branch` form, with the spelling to use instead when a
86+ // subcommand is given. None of these flags may gain an EnvVars or FilePath source: the guard below
87+ // reads IsSet, which is also true for a value that did not come from the command line.
88+ type bareFormFlag struct {
89+ name , hint string
90+ }
91+
92+ var bareFormFlags = []bareFormFlag {
93+ {name : "delete" , hint : "use either `dblab branch --delete BRANCH_NAME` or `dblab branch delete BRANCH_NAME`" },
94+ {name : "parent-branch" , hint : "place it after the subcommand: `dblab branch create --parent-branch VALUE BRANCH_NAME`" },
95+ {name : "snapshot-id" , hint : "place it after the subcommand: `dblab branch create --snapshot-id VALUE BRANCH_NAME`" },
96+ {name : "protected" , hint : "it applies to the bare form only: `dblab branch --protected VALUE BRANCH_NAME`" },
97+ }
98+
99+ // rejectBareFormFlags runs before `dblab branch` dispatches, and fails when a bare-form flag is
100+ // followed by a subcommand, the built-in help included. urfave/cli resolves the subcommand before
101+ // the bare-form action runs, so the flag would otherwise be parsed and then dropped:
102+ // `dblab branch --snapshot-id X create dev` would create from the wrong snapshot and
103+ // `dblab branch --protected 2h list` would list without changing any protection.
104+ func rejectBareFormFlags (cliCtx * cli.Context ) error {
105+ subcommand := subcommandNamed (cliCtx .Command , cliCtx .Args ().First ())
106+ if subcommand == nil {
107+ return nil
108+ }
109+
110+ for _ , flag := range bareFormFlags {
111+ if ! cliCtx .IsSet (flag .name ) {
112+ continue
113+ }
114+
115+ return commands .NewActionError (fmt .Sprintf ("--%s cannot precede the %s subcommand; %s" ,
116+ flag .name , subcommand .Name , flag .hint ))
89117 }
90118
119+ return nil
120+ }
121+
122+ func subcommandNamed (command * cli.Command , name string ) * cli.Command {
123+ if command == nil || name == "" {
124+ return nil
125+ }
126+
127+ for _ , subcommand := range command .Subcommands {
128+ if subcommand .HasName (name ) {
129+ return subcommand
130+ }
131+ }
132+
133+ return nil
134+ }
135+
136+ // branchAction dispatches the bare `dblab branch` form: --protected updates the protection of the
137+ // named branch, a positional name creates a branch, --delete removes one, and no arguments lists
138+ // them. The list, create, delete and switch subcommands express the same operations unambiguously
139+ // and take precedence, so a name that collides with one of them never silently creates a branch.
140+ func branchAction (cliCtx * cli.Context ) error {
91141 branchName := cliCtx .Args ().First ()
92142
93143 // update branch protection.
@@ -104,12 +154,20 @@ func list(cliCtx *cli.Context) error {
104154 return create (cliCtx )
105155 }
106156
107- // delete branch.
108- if deleteName := cliCtx .String ("delete" ); deleteName != "" {
157+ // delete branch. an explicitly empty name reaches the guard in deleteBranch.
158+ if cliCtx .IsSet ("delete" ) {
109159 return deleteBranch (cliCtx )
110160 }
111161
112- // list branches.
162+ return listBranches (cliCtx )
163+ }
164+
165+ func listBranches (cliCtx * cli.Context ) error {
166+ dblabClient , err := commands .ClientByCLIContext (cliCtx )
167+ if err != nil {
168+ return err
169+ }
170+
113171 branches , err := dblabClient .ListBranchesView (cliCtx .Context )
114172 if err != nil {
115173 return err
@@ -234,13 +292,17 @@ func isBranchExist(cliCtx *cli.Context, branchName string) error {
234292}
235293
236294func create (cliCtx * cli.Context ) error {
295+ branchName := cliCtx .Args ().First ()
296+
297+ if branchName == "" {
298+ return commands .NewActionError ("BRANCH_NAME is required to create a branch" )
299+ }
300+
237301 dblabClient , err := commands .ClientByCLIContext (cliCtx )
238302 if err != nil {
239303 return err
240304 }
241305
242- branchName := cliCtx .Args ().First ()
243-
244306 baseBranch := cliCtx .String ("parent-branch" )
245307 snapshotID := cliCtx .String ("snapshot-id" )
246308
@@ -315,15 +377,24 @@ func getBaseBranch(cliCtx *cli.Context) string {
315377 return baseBranch
316378}
317379
380+ // deleteBranch removes the branch named by the --delete flag of the bare `dblab branch` form, or by
381+ // the positional argument of the `delete` subcommand.
318382func deleteBranch (cliCtx * cli.Context ) error {
383+ branchName := cliCtx .String ("delete" )
384+ if branchName == "" {
385+ branchName = cliCtx .Args ().First ()
386+ }
387+
388+ if branchName == "" {
389+ return commands .NewActionError ("BRANCH_NAME is required to delete a branch" )
390+ }
391+
319392 dblabClient , err := commands .ClientByCLIContext (cliCtx )
320393 if err != nil {
321394 return err
322395 }
323396
324- branchName := cliCtx .String ("delete" )
325-
326- branching , err := getBranchingFromEnv ()
397+ branching , err := loadBranching ()
327398 if err != nil {
328399 return err
329400 }
@@ -400,6 +471,10 @@ func history(cliCtx *cli.Context) error {
400471 return err
401472}
402473
474+ // loadBranching reads the branching state of the current environment. It is a variable so tests can
475+ // run deleteBranch without the invoking user's CLI config.
476+ var loadBranching = getBranchingFromEnv
477+
403478func getBranchingFromEnv () (config.Branching , error ) {
404479 branching := config.Branching {}
405480
0 commit comments