help: custom printer with config options (#922)
autofix.ci / autofix (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Generate test matrix (push) Has been cancelled
GitHub Pages / build (push) Has been cancelled
CI / Test / ${{ matrix.name }} (push) Has been cancelled
CI / OK (push) Has been cancelled
GitHub Pages / deploy (push) Has been cancelled

Implement a custom help printer
that shows configuration options alongside flags.
This ensures help output is consistent and predictable.
Implementation based on Kong's default help printer.

The custom printer:

- shows config keys for flags next to them
- lists config-only options in a separate section below flags

Testing:
A new test matches the `--help` for all commands
against golden copies in testdata/help/.
These are auto-updated by `mise run generate`,
so autofix will take care of generating them for most PRs.

Resolves #884
This commit is contained in:
Abhinav Gupta
2025-10-31 09:10:08 -07:00
committed by GitHub
parent 813e0cf0a0
commit da845d89d6
75 changed files with 2381 additions and 181 deletions
@@ -0,0 +1,3 @@
kind: Changed
body: --help for all commands now also lists configuration options.
time: 2025-10-31T06:36:00.003171-07:00
+11 -4
View File
@@ -17,15 +17,22 @@ type authLoginCmd struct {
func (*authLoginCmd) Help() string {
return text.Dedent(`
For GitHub, a prompt will allow selecting between
OAuth, GitHub App, and Personal Access Token-based authentication.
A prompt will allow selecting between available authentication methods.
Available methods include:
- OAuth: Web-based authentication flow
- GitHub App (GitHub only): Authenticate using a GitHub App installation
- Personal Access Token: Directly provide a personal access token
- CLI: Use the GitHub or GitLab CLI tool for authentication (if installed)
The differences between them are explained in the prompt.
The authentication token is stored in a system-provided secure storage.
The authentication token is stored in a system-provided
secure storage if available.
Use 'gs auth logout' to log out and delete the token from storage.
Fails if already logged in.
Use --refresh to force a refresh of the authentication token,
Use --refresh to force a refresh of the authentication token
or change the authentication method.
`)
}
+2 -2
View File
@@ -11,10 +11,10 @@ type authLogoutCmd struct{}
func (*authLogoutCmd) Help() string {
return text.Dedent(`
The stored authentication information is deleted from secure storage.
The stored authentication information is deleted.
Use 'gs auth login' to log in again.
No-op if not logged in.
Does not do anything if not logged in.
`)
}
+5 -1
View File
@@ -19,7 +19,11 @@ type bottomCmd struct {
func (*bottomCmd) Help() string {
return text.Dedent(`
Checks out the bottom-most branch in the current branch's stack.
Use the -n flag to print the branch without checking it out.
Does nothing if already on the bottom-most branch,
and returns an error if on trunk.
Use -n to print the branch name to stdout
without checking it out.
`)
}
+1 -1
View File
@@ -47,7 +47,7 @@ type BranchPromptConfig struct {
// hidden:"" means that the CLI flag isn't intended to be used.
// Only the configuration.
BranchPromptSort string `hidden:"" config:"branchPrompt.sort" help:"Sort branches by the given field"`
BranchPromptSort string `hidden:"" config:"branchPrompt.sort" help:"Sort branches by the given field. Common values include 'refname', 'commiterdate', etc. Defaults to branch name."`
}
// BeforeApply is called by Kong as part of parsing.
+3 -5
View File
@@ -28,11 +28,9 @@ func (*branchCheckoutCmd) Help() string {
Provide a branch name as an argument to skip the prompt.
Use -u/--untracked to show untracked branches in the prompt.
Use the spice.branchPrompt.sort configuration option
to specify the sort order of branches in the prompt.
Commonly used field names include "refname", "commiterdate", etc.
By default, branches are sorted by name.
Use --detach to detach HEAD to the commit of the selected branch.
Use -n to print the selected branch name to stdout
without checking it out.
`)
}
+2 -2
View File
@@ -14,8 +14,8 @@ import (
)
type branchCreateConfig struct {
Prefix string `default:"" config:"branchCreate.prefix" help:"Prepend a prefix to the name of the branch being created" hidden:""`
GeneratedBranchNameLimit int `default:"32" config:"branchCreate.generatedBranchNameLimit" help:"Maximum length of auto-generated branch names (truncated at word boundaries)" hidden:""`
Prefix string `default:"" config:"branchCreate.prefix" help:"Always add a prefix to branch names." hidden:""`
GeneratedBranchNameLimit int `default:"32" config:"branchCreate.generatedBranchNameLimit" help:"Maximum length of auto-generated branch names (truncated at word boundaries). Defaults to 32." hidden:""`
}
type branchCreateCmd struct {
+9 -7
View File
@@ -21,14 +21,16 @@ type branchDeleteCmd struct {
func (*branchDeleteCmd) Help() string {
return text.Dedent(`
The deleted branches and their commits are removed from the stack.
Branches above the deleted branches are rebased onto
the next branches available downstack.
Branches above the deleted branches are first rebased onto
the next branches available downstack,
or onto trunk if there are no branches available below.
A prompt will allow selecting the target branch if none are provided.
Use the spice.branchPrompt.sort configuration option
to specify the sort order of branches in the prompt.
Commonly used field names include "refname", "commiterdate", etc.
By default, branches are sorted by name.
Without any arguments,
a prompt will allow selecting the branch to delete.
By default, if the branch to be deleted has unmerged changes,
the deletion will be aborted.
Use --force to delete the branch regardless of unmerged changes.
`)
}
+6 -4
View File
@@ -15,10 +15,12 @@ type branchEditCmd struct{}
func (*branchEditCmd) Help() string {
return text.Dedent(`
Starts an interactive rebase with only the commits
in this branch.
Following the rebase, branches upstack from this branch
will be restacked.
Starts an interactive rebase
with only the commits
from this branch.
After the rebase,
branches upstack from this branch will be restacked.
`)
}
+1
View File
@@ -24,6 +24,7 @@ func (*branchFoldCmd) Help() string {
and the current branch will be deleted.
Branches above the folded branch will point
to the next branch downstack.
Use the --branch flag to target a different branch.
`)
}
+11 -11
View File
@@ -22,9 +22,16 @@ type branchOntoCmd struct {
func (*branchOntoCmd) Help() string {
return text.Dedent(`
The commits of the current branch are transplanted onto another
branch.
Branches upstack are moved to point to its original base.
Commits of the current branch
are transplanted onto another branch
while leaving the rest of the stack intact.
That is, branches above the current branch
are first rebased onto its original base,
and then the current branch is moved onto the new base.
A prompt will allow selecting the new base for the branch.
Provide an argument to skip the prompt.
Use the --branch flag to target a different branch for the move.
For example, given the following stack with B checked out,
running 'gs branch onto main' will move B onto main
@@ -37,14 +44,7 @@ func (*branchOntoCmd) Help() string {
┌─┴ A ├─┴ A
trunk trunk
Use --branch to move a different branch than the current one.
A prompt will allow selecting the new base.
Use the spice.branchPrompt.sort configuration option
to specify the sort order of branches in the prompt.
Commonly used field names include "refname", "commiterdate", etc.
By default, branches are sorted by name.
Provide the new base name as an argument to skip the prompt.
Use 'gs upstack onto' to also move the upstack branches.
`)
}
+7 -4
View File
@@ -20,7 +20,7 @@ type branchRenameCmd struct {
func (*branchRenameCmd) Help() string {
return text.Dedent(`
The following modes are supported:
The following usage modes are supported:
# Rename <old> to <new>
gs branch rename <old> <new>
@@ -31,9 +31,12 @@ func (*branchRenameCmd) Help() string {
# Rename current branch interactively
gs branch rename
For branches renamed with 'git branch -m',
use 'gs branch track' and 'gs branch untrack'
to update the branch tracking.
If a branch was renamed outside of 'gs',
for example with 'git branch -m',
the branch tracking information will be out of date.
To fix this,
untrack the old branch name with 'gs branch untrack <old>',
and track the new branch name with 'gs branch track <new>'.
`)
}
+16 -8
View File
@@ -22,13 +22,13 @@ type branchSplitCmd struct {
func (*branchSplitCmd) Help() string {
return text.Dedent(`
Splits the current branch into two or more branches at specific
commits, inserting the new branches into the stack
Splits the current branch into two or more branches
at specific commits,
inserting the new branches into the stack
at the positions of the commits.
Use the --branch flag to specify a different branch to split.
By default, the command will prompt for commits to introduce
splits at.
The command will prompt for commits to introduce splits at.
Supply the --at flag one or more times to split a branch
without a prompt.
@@ -44,10 +44,18 @@ func (*branchSplitCmd) Help() string {
# split at the previous commit
gs branch split --at HEAD^:newbranch
When prompted for branch names, you can reuse the original branch
name for one of the intermediate commits. When you do this, the
original branch will be reassigned to that commit, and you'll be
prompted to provide a name for the remaining HEAD commits.
If the original branch is assigned to one of the splits,
it is required to provide a new name for the commit at HEAD.
Fo example, if we have branch A with three commits:
┌─ A
│ abcdef1 Commit 3 (HEAD)
│ bcdef12 Commit 2
│ cdef123 Commit 1
trunk
A split at commit 2 using the branch name "A"
would require a new name to be provided for commit 3.
`)
}
+22 -7
View File
@@ -24,18 +24,27 @@ type submitOptions struct {
const _submitHelp = `
Use --dry-run to print what would be submitted without submitting it.
For new Change Requests, a prompt will allow filling metadata.
Use --fill to populate title and body from the commit messages,
and --[no-]draft to set the draft status.
Omitting the draft flag will leave the status unchanged of open CRs.
Use --fill to populate title and body from the commit messages.
The --[no-]draft flag marks the CR as draft or not.
Use the 'spice.submit.draft' configuration option
to mark new CRs as drafts (or not) by default,
skipping the prompt.
For updating Change Requests,
use --[no-]draft to change its draft status.
Without the flag, the draft status is not changed.
Use --no-publish to push branches without creating CRs.
This has no effect if a branch already has an open CR.
Use --update-only to only update branches with existing CRs,
and skip those that would create new CRs.
Use --nav-comment=false to disable navigation comments in CRs,
or --nav-comment=multiple to post those comments only if there are multiple CRs in the stack.
or --nav-comment=multiple to post those comments
only if there are multiple CRs in the stack.
`
type branchSubmitCmd struct {
@@ -55,18 +64,24 @@ func (*branchSubmitCmd) Help() string {
For new Change Requests, a prompt will allow filling metadata.
Use the --title and --body flags to skip the prompt,
or the --fill flag to use the commit message to fill them in.
The --draft flag marks the change request as a draft.
The --[no-]draft flag marks the CR as draft or not.
Use the 'spice.submit.draft' configuration option
to mark new CRs as drafts (or not) by default,
skipping the prompt.
For updating Change Requests,
use --draft/--no-draft to change its draft status.
use --[no-]draft to change its draft status.
Without the flag, the draft status is not changed.
Use --no-publish to push branches without creating CRs.
This has no effect if a branch already has an open CR.
Use --update-only to only update branches with existing CRs,
and skip those that would create new CRs.
Use --nav-comment=false to disable navigation comments in CRs,
or --nav-comment=multiple to post those comments only if there are multiple CRs in the stack.
or --nav-comment=multiple to post those comments
only if there are multiple CRs in the stack.
`)
}
+3
View File
@@ -21,6 +21,9 @@ func (*branchTrackCmd) Help() string {
The base is guessed by comparing against other tracked branches.
Use --base to specify a base explicitly.
Use 'gs downstack track' from the topmost branch
to track a manully created stack of branches at once.
`)
}
+5 -2
View File
@@ -30,10 +30,13 @@ func (*commitAmendCmd) Help() string {
return text.Dedent(`
Staged changes are amended into the topmost commit.
Branches upstack are restacked if necessary.
Use this as a shortcut for 'git commit --amend'
This is a shortcut for 'git commit --amend'
followed by 'gs upstack restack'.
An editor is opened to edit the commit message,
Use 'gs commit fixup' to amend commits
that are further downstack.
An editor is opened to edit the commit message
unless the --no-edit flag is given.
Use the -m/--message option to specify the message
on the command line.
+131 -72
View File
@@ -54,15 +54,22 @@ gs auth login [flags]
Log in to a service
For GitHub, a prompt will allow selecting between
OAuth, GitHub App, and Personal Access Token-based authentication.
A prompt will allow selecting between available authentication methods.
Available methods include:
- OAuth: Web-based authentication flow
- GitHub App (GitHub only): Authenticate using a GitHub App installation
- Personal Access Token: Directly provide a personal access token
- CLI: Use the GitHub or GitLab CLI tool for authentication (if installed)
The differences between them are explained in the prompt.
The authentication token is stored in a system-provided secure storage.
The authentication token is stored in a system-provided
secure storage if available.
Use 'gs auth logout' to log out and delete the token from storage.
Fails if already logged in.
Use --refresh to force a refresh of the authentication token,
Use --refresh to force a refresh of the authentication token
or change the authentication method.
**Flags**
@@ -87,10 +94,10 @@ gs auth logout [flags]
Log out of a service
The stored authentication information is deleted from secure storage.
The stored authentication information is deleted.
Use 'gs auth login' to log in again.
No-op if not logged in.
Does not do anything if not logged in.
## Repository
@@ -223,18 +230,27 @@ Change Requests are created or updated
for all branches in the current stack.
Use --dry-run to print what would be submitted without submitting it.
For new Change Requests, a prompt will allow filling metadata.
Use --fill to populate title and body from the commit messages,
and --[no-]draft to set the draft status.
Omitting the draft flag will leave the status unchanged of open CRs.
Use --fill to populate title and body from the commit messages.
The --[no-]draft flag marks the CR as draft or not.
Use the 'spice.submit.draft' configuration option
to mark new CRs as drafts (or not) by default,
skipping the prompt.
For updating Change Requests,
use --[no-]draft to change its draft status.
Without the flag, the draft status is not changed.
Use --no-publish to push branches without creating CRs.
This has no effect if a branch already has an open CR.
Use --update-only to only update branches with existing CRs,
and skip those that would create new CRs.
Use --nav-comment=false to disable navigation comments in CRs,
or --nav-comment=multiple to post those comments only if there are multiple CRs in the stack.
or --nav-comment=multiple to post those comments
only if there are multiple CRs in the stack.
**Flags**
@@ -334,18 +350,27 @@ it must have already been submitted by a prior command.
Use --branch to start at a different branch.
Use --dry-run to print what would be submitted without submitting it.
For new Change Requests, a prompt will allow filling metadata.
Use --fill to populate title and body from the commit messages,
and --[no-]draft to set the draft status.
Omitting the draft flag will leave the status unchanged of open CRs.
Use --fill to populate title and body from the commit messages.
The --[no-]draft flag marks the CR as draft or not.
Use the 'spice.submit.draft' configuration option
to mark new CRs as drafts (or not) by default,
skipping the prompt.
For updating Change Requests,
use --[no-]draft to change its draft status.
Without the flag, the draft status is not changed.
Use --no-publish to push branches without creating CRs.
This has no effect if a branch already has an open CR.
Use --update-only to only update branches with existing CRs,
and skip those that would create new CRs.
Use --nav-comment=false to disable navigation comments in CRs,
or --nav-comment=multiple to post those comments only if there are multiple CRs in the stack.
or --nav-comment=multiple to post those comments
only if there are multiple CRs in the stack.
**Flags**
@@ -397,6 +422,10 @@ Move a branch onto another branch
The current branch and its upstack will move onto the new base.
A prompt will allow selecting the new base for the branch.
Provide an argument to skip the prompt.
Use the --branch flag to target a different branch for the move.
For example, given the following stack with B checked out,
'gs upstack onto main' will have the following effect:
@@ -409,15 +438,6 @@ For example, given the following stack with B checked out,
Use 'gs branch onto' to leave the branch's upstack alone.
Use --branch to move a different branch than the current one.
A prompt will allow selecting the new base.
Use the spice.branchPrompt.sort configuration option
to specify the sort order of branches in the prompt.
Commonly used field names include "refname", "commiterdate", etc.
By default, branches are sorted by name.
Provide the new base name as an argument to skip the prompt.
**Arguments**
* `onto`: Destination branch
@@ -483,18 +503,27 @@ for the current branch and all branches below it until trunk.
Use --branch to start at a different branch.
Use --dry-run to print what would be submitted without submitting it.
For new Change Requests, a prompt will allow filling metadata.
Use --fill to populate title and body from the commit messages,
and --[no-]draft to set the draft status.
Omitting the draft flag will leave the status unchanged of open CRs.
Use --fill to populate title and body from the commit messages.
The --[no-]draft flag marks the CR as draft or not.
Use the 'spice.submit.draft' configuration option
to mark new CRs as drafts (or not) by default,
skipping the prompt.
For updating Change Requests,
use --[no-]draft to change its draft status.
Without the flag, the draft status is not changed.
Use --no-publish to push branches without creating CRs.
This has no effect if a branch already has an open CR.
Use --update-only to only update branches with existing CRs,
and skip those that would create new CRs.
Use --nav-comment=false to disable navigation comments in CRs,
or --nav-comment=multiple to post those comments only if there are multiple CRs in the stack.
or --nav-comment=multiple to post those comments
only if there are multiple CRs in the stack.
**Flags**
@@ -554,6 +583,9 @@ Use 'gs branch create' to automatically track new branches.
The base is guessed by comparing against other tracked branches.
Use --base to specify a base explicitly.
Use 'gs downstack track' from the topmost branch
to track a manully created stack of branches at once.
**Arguments**
* `branch`: Name of the branch to track
@@ -594,11 +626,9 @@ A prompt will allow selecting between tracked branches.
Provide a branch name as an argument to skip the prompt.
Use -u/--untracked to show untracked branches in the prompt.
Use the spice.branchPrompt.sort configuration option
to specify the sort order of branches in the prompt.
Commonly used field names include "refname", "commiterdate", etc.
By default, branches are sorted by name.
Use --detach to detach HEAD to the commit of the selected branch.
Use -n to print the selected branch name to stdout
without checking it out.
**Arguments**
@@ -700,14 +730,16 @@ gs branch (b) delete (d,rm) [<branches> ...] [flags]
Delete branches
The deleted branches and their commits are removed from the stack.
Branches above the deleted branches are rebased onto
the next branches available downstack.
Branches above the deleted branches are first rebased onto
the next branches available downstack,
or onto trunk if there are no branches available below.
A prompt will allow selecting the target branch if none are provided.
Use the spice.branchPrompt.sort configuration option
to specify the sort order of branches in the prompt.
Commonly used field names include "refname", "commiterdate", etc.
By default, branches are sorted by name.
Without any arguments,
a prompt will allow selecting the branch to delete.
By default, if the branch to be deleted has unmerged changes,
the deletion will be aborted.
Use --force to delete the branch regardless of unmerged changes.
**Arguments**
@@ -731,6 +763,7 @@ Commits from the current branch will be merged into its base
and the current branch will be deleted.
Branches above the folded branch will point
to the next branch downstack.
Use the --branch flag to target a different branch.
**Flags**
@@ -745,13 +778,13 @@ gs branch (b) split (sp) [flags]
Split a branch on commits
Splits the current branch into two or more branches at specific
commits, inserting the new branches into the stack
Splits the current branch into two or more branches
at specific commits,
inserting the new branches into the stack
at the positions of the commits.
Use the --branch flag to specify a different branch to split.
By default, the command will prompt for commits to introduce
splits at.
The command will prompt for commits to introduce splits at.
Supply the --at flag one or more times to split a branch
without a prompt.
@@ -767,10 +800,18 @@ For example:
# split at the previous commit
gs branch split --at HEAD^:newbranch
When prompted for branch names, you can reuse the original branch
name for one of the intermediate commits. When you do this, the
original branch will be reassigned to that commit, and you'll be
prompted to provide a name for the remaining HEAD commits.
If the original branch is assigned to one of the splits,
it is required to provide a new name for the commit at HEAD.
Fo example, if we have branch A with three commits:
┌─ A
│ abcdef1 Commit 3 (HEAD)
│ bcdef12 Commit 2
│ cdef123 Commit 1
trunk
A split at commit 2 using the branch name "A"
would require a new name to be provided for commit 3.
**Flags**
@@ -808,10 +849,12 @@ gs branch (b) edit (e)
Edit the commits in a branch
Starts an interactive rebase with only the commits
in this branch.
Following the rebase, branches upstack from this branch
will be restacked.
Starts an interactive rebase
with only the commits
from this branch.
After the rebase,
branches upstack from this branch will be restacked.
### gs branch rename
@@ -821,7 +864,7 @@ gs branch (b) rename (rn,mv) [<old-name> [<new-name>]]
Rename a branch
The following modes are supported:
The following usage modes are supported:
# Rename <old> to <new>
gs branch rename <old> <new>
@@ -832,9 +875,12 @@ The following modes are supported:
# Rename current branch interactively
gs branch rename
For branches renamed with 'git branch -m',
use 'gs branch track' and 'gs branch untrack'
to update the branch tracking.
If a branch was renamed outside of 'gs',
for example with 'git branch -m',
the branch tracking information will be out of date.
To fix this,
untrack the old branch name with 'gs branch untrack <old>',
and track the new branch name with 'gs branch track <new>'.
**Arguments**
@@ -865,9 +911,16 @@ gs branch (b) onto (on) [<onto>] [flags]
Move a branch onto another branch
The commits of the current branch are transplanted onto another
branch.
Branches upstack are moved to point to its original base.
Commits of the current branch
are transplanted onto another branch
while leaving the rest of the stack intact.
That is, branches above the current branch
are first rebased onto its original base,
and then the current branch is moved onto the new base.
A prompt will allow selecting the new base for the branch.
Provide an argument to skip the prompt.
Use the --branch flag to target a different branch for the move.
For example, given the following stack with B checked out,
running 'gs branch onto main' will move B onto main
@@ -880,14 +933,7 @@ and leave C on top of A.
┌─┴ A ├─┴ A
trunk trunk
Use --branch to move a different branch than the current one.
A prompt will allow selecting the new base.
Use the spice.branchPrompt.sort configuration option
to specify the sort order of branches in the prompt.
Commonly used field names include "refname", "commiterdate", etc.
By default, branches are sorted by name.
Provide the new base name as an argument to skip the prompt.
Use 'gs upstack onto' to also move the upstack branches.
**Arguments**
@@ -914,18 +960,24 @@ Use the --branch flag to target a different branch.
For new Change Requests, a prompt will allow filling metadata.
Use the --title and --body flags to skip the prompt,
or the --fill flag to use the commit message to fill them in.
The --draft flag marks the change request as a draft.
The --[no-]draft flag marks the CR as draft or not.
Use the 'spice.submit.draft' configuration option
to mark new CRs as drafts (or not) by default,
skipping the prompt.
For updating Change Requests,
use --draft/--no-draft to change its draft status.
use --[no-]draft to change its draft status.
Without the flag, the draft status is not changed.
Use --no-publish to push branches without creating CRs.
This has no effect if a branch already has an open CR.
Use --update-only to only update branches with existing CRs,
and skip those that would create new CRs.
Use --nav-comment=false to disable navigation comments in CRs,
or --nav-comment=multiple to post those comments only if there are multiple CRs in the stack.
or --nav-comment=multiple to post those comments
only if there are multiple CRs in the stack.
**Flags**
@@ -994,10 +1046,13 @@ Amend the current commit
Staged changes are amended into the topmost commit.
Branches upstack are restacked if necessary.
Use this as a shortcut for 'git commit --amend'
This is a shortcut for 'git commit --amend'
followed by 'gs upstack restack'.
An editor is opened to edit the commit message,
Use 'gs commit fixup' to amend commits
that are further downstack.
An editor is opened to edit the commit message
unless the --no-edit flag is given.
Use the -m/--message option to specify the message
on the command line.
@@ -1224,7 +1279,11 @@ gs bottom (D) [flags]
Move to the bottom of the stack
Checks out the bottom-most branch in the current branch's stack.
Use the -n flag to print the branch without checking it out.
Does nothing if already on the bottom-most branch,
and returns an error if on trunk.
Use -n to print the branch name to stdout
without checking it out.
**Flags**
+8
View File
@@ -28,6 +28,14 @@ func (cmd *dumpMarkdownCmd) Run(app *kong.Kong, shorts *shorthand.BuiltinSource)
}
defer func() { _ = ref.Close() }()
if help := app.Model.HelpFlag; help != nil {
help.Help = "Show help for the command"
help.Group = &kong.Group{
Key: "globals",
Title: "Global Flags:",
}
}
d := cliDumper{w: ref}
d.dump(app.Model)
+5
View File
@@ -0,0 +1,5 @@
package main
import "flag"
var _update = flag.Bool("update", false, "update golden files")
+529
View File
@@ -0,0 +1,529 @@
package main
import (
"fmt"
"go/doc/comment"
"io"
"slices"
"strings"
"github.com/alecthomas/kong"
)
const (
// _helpIndent specifies the base indentation level in spaces
// for help text content.
// This value is used when formatting wrapped text in the right column
// of two-column layouts (flags, arguments, commands).
//
// Example with _helpIndent = 2:
// --flag=VALUE This is help text that wraps
// to multiple lines with proper
// indentation applied.
_helpIndent = 2
// _helpColumnPadding specifies the number of spaces between
// the left and right columns in two-column layouts.
// This creates visual separation between column content.
//
// Example with _helpColumnPadding = 4:
// --flag=VALUE Help text here
// ^^^^^ left ^^^^ right column
// column (4 spaces separate them)
_helpColumnPadding = 4
// _maxLeftColumnRatio defines the maximum width of the left column
// as a ratio of terminal width (37.5% = 0.375).
// This prevents the left column from consuming too much horizontal space
// in wide terminals, ensuring the right column has adequate room
// for help text.
//
// For an 80-character terminal: max left width = 80 * 0.375 = 30 chars
// For a 120-character terminal: max left width = 120 * 0.375 = 45 chars
_maxLeftColumnRatio = 0.375
// _minLeftColumnWidth specifies the minimum width in characters
// for the left column in two-column layouts.
// This ensures readability even in narrow terminals
// by guaranteeing a baseline width for flags, commands, and arguments.
//
// When the left column content is shorter than this minimum,
// the column is still sized to this width for consistent alignment.
_minLeftColumnWidth = 30
)
// helpPrinter is a custom help printer for git-spice.
// It acts similarly to Kong's default help output to show configuration options.
func helpPrinter(_ kong.HelpOptions, ctx *kong.Context) error {
// The default help flag text has a period at the end,
// which doesn't match the rest of our help text.
// Remove the period and place it in the same group
// as the other global flags.
if help := ctx.Model.HelpFlag; help != nil {
help.Help = "Show help for the command"
help.Group = &kong.Group{
Key: "globals",
Title: "Global Flags:",
}
}
w := newHelpWriter(ctx.Stdout)
selected := ctx.Selected()
if selected == nil {
w.printApp(ctx.Model)
} else {
w.printCommand(ctx.Model, selected)
}
// For the help of the top-level command,
// print a note about shorthand aliases.
if len(ctx.Command()) == 0 {
w.Print("")
w.Print("Aliases can be combined to form shorthands for commands. For example:")
w.Printf(" %s bc => %s branch create", ctx.Model.Name, ctx.Model.Name)
w.Printf(" %s cc => %s commit create", ctx.Model.Name, ctx.Model.Name)
}
return nil
}
// helpWriter builds formatted help output with support for indentation
// and word wrapping.
type helpWriter struct {
// indent is the current indentation prefix for all printed lines.
indent string
// width is the maximum line width for word wrapping.
width int
// out is the writer where output is written.
out io.Writer
}
func newHelpWriter(out io.Writer) *helpWriter {
wrapWidth := guessWidth(out)
return &helpWriter{
width: wrapWidth,
out: out,
}
}
// TODO: io.Writer implementation that handles indentation?
// Printf renders formatted text with the current indentation.
// Resulting text should not have newlines.
func (w *helpWriter) Printf(format string, args ...any) {
w.Print(fmt.Sprintf(format, args...))
}
// Print writes a single line of text with the current indentation.
// text should not have newlines.
func (w *helpWriter) Print(text string) {
line := strings.TrimRight(w.indent+text, " ")
// Ignore write errors - help output is best-effort
_, _ = io.WriteString(w.out, line+"\n")
}
// Indent returns a new helpWriter with increased indentation.
func (w *helpWriter) Indent() *helpWriter {
return &helpWriter{
indent: w.indent + " ",
out: w.out,
width: w.width - 2,
}
}
// Wrap word-wraps the given text to fit within the current width
// and prints each line with the current indentation.
//
// Uses GoDoc formatting to perform wrapping
// that respects paragraph breaks and indentation in the input text.
func (w *helpWriter) Wrap(text string) {
formatted := strings.TrimSpace(formatHelpText(text, " ", w.width))
for line := range strings.SplitSeq(formatted, "\n") {
w.Print(line)
}
}
func (w *helpWriter) printApp(app *kong.Application) {
w.Printf("Usage: %s%s", app.Name, app.Summary())
w.printNodeDetail(app.Node, true)
cmds := app.Leaves(true)
if len(cmds) > 0 && app.HelpFlag != nil {
w.Print("")
w.Printf(`Run "%s <command> --help" for more information on a command.`, app.Name)
}
}
func (w *helpWriter) printCommand(app *kong.Application, cmd *kong.Node) {
w.Printf("Usage: %s %s", app.Name, cmd.Summary())
w.printNodeDetail(cmd, true)
}
func (w *helpWriter) printNodeDetail(node *kong.Node, hide bool) {
if node.Help != "" {
w.Print("")
w.Wrap(node.Help)
}
if node.Detail != "" {
w.Print("")
w.Wrap(node.Detail)
}
if len(node.Positional) > 0 {
w.Print("")
w.Print("Arguments:")
w.Indent().writePositionals(node.Positional)
}
w.printFlags(node, hide)
cmds := node.Leaves(hide)
if len(cmds) > 0 {
for _, group := range collectCommandGroups(cmds) {
w.Print("")
if group.Metadata.Title != "" {
w.Wrap(group.Metadata.Title)
}
if group.Metadata.Description != "" {
w.Indent().Wrap(group.Metadata.Description)
w.Print("")
}
w.Indent().writeCompactCommandList(group.Commands)
}
}
// Print config-only options (hidden:"" but with config tags).
w.writeConfigOnlyOptions(node)
}
// printFlags prints all flags for the given node.
func (w *helpWriter) printFlags(node *kong.Node, hide bool) {
flags := node.AllFlags(hide)
if len(flags) == 0 {
return
}
for _, group := range collectFlagGroups(flags) {
w.Print("")
if group.Metadata.Title != "" {
w.Wrap(group.Metadata.Title)
}
if group.Metadata.Description != "" {
w.Indent().Wrap(group.Metadata.Description)
w.Print("")
}
w.Indent().writeFlags(group.Flags)
}
}
func (w *helpWriter) writeCompactCommandList(cmds []*kong.Node) {
var rows [][2]string
for _, cmd := range cmds {
if cmd.Hidden {
continue
}
rows = append(rows, [2]string{cmd.Path(), cmd.Help})
}
w.writeTwoColumns(rows)
}
func (w *helpWriter) writePositionals(args []*kong.Positional) {
var rows [][2]string
for _, arg := range args {
rows = append(rows, [2]string{arg.Summary(), formatValueHelp(arg)})
}
w.writeTwoColumns(rows)
}
func (w *helpWriter) writeFlags(groups [][]*kong.Flag) {
var rows [][2]string
var haveShort bool
for _, group := range groups {
for _, flag := range group {
if flag.Short != 0 {
haveShort = true
break
}
}
}
for i, group := range groups {
if i > 0 {
rows = append(rows, [2]string{"", ""})
}
for _, flag := range group {
if flag.Hidden {
continue
}
name, help := formatFlag(haveShort, flag)
rows = append(rows, [2]string{name, help})
}
}
w.writeTwoColumns(rows)
}
func (w *helpWriter) writeConfigOnlyOptions(node *kong.Node) {
// Collect hidden flags that have config tags
var configFlags []*kong.Flag
for _, flag := range node.Flags {
if !flag.Hidden {
continue
}
key := flag.Tag.Get("config")
if key == "" || key[0] == '@' {
// Skip flags without config tags
// or git config references (starting with '@').
continue
}
configFlags = append(configFlags, flag)
}
if len(configFlags) == 0 {
return
}
// Sort config flags by their config key for consistent output
slices.SortFunc(configFlags, func(a, b *kong.Flag) int {
return strings.Compare(a.Tag.Get("config"), b.Tag.Get("config"))
})
w.Print("")
w.Wrap("Configuration (🔧):")
var rows [][2]string
for _, flag := range configFlags {
name, help := formatConfig(flag)
rows = append(rows, [2]string{name, help})
}
w.Indent().writeTwoColumns(rows)
}
// writeTwoColumns formats and prints rows in a two-column layout.
// The left column width is determined by the widest entry,
// capped at a maximum based on terminal width.
// Text in the right column is wrapped if it exceeds available space.
func (w *helpWriter) writeTwoColumns(rows [][2]string) {
maxLeft := max(int(float64(w.width)*_maxLeftColumnRatio), _minLeftColumnWidth)
// Find widest left column entry (up to maxLeft)
leftColWidth := 0
for _, row := range rows {
if c := len(row[0]); c > leftColWidth && c < maxLeft {
leftColWidth = c
}
}
offsetStr := strings.Repeat(" ", leftColWidth+_helpColumnPadding)
for _, row := range rows {
codePrefix := strings.Repeat(" ", _helpIndent)
rightColWidth := w.width - leftColWidth - _helpColumnPadding
formatted := strings.TrimRight(formatHelpText(row[1], codePrefix, rightColWidth), "\n")
lines := strings.Split(formatted, "\n")
line := fmt.Sprintf("%-*s", leftColWidth, row[0])
// If left column fits inline, put first line of right column on same line
if len(row[0]) < maxLeft {
line += fmt.Sprintf("%*s%s", _helpColumnPadding, "", lines[0])
lines = lines[1:]
}
w.Print(line)
for _, line := range lines {
w.Printf("%s%s", offsetStr, line)
}
}
}
// helpFlagGroup represents a group of flags with their metadata.
type helpFlagGroup struct {
Metadata *kong.Group
Flags [][]*kong.Flag
}
// collectFlagGroups organizes flags into groups based on their group key.
// Ungrouped flags are always returned first.
// Groups are returned in order of first appearance.
func collectFlagGroups(flags [][]*kong.Flag) []helpFlagGroup {
// Track groups in order of appearance
var groups []*kong.Group
seenGroups := make(map[string]bool)
// Flags grouped by their group key
flagsByGroup := make(map[string][][]*kong.Flag)
for _, levelFlags := range flags {
levelFlagsByGroup := make(map[string][]*kong.Flag)
for _, flag := range levelFlags {
key := ""
if flag.Group != nil {
key = flag.Group.Key
if !seenGroups[key] {
groups = append(groups, flag.Group)
seenGroups[key] = true
}
}
levelFlagsByGroup[key] = append(levelFlagsByGroup[key], flag)
}
for key, flags := range levelFlagsByGroup {
flagsByGroup[key] = append(flagsByGroup[key], flags)
}
}
var out []helpFlagGroup
// Ungrouped flags are always displayed first
if ungroupedFlags, ok := flagsByGroup[""]; ok {
out = append(out, helpFlagGroup{
Metadata: &kong.Group{Title: "Flags:"},
Flags: ungroupedFlags,
})
}
for _, group := range groups {
out = append(out, helpFlagGroup{Metadata: group, Flags: flagsByGroup[group.Key]})
}
return out
}
// helpCommandGroup represents a group of commands with their metadata.
type helpCommandGroup struct {
Metadata *kong.Group
Commands []*kong.Node
}
// collectCommandGroups organizes commands into groups based on their group key.
// Ungrouped commands are always returned first.
// Groups are returned in order of first appearance.
func collectCommandGroups(nodes []*kong.Node) []helpCommandGroup {
// Track groups in order of appearance
var groups []*kong.Group
seenGroups := make(map[string]struct{})
// Nodes grouped by their group key
nodesByGroup := make(map[string][]*kong.Node)
for _, node := range nodes {
var key string
if group := node.ClosestGroup(); group != nil {
key = group.Key
if _, ok := seenGroups[key]; !ok {
groups = append(groups, group)
seenGroups[key] = struct{}{}
}
}
nodesByGroup[key] = append(nodesByGroup[key], node)
}
var out []helpCommandGroup
// Ungrouped nodes are always displayed first
if ungroupedNodes, ok := nodesByGroup[""]; ok {
out = append(out, helpCommandGroup{
Metadata: &kong.Group{Title: "Commands:"},
Commands: ungroupedNodes,
})
}
for _, group := range groups {
out = append(out, helpCommandGroup{Metadata: group, Commands: nodesByGroup[group.Key]})
}
return out
}
// formatFlag returns the formatted flag name and help text for the given flag.
//
// haveShort indicates whether any flags in the current group have short names,
// which affects alignment.
func formatFlag(haveShort bool, flag *kong.Flag) (flagName, flagHelp string) {
var sb strings.Builder
name := flag.Name
isBool := flag.IsBool()
isCounter := flag.IsCounter()
var short string
if flag.Short != 0 {
short = "-" + string(flag.Short) + ", "
} else if haveShort {
short = " "
}
if isBool && flag.Tag.Negatable == "_" {
name = "[no-]" + name
} else if isBool && flag.Tag.Negatable != "" {
name += "/" + flag.Tag.Negatable
}
sb.WriteString(fmt.Sprintf("%s--%s", short, name))
if !isBool && !isCounter {
sb.WriteString("=" + flag.FormatPlaceHolder())
}
flagName = sb.String()
flagHelp = formatValueHelp(flag.Value)
// Add config annotation if this flag has a config override
if configKey := flag.Tag.Get("config"); configKey != "" && configKey[0] != '@' {
flagHelp = fmt.Sprintf("%s (🔧 spice.%s)", flagHelp, configKey)
}
return flagName, flagHelp
}
func formatConfig(flag *kong.Flag) (configName, configHelp string) {
configName = "spice." + flag.Tag.Get("config")
configHelp = formatValueHelp(flag.Value)
return configName, configHelp
}
func formatValueHelp(value *kong.Value) string {
// Skip adding environment variables if:
// - there are no env vars to add, or
// - the help text already contains ${env} interpolation
if len(value.Tag.Envs) == 0 || strings.Contains(value.OrigHelp, "${env}") {
return value.Help
}
suffix := "(" + formatEnvs(value.Tag.Envs) + ")"
help := strings.TrimSuffix(value.Help, ".")
if help == "" {
return suffix
}
if help == value.Help {
// No period was trimmed
return help + " " + suffix
}
// Period was trimmed, add it back after suffix
return help + " " + suffix + "."
}
func formatEnvs(envs []string) string {
formatted := make([]string, len(envs))
for i := range envs {
formatted[i] = "$" + envs[i]
}
return strings.Join(formatted, ", ")
}
func guessWidth(w io.Writer) int {
// Try to get terminal width
type widther interface {
Width() int
}
if wt, ok := w.(widther); ok {
return wt.Width()
}
// Default to 80 columns
return 80
}
func formatHelpText(text string, codePrefix string, width int) string {
var parser comment.Parser
doc := parser.Parse(text)
return string((&comment.Printer{
TextWidth: width,
TextCodePrefix: codePrefix,
}).Text(doc))
}
+104
View File
@@ -0,0 +1,104 @@
package main
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"github.com/alecthomas/kong"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
//go:generate go test -run ^TestHelp$ -update
func TestHelp(t *testing.T) {
// Build Kong parser with the same configuration as main
var cmd mainCmd
parser, err := kong.New(&cmd,
kong.Name("gs"),
kong.Description("gs (git-spice) is a command line tool for stacking Git branches."),
kong.Help(helpPrinter),
kong.Exit(func(int) {}), // Don't actually exit in tests
kong.Vars{
"defaultPrompt": "true",
},
)
require.NoError(t, err)
// Collect all commands to test
var commands []struct {
name string
path []string
}
// Add root help
commands = append(commands, struct {
name string
path []string
}{
name: "gs",
path: []string{},
})
// Collect all leaf commands (exclude hidden)
for _, node := range parser.Model.Leaves(true) {
// Build the command path
var path []string
for n := node; n != nil && n.Type == kong.CommandNode; n = n.Parent {
path = append([]string{n.Name}, path...)
}
if len(path) > 0 {
commands = append(commands, struct {
name string
path []string
}{
name: strings.Join(path, " "),
path: path,
})
}
}
for _, tc := range commands {
t.Run(tc.name, func(t *testing.T) {
// Generate help output by creating a parser with a buffer
var helpBuf bytes.Buffer
testParser, err := kong.New(&cmd,
kong.Name("gs"),
kong.Description("gs (git-spice) is a command line tool for stacking Git branches."),
kong.ConfigureHelp(kong.HelpOptions{Compact: true}),
kong.Help(helpPrinter),
kong.Exit(func(int) {}),
kong.Writers(&helpBuf, &helpBuf),
kong.Vars{
"defaultPrompt": "true",
},
)
require.NoError(t, err)
_, _ = testParser.Parse(append(tc.path, "--help"))
actual := helpBuf.String()
// Determine golden file path
filename := strings.ReplaceAll(tc.name, " ", "_") + ".txt"
goldenPath := filepath.Join("testdata", "help", filename)
if *_update {
// Update mode: write actual output to golden file
err := os.MkdirAll(filepath.Dir(goldenPath), 0o755)
require.NoError(t, err)
err = os.WriteFile(goldenPath, []byte(actual), 0o644)
require.NoError(t, err)
t.Logf("Updated golden file: %s", goldenPath)
} else {
// Test mode: compare against golden file
expected, err := os.ReadFile(goldenPath)
require.NoError(t, err, "failed to read golden file: %s", goldenPath)
assert.Equal(t, string(expected), actual)
}
})
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ type Options struct {
DryRun bool `short:"n" xor:"detach-or-dry-run" help:"Print the target branch without checking it out"`
Detach bool `xor:"detach-or-dry-run" help:"Detach HEAD after checking out"`
Verbose bool `name:"checkout-verbose" hidden:"" default:"true" config:"checkout.verbose"`
Verbose bool `name:"checkout-verbose" help:"Print information about the checked out branch." hidden:"" default:"true" config:"checkout.verbose"`
}
// Store provides access to the git-spice state.
+3 -3
View File
@@ -146,7 +146,7 @@ type Options struct {
// when creating new Change Requests.
//
// --draft/--no-draft will override this value.
DraftDefault bool `config:"submit.draft" hidden:"" default:"false"`
DraftDefault bool `config:"submit.draft" help:"Default value for --draft when creating change requests." hidden:"" default:"false"`
// TODO: Other creation options e.g.:
// - assignees
@@ -154,7 +154,7 @@ type Options struct {
// - reviewers
Labels []string `name:"label" short:"l" help:"Add labels to the change request. Pass multiple times or separate with commas."`
ConfiguredLabels []string `name:"configured-labels" hidden:"" config:"submit.label"` // merged with Labels
ConfiguredLabels []string `name:"configured-labels" help:"Default labels to add to change requests." hidden:"" config:"submit.label"` // merged with Labels
// ListTemplatesTimeout controls the timeout for listing CR templates.
ListTemplatesTimeout time.Duration `hidden:"" config:"submit.listTemplatesTimeout" help:"Timeout for listing CR templates" default:"1s"`
@@ -211,7 +211,7 @@ func (s *NavCommentSync) UnmarshalText(bs []byte) error {
// BatchOptions defines options
// that are only available to batch submit operations.
type BatchOptions struct {
UpdateOnlyDefault bool `config:"submit.updateOnly" hidden:"" default:"false"`
UpdateOnlyDefault bool `config:"submit.updateOnly" help:"Default value for --update-only in batch submit operations." hidden:"" default:"false"`
}
// BatchRequest is a request to submit one or more change requests.
+1 -1
View File
@@ -150,7 +150,7 @@ type TrunkOptions struct {
// TODO: flag to not delete merged branches?
Restack bool `help:"Restack the current stack after syncing"`
ClosedChanges ClosedChanges `default:"ask" config:"repoSync.closedChanges" enum:"ask,ignore" help:"How to handle closed change requests" hidden:""`
ClosedChanges ClosedChanges `default:"ask" config:"repoSync.closedChanges" enum:"ask,ignore" help:"How to handle closed change requests. One of 'ask' and 'ignore'." hidden:""`
}
// SyncTrunk syncs the trunk branch with the remote repository,
+4 -4
View File
@@ -92,14 +92,14 @@ type ListHandler interface {
type branchLogCmd struct {
list.Options
ChangeFormat changeFormat `config:"log.crFormat" hidden:"" default:"id"`
ChangeFormatShort *changeFormat `config:"logShort.crFormat" hidden:""`
ChangeFormatLong *changeFormat `config:"logLong.crFormat" hidden:""`
ChangeFormat changeFormat `config:"log.crFormat" help:"Format for displaying change request information. One of 'id' or 'url'." hidden:"" default:"id"`
ChangeFormatShort *changeFormat `config:"logShort.crFormat" help:"Format for displaying change request information in short log. One of 'id' or 'url', defaults to crFormat." hidden:""`
ChangeFormatLong *changeFormat `config:"logLong.crFormat" help:"Format for displaying change request information in long log. One of 'id' or 'url', defaults to crFormat." hidden:""`
CRStatus bool `name:"cr-status" short:"S" config:"log.crStatus" help:"Request and include information about the Change Request" default:"false" negatable:""`
// TODO: When needed, add a crStatusFormat config to control presentation.
PushStatusFormat pushStatusFormat `config:"log.pushStatusFormat" help:"Show indicator for branches that are out of sync with their remotes." hidden:"" default:"true"`
PushStatusFormat pushStatusFormat `config:"log.pushStatusFormat" help:"Show indicator for branches that are out of sync with their remotes. One of 'true', 'false' and 'aheadbehind'." hidden:"" default:"true"`
JSON bool `name:"json" released:"v0.18.0" help:"Write to stdout as a stream of JSON objects in an unspecified order"`
}
+1 -31
View File
@@ -141,42 +141,12 @@ func main() {
"defaultPrompt": strconv.FormatBool(isatty.IsTerminal(os.Stdin.Fd())),
},
kong.UsageOnError(),
kong.ConfigureHelp(kong.HelpOptions{Compact: true}),
kong.Help(func(options kong.HelpOptions, ctx *kong.Context) error {
if err := kong.DefaultHelpPrinter(options, ctx); err != nil {
return err
}
// For the help of the top-level command,
// print a note about shorthand aliases.
if len(ctx.Command()) == 0 {
_, _ = fmt.Fprintf(ctx.Stdout,
"\nAliases can be combined to form shorthands for commands. For example:\n"+
" %[1]v bc => %[1]v branch create\n"+
" %[1]v cc => %[1]v commit create\n",
cmdName,
)
}
return nil
}),
kong.Help(helpPrinter),
)
if err != nil {
panic(err)
}
// The default help flag text has a period at the end,
// which doesn't match the rest of our help text.
// Remove the period and place it in the same group
// as the other global flags.
if help := parser.Model.HelpFlag; help != nil {
help.Help = "Show help for the command"
help.Group = &kong.Group{
Key: "globals",
Title: "Global Flags:",
}
}
builtinShorthands, err := shorthand.NewBuiltin(parser.Model)
if err != nil {
panic(err)
+1 -2
View File
@@ -30,8 +30,7 @@ import (
)
var (
_update = flag.Bool("update", false, "update golden files")
_debug = flag.Bool("debug", false, "enable debug logging")
_debug = flag.Bool("debug", false, "enable debug logging")
_shardIndex = flag.Int("shard-index", 0, "index of the test shard to run")
_shardCount = flag.Int("shard-count", 1, "total number of test shards")
+31
View File
@@ -0,0 +1,31 @@
Usage: gs auth login [flags]
Log in to a service
A prompt will allow selecting between available authentication methods.
Available methods include:
- OAuth: Web-based authentication flow - GitHub App (GitHub only):
Authenticate using a GitHub App installation - Personal Access Token: Directly
provide a personal access token - CLI: Use the GitHub or GitLab CLI tool for
authentication (if installed)
The differences between them are explained in the prompt.
The authentication token is stored in a system-provided secure storage if
available. Use 'gs auth logout' to log out and delete the token from storage.
Fails if already logged in. Use --refresh to force a refresh of the
authentication token or change the authentication method.
Flags:
--forge=NAME Name of the forge to log into
--refresh Force a refresh of the authentication token
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+18
View File
@@ -0,0 +1,18 @@
Usage: gs auth logout [flags]
Log out of a service
The stored authentication information is deleted. Use 'gs auth login' to log in
again.
Does not do anything if not logged in.
Flags:
--forge=NAME Name of the forge to log into
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+15
View File
@@ -0,0 +1,15 @@
Usage: gs auth status [flags]
Show current login status
Exits with a non-zero code if not logged in.
Flags:
--forge=NAME Name of the forge to log into
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+22
View File
@@ -0,0 +1,22 @@
Usage: gs bottom (D) [flags]
Move to the bottom of the stack
Checks out the bottom-most branch in the current branch's stack. Does nothing if
already on the bottom-most branch, and returns an error if on trunk.
Use -n to print the branch name to stdout without checking it out.
Flags:
-n, --dry-run Print the target branch without checking it out
--detach Detach HEAD after checking out
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.checkout.verbose Print information about the checked out branch.
+35
View File
@@ -0,0 +1,35 @@
Usage: gs branch (b) checkout (co) [<branch>] [flags]
Switch to a branch
A prompt will allow selecting between tracked branches. Provide a branch name as
an argument to skip the prompt.
Use -u/--untracked to show untracked branches in the prompt. Use --detach to
detach HEAD to the commit of the selected branch. Use -n to print the selected
branch name to stdout without checking it out.
Arguments:
[<branch>] Name of the branch to checkout
Flags:
-n, --dry-run Print the target branch without checking it out
--detach Detach HEAD after checking out
-u, --untracked Show untracked branches if one isn't supplied (🔧
spice.branchCheckout.showUntracked)
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.branchCheckout.trackUntrackedPrompt
Whether to prompt to track untracked branches when
checked out.
spice.branchPrompt.sort Sort branches by the given field. Common values
include 'refname', 'commiterdate', etc. Defaults to
branch name.
spice.checkout.verbose Print information about the checked out branch.
+83
View File
@@ -0,0 +1,83 @@
Usage: gs branch (b) create (c) [<name>] [flags]
Create a new branch
Staged changes will be committed to the new branch. If there are no staged
changes, an empty commit will be created. Use -a/--all to automatically stage
modified and deleted files, just like 'git commit -a'. Use --no-commit to create
the branch without committing. -m/--message always implies --commit.
If a branch name is not provided, it will be generated from the
commit message. If the 'spice.branchCreate.prefix' configuration
option is set, branch names will be prefixed with its value. If the
'spice.branchCreate.generatedBranchNameLimit' configuration option is set,
auto-generated branch names will be truncated to that length at word boundaries
(defaults to 32).
The new branch will use the current branch as its base. Use --target to specify
a different base branch.
--insert will move the branches upstack from the target branch on top of the new
branch. --below will create the new branch below the target branch.
For example, given the following stack, with A checked out:
┌── C
┌─┴ B
┌─┴ A ◀
trunk
'gs branch create X' will have the following effects with different flags:
gs branch create X
default │ --insert │ --below
──────────┼──────────────┼──────────
┌── X │ ┌── C │ ┌── C
│ ┌── C │ ┌─┴ B │ ┌─┴ B
├─┴ B │ ┌─┴ X │ ┌─┴ A
┌─┴ A │ ┌─┴ A │ ┌─┴ X
trunk │ trunk │ trunk
In all cases above, use of -t/--target flag will change the target (A) to the
specified branch:
gs branch create X --target B
default │ --insert │ --below
──────────┼──────────────┼────────────
┌── X │ ┌── C │ ┌── C
├── C │ ┌─┴ X │ ┌─┴ B
┌─┴ B │ ┌─┴ B │ ┌─┴ X
┌─┴ A │ ┌─┴ A │ ┌─┴ A
trunk │ trunk │ trunk
Arguments:
[<name>] Name of the new branch
Flags:
--insert Restack the upstack of the target branch onto the new
branch
--below Place the branch below the target branch and restack
its upstack
-t, --target=BRANCH Branch to create the new branch above/below
-a, --all Automatically stage modified and deleted files
-m, --message=MSG Commit message
--no-verify Bypass pre-commit and commit-msg hooks.
--signoff Add Signed-off-by trailer to the commit message (🔧
spice.commit.signoff)
--[no-]commit Commit staged changes to the new branch, or create an
empty commit (🔧 spice.branchCreate.commit)
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.branchCreate.generatedBranchNameLimit
Maximum length of auto-generated branch names
(truncated at word boundaries). Defaults to 32.
spice.branchCreate.prefix Always add a prefix to branch names.
+30
View File
@@ -0,0 +1,30 @@
Usage: gs branch (b) delete (d,rm) [<branches> ...] [flags]
Delete branches
The deleted branches and their commits are removed from the stack. Branches
above the deleted branches are first rebased onto the next branches available
downstack, or onto trunk if there are no branches available below.
Without any arguments, a prompt will allow selecting the branch to delete.
By default, if the branch to be deleted has unmerged changes, the deletion will
be aborted. Use --force to delete the branch regardless of unmerged changes.
Arguments:
[<branches> ...] Names of the branches to delete
Flags:
--force Force deletion of the branch
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.branchPrompt.sort Sort branches by the given field. Common values
include 'refname', 'commiterdate', etc. Defaults to
branch name.
+14
View File
@@ -0,0 +1,14 @@
Usage: gs branch (b) edit (e)
Edit the commits in a branch
Starts an interactive rebase with only the commits from this branch.
After the rebase, branches upstack from this branch will be restacked.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+19
View File
@@ -0,0 +1,19 @@
Usage: gs branch (b) fold (fo) [flags]
Merge a branch into its base
Commits from the current branch will be merged into its base and the current
branch will be deleted. Branches above the folded branch will point to the next
branch downstack.
Use the --branch flag to target a different branch.
Flags:
--branch=NAME Name of the branch
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+42
View File
@@ -0,0 +1,42 @@
Usage: gs branch (b) onto (on) [<onto>] [flags]
Move a branch onto another branch
Commits of the current branch are transplanted onto another branch while leaving
the rest of the stack intact. That is, branches above the current branch are
first rebased onto its original base, and then the current branch is moved onto
the new base.
A prompt will allow selecting the new base for the branch. Provide an argument
to skip the prompt. Use the --branch flag to target a different branch for the
move.
For example, given the following stack with B checked out, running 'gs branch
onto main' will move B onto main and leave C on top of A.
gs branch onto main
┌── C ┌── B ◀
┌─┴ B ◀ │ ┌── C
┌─┴ A ├─┴ A
trunk trunk
Use 'gs upstack onto' to also move the upstack branches.
Arguments:
[<onto>] Destination branch
Flags:
--branch=NAME Branch to move
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.branchPrompt.sort Sort branches by the given field. Common values
include 'refname', 'commiterdate', etc. Defaults to
branch name.
+30
View File
@@ -0,0 +1,30 @@
Usage: gs branch (b) rename (rn,mv) [<old-name> [<new-name>]]
Rename a branch
The following usage modes are supported:
# Rename <old> to <new>
gs branch rename <old> <new>
# Rename current branch to <new>
gs branch rename <new>
# Rename current branch interactively
gs branch rename
If a branch was renamed outside of 'gs', for example with 'git branch -m',
the branch tracking information will be out of date. To fix this, untrack the
old branch name with 'gs branch untrack <old>', and track the new branch name
with 'gs branch track <new>'.
Arguments:
[<old-name>] Old name of the branch
[<new-name>] New name of the branch
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+16
View File
@@ -0,0 +1,16 @@
Usage: gs branch (b) restack (r) [flags]
Restack a branch
The current branch will be rebased onto its base, ensuring a linear history.
Use --branch to target a different branch.
Flags:
--branch=NAME Branch to restack
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+45
View File
@@ -0,0 +1,45 @@
Usage: gs branch (b) split (sp) [flags]
Split a branch on commits
Splits the current branch into two or more branches at specific commits,
inserting the new branches into the stack at the positions of the commits.
Use the --branch flag to specify a different branch to split.
The command will prompt for commits to introduce splits at. Supply the --at flag
one or more times to split a branch without a prompt.
--at COMMIT:NAME
Where COMMIT resolves to a commit per gitrevisions(7), and NAME is the name of
the new branch. For example:
# split at a specific commit
gs branch split --at 1234567:newbranch
# split at the previous commit
gs branch split --at HEAD^:newbranch
If the original branch is assigned to one of the splits, it is required to
provide a new name for the commit at HEAD. Fo example, if we have branch A with
three commits:
┌─ A
│ abcdef1 Commit 3 (HEAD)
│ bcdef12 Commit 2
│ cdef123 Commit 1
trunk
A split at commit 2 using the branch name "A" would require a new name to be
provided for commit 3.
Flags:
--at=COMMIT:NAME,... Commits to split the branch at.
--branch=NAME Branch to split commits of.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+23
View File
@@ -0,0 +1,23 @@
Usage: gs branch (b) squash (sq) [flags]
Squash a branch into one commit
Squash all commits in the current branch into a single commit and restack
upstack branches.
An editor will open to edit the commit message of the squashed commit. Use the
-m/--message flag to specify a commit message without editing.
Flags:
--no-verify Bypass pre-commit and commit-msg hooks.
--no-edit Do not open an editor to edit the squashed commit
message. Only applicable if --message is not used.
-m, --message=MSG Use the given message as the commit message.
--branch=NAME Branch to squash. Defaults to current branch.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+71
View File
@@ -0,0 +1,71 @@
Usage: gs branch (b) submit (s) [flags]
Submit a branch
A Change Request is created for the current branch, or updated if it already
exists. Use the --branch flag to target a different branch.
For new Change Requests, a prompt will allow filling metadata. Use the --title
and --body flags to skip the prompt, or the --fill flag to use the commit
message to fill them in. The --[no-]draft flag marks the CR as draft or not.
Use the 'spice.submit.draft' configuration option to mark new CRs as drafts (or
not) by default, skipping the prompt.
For updating Change Requests, use --[no-]draft to change its draft status.
Without the flag, the draft status is not changed.
Use --no-publish to push branches without creating CRs. This has no effect if a
branch already has an open CR.
Use --update-only to only update branches with existing CRs, and skip those that
would create new CRs.
Use --nav-comment=false to disable navigation comments in CRs, or
--nav-comment=multiple to post those comments only if there are multiple CRs in
the stack.
Flags:
-n, --dry-run Don't actually submit the stack
-c, --fill Fill in the change title and body from the commit
messages
--[no-]draft Whether to mark change requests as drafts
--[no-]publish Whether to create CRs for pushed branches. Defaults
to true. (🔧 spice.submit.publish)
-w, --web Open submitted changes in a web browser. Accepts
an optional argument: 'true', 'false', 'created'.
(🔧 spice.submit.web)
--nav-comment=true Whether to add a navigation comment to the change
request. Must be one of: true, false, multiple.
(🔧 spice.submit.navigationComment)
--force Force push, bypassing safety checks
--no-verify Bypass pre-push hooks when pushing to the remote.
-u, --[no-]update-only Only update existing change requests, do not create
new ones
-l, --label=LABEL,... Add labels to the change request. Pass multiple
times or separate with commas.
--no-web Alias for --web=false.
--title=TITLE Title of the change request
--body=BODY Body of the change request
--branch=NAME Branch to submit
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.submit.draft Default value for --draft when creating change
requests.
spice.submit.label Default labels to add to change requests.
spice.submit.listTemplatesTimeout
Timeout for listing CR templates
spice.submit.navigationCommentStyle.marker
Marker to use for the current change in navigation
comments. Defaults to '◀'.
spice.submit.navigationCommentSync
Which navigation comment to sync. Must be one of:
branch, downstack.
spice.submit.template Default template to use when multiple templates are
available
+25
View File
@@ -0,0 +1,25 @@
Usage: gs branch (b) track (tr) [<branch>] [flags]
Track a branch
A branch must be tracked to be able to run gs operations on it. Use 'gs branch
create' to automatically track new branches.
The base is guessed by comparing against other tracked branches. Use --base to
specify a base explicitly.
Use 'gs downstack track' from the topmost branch to track a manully created
stack of branches at once.
Arguments:
[<branch>] Name of the branch to track
Flags:
-b, --base=BRANCH Base branch this merges into
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+19
View File
@@ -0,0 +1,19 @@
Usage: gs branch (b) untrack (untr) [<branch>]
Forget a tracked branch
The current branch is deleted from git-spice's data store but not deleted from
the repository. Branches upstack from it are not affected, and will use the next
branch downstack as their new base.
Provide a branch name as an argument to target a different branch.
Arguments:
[<branch>] Name of the branch to untrack. Defaults to current.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+41
View File
@@ -0,0 +1,41 @@
Usage: gs commit (c) amend (a) [flags]
Amend the current commit
Staged changes are amended into the topmost commit. Branches upstack are
restacked if necessary. This is a shortcut for 'git commit --amend' followed by
'gs upstack restack'.
Use 'gs commit fixup' to amend commits that are further downstack.
An editor is opened to edit the commit message unless the --no-edit flag is
given. Use the -m/--message option to specify the message on the command line.
Git hooks are run unless the --no-verify flag is given.
Use the -a/--all flag to stage all changes before committing.
To prevent accidental amends on the trunk branch, a prompt will require
confirmation when amending on trunk. The --no-prompt flag can be used to skip
this prompt in scripts.
Flags:
-a, --all Stage all changes before committing.
--allow-empty Create a commit even if it contains no changes.
-m, --message=MSG Use the given message as the commit message.
--no-edit Don't edit the commit message
--no-verify Bypass pre-commit and commit-msg hooks.
--signoff Add Signed-off-by trailer to the commit message (🔧
spice.commit.signoff)
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.branchCreate.generatedBranchNameLimit
Maximum length of auto-generated branch names
(truncated at word boundaries). Defaults to 32.
spice.branchCreate.prefix Always add a prefix to branch names.
+33
View File
@@ -0,0 +1,33 @@
Usage: gs commit (c) create (c) [flags]
Create a new commit
Staged changes are committed to the current branch. Branches upstack are
restacked if necessary. Use this as a shortcut for 'git commit' followed by 'gs
upstack restack'.
An editor is opened to edit the commit message. Use the -m/--message option
to specify the message without opening an editor. Git hooks are run unless the
--no-verify flag is given.
Use the -a/--all flag to stage all changes before committing.
Use the --fixup flag to create a new commit that will be merged into another
commit when run with 'git rebase --autosquash'. See also, the 'gs commit fixup'
command, which is preferable when you want to apply changes to an older commit.
Flags:
-a, --all Stage all changes before committing.
--allow-empty Create a new commit even if it contains no changes.
--fixup=COMMIT Create a fixup commit. See also 'gs commit fixup'.
-m, --message=MSG Use the given message as the commit message.
--no-verify Bypass pre-commit and commit-msg hooks.
--signoff Add Signed-off-by trailer to the commit message (🔧
spice.commit.signoff)
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+25
View File
@@ -0,0 +1,25 @@
Usage: gs commit (c) fixup (f) [<commit>] [flags]
Fixup a commit below the current commit
Apply staged uncommited changes to another commit down the stack, and restack
the rest of the stack on top of it.
If a commit is not specified, a prompt is shown to select one. If the commit is
specified, it must be reachable from the current commit, (i.e. it must be down
the stack).
If it's not possible to apply the staged changes to the commit without causing a
conflict, the command will fail.
This command requires at least Git 2.45.
Arguments:
[<commit>] The commit to fixup. Must be reachable from the HEAD commit.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+29
View File
@@ -0,0 +1,29 @@
Usage: gs commit (c) pick (p) [<commit>] [flags]
Cherry-pick a commit
Apply the changes introduced by a commit to the current branch and restack the
upstack branches.
If a commit is not specified, a prompt will allow picking from commits of
upstack branches of the current branch. Use the --from option to pick a commit
from a different branch or its upstack.
If it's not possible to cherry-pick the requested commit without causing a
conflict, the command will fail. If the requested commit is a merge commit,
the command will fail.
This command requires at least Git 2.45.
Arguments:
[<commit>] Commit to cherry-pick
Flags:
--from=NAME Branch whose upstack commits will be considered.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+17
View File
@@ -0,0 +1,17 @@
Usage: gs commit (c) split (sp) [flags]
Split the current commit
Interactively select hunks from the current commit to split into new commits
below it. Branches upstack are restacked as needed.
Flags:
-m, --message=MSG Use the given message as the commit message.
--no-verify Bypass pre-commit and commit-msg hooks.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+24
View File
@@ -0,0 +1,24 @@
Usage: gs down (d) [<n>] [flags]
Move down one branch
Checks out the branch below the current branch. If the current branch is at the
bottom of the stack, checks out the trunk branch. Use the -n flag to print the
branch without checking it out.
Arguments:
[<n>] Number of branches to move up.
Flags:
-n, --dry-run Print the target branch without checking it out
--detach Detach HEAD after checking out
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.checkout.verbose Print information about the checked out branch.
+24
View File
@@ -0,0 +1,24 @@
Usage: gs downstack (ds) edit (e) [flags]
Edit the order of branches below a branch
An editor opens with a list of branches in-order, starting from the current
branch until trunk. The current branch is at the top of the list. Use --branch
to start at a different branch.
Modifications to the list will be reflected in the stack when the editor is
closed, and the topmost branch will be checked out. If the file is cleared, no
changes will be made. Branches that are deleted from the list will be ignored.
Branches that are upstack of the current branch will not be modified.
Flags:
--editor=STRING Editor to use for editing the downstack. Defaults to Git's
default editor.
--branch=NAME Branch to edit from. Defaults to current branch.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+72
View File
@@ -0,0 +1,72 @@
Usage: gs downstack (ds) submit (s) [flags]
Submit a branch and those below it
Change Requests are created or updated for the current branch and all branches
below it until trunk. Use --branch to start at a different branch.
Use --dry-run to print what would be submitted without submitting it.
For new Change Requests, a prompt will allow filling metadata. Use --fill to
populate title and body from the commit messages. The --[no-]draft flag marks
the CR as draft or not. Use the 'spice.submit.draft' configuration option to
mark new CRs as drafts (or not) by default, skipping the prompt.
For updating Change Requests, use --[no-]draft to change its draft status.
Without the flag, the draft status is not changed.
Use --no-publish to push branches without creating CRs. This has no effect if a
branch already has an open CR.
Use --update-only to only update branches with existing CRs, and skip those that
would create new CRs.
Use --nav-comment=false to disable navigation comments in CRs, or
--nav-comment=multiple to post those comments only if there are multiple CRs in
the stack.
Flags:
-n, --dry-run Don't actually submit the stack
-c, --fill Fill in the change title and body from the commit
messages
--[no-]draft Whether to mark change requests as drafts
--[no-]publish Whether to create CRs for pushed branches. Defaults
to true. (🔧 spice.submit.publish)
-w, --web Open submitted changes in a web browser. Accepts
an optional argument: 'true', 'false', 'created'.
(🔧 spice.submit.web)
--nav-comment=true Whether to add a navigation comment to the change
request. Must be one of: true, false, multiple.
(🔧 spice.submit.navigationComment)
--force Force push, bypassing safety checks
--no-verify Bypass pre-push hooks when pushing to the remote.
-u, --[no-]update-only Only update existing change requests, do not create
new ones
-l, --label=LABEL,... Add labels to the change request. Pass multiple
times or separate with commas.
--no-web Alias for --web=false.
--branch=NAME Branch to start at
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.submit.draft Default value for --draft when creating change
requests.
spice.submit.label Default labels to add to change requests.
spice.submit.listTemplatesTimeout
Timeout for listing CR templates
spice.submit.navigationCommentStyle.marker
Marker to use for the current change in navigation
comments. Defaults to '◀'.
spice.submit.navigationCommentSync
Which navigation comment to sync. Must be one of:
branch, downstack.
spice.submit.template Default template to use when multiple templates are
available
spice.submit.updateOnly Default value for --update-only in batch submit
operations.
+19
View File
@@ -0,0 +1,19 @@
Usage: gs downstack (ds) track (tr) [<branch>]
Track all untracked branches below a branch
Track all untracked branches in the downstack of a branch.
Starting from the specified branch (or current branch), identify and track any
untracked branches downstack from it, until reaching trunk or an already-tracked
branch.
Arguments:
[<branch>] Name of the branch to start tracking from
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+82
View File
@@ -0,0 +1,82 @@
Usage: gs <command> [flags]
gs (git-spice) is a command line tool for stacking Git branches.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Commands:
version Print version information and quit
Shell
shell completion Generate shell completion script
Authentication
auth login Log in to a service
auth status Show current login status
auth logout Log out of a service
Repository
repo (r) init (i) Initialize a repository
repo (r) sync (s) Pull latest changes from the remote
repo (r) restack (r) Restack all tracked branches
Log
log (l) short (s) List branches
log (l) long (l) List branches and commits
Stack
stack (s) submit (s) Submit a stack
stack (s) restack (r) Restack a stack
stack (s) edit (e) Edit the order of branches in a stack
stack (s) delete (d) Delete all branches in a stack
upstack (us) submit (s) Submit a branch and those above it
upstack (us) restack (r) Restack a branch and its upstack
upstack (us) onto (o) Move a branch onto another branch
upstack (us) delete (d) Delete all branches above the current branch
downstack (ds) track (tr) Track all untracked branches below a branch
downstack (ds) submit (s) Submit a branch and those below it
downstack (ds) edit (e) Edit the order of branches below a branch
Branch
branch (b) track (tr) Track a branch
branch (b) untrack (untr) Forget a tracked branch
branch (b) checkout (co) Switch to a branch
branch (b) create (c) Create a new branch
branch (b) delete (d,rm) Delete branches
branch (b) fold (fo) Merge a branch into its base
branch (b) split (sp) Split a branch on commits
branch (b) squash (sq) Squash a branch into one commit
branch (b) edit (e) Edit the commits in a branch
branch (b) rename (rn,mv) Rename a branch
branch (b) restack (r) Restack a branch
branch (b) onto (on) Move a branch onto another branch
branch (b) submit (s) Submit a branch
Commit
commit (c) create (c) Create a new commit
commit (c) amend (a) Amend the current commit
commit (c) split (sp) Split the current commit
commit (c) fixup (f) Fixup a commit below the current commit
commit (c) pick (p) Cherry-pick a commit
Rebase
rebase (rb) continue (c) Continue an interrupted operation
rebase (rb) abort (a) Abort an operation
Navigation
up (u) Move up one branch
down (d) Move down one branch
top (U) Move to the top of the stack
bottom (D) Move to the bottom of the stack
trunk Move to the trunk branch
Run "gs <command> --help" for more information on a command.
Aliases can be combined to form shorthands for commands. For example:
gs bc => gs branch create
gs cc => gs commit create
+37
View File
@@ -0,0 +1,37 @@
Usage: gs log (l) long (l) [flags]
List branches and commits
Only branches that are upstack and downstack from the current branch are shown.
Use with the -a/--all flag to show all tracked branches.
With --json, prints output to stdout as a stream of JSON objects. See
https://abhinav.github.io/git-spice/cli/json/ for details.
Flags:
-a, --all Show all tracked branches, not just the current stack.
(🔧 spice.log.all)
-S, --[no-]cr-status Request and include information about the Change
Request (🔧 spice.log.crStatus)
--json Write to stdout as a stream of JSON objects in an
unspecified order
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.log.crFormat Format for displaying change request
information. One of 'id' or 'url'.
spice.log.pushStatusFormat Show indicator for branches that are out of sync
with their remotes. One of 'true', 'false' and
'aheadbehind'.
spice.logLong.crFormat Format for displaying change request information
in long log. One of 'id' or 'url', defaults to
crFormat.
spice.logShort.crFormat Format for displaying change request information
in short log. One of 'id' or 'url', defaults to
crFormat.
+37
View File
@@ -0,0 +1,37 @@
Usage: gs log (l) short (s) [flags]
List branches
Only branches that are upstack and downstack from the current branch are shown.
Use with the -a/--all flag to show all tracked branches.
With --json, prints output to stdout as a stream of JSON objects. See
https://abhinav.github.io/git-spice/cli/json/ for details.
Flags:
-a, --all Show all tracked branches, not just the current stack.
(🔧 spice.log.all)
-S, --[no-]cr-status Request and include information about the Change
Request (🔧 spice.log.crStatus)
--json Write to stdout as a stream of JSON objects in an
unspecified order
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.log.crFormat Format for displaying change request
information. One of 'id' or 'url'.
spice.log.pushStatusFormat Show indicator for branches that are out of sync
with their remotes. One of 'true', 'false' and
'aheadbehind'.
spice.logLong.crFormat Format for displaying change request information
in long log. One of 'id' or 'url', defaults to
crFormat.
spice.logShort.crFormat Format for displaying change request information
in short log. One of 'id' or 'url', defaults to
crFormat.
+18
View File
@@ -0,0 +1,18 @@
Usage: gs rebase (rb) abort (a)
Abort an operation
Cancels an ongoing git-spice operation that was interrupted by a git rebase.
For example, if 'gs upstack restack' encounters a conflict, cancel the operation
with 'gs rebase abort' (or its shorthand 'gs rba'), going back to the state
before the rebase.
The command can be used in place of 'git rebase --abort' even if a git-spice
operation is not currently in progress.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+27
View File
@@ -0,0 +1,27 @@
Usage: gs rebase (rb) continue (c) [flags]
Continue an interrupted operation
Continues an ongoing git-spice operation interrupted by a git rebase after
all conflicts have been resolved. For example, if 'gs upstack restack' gets
interrupted because a conflict arises during the rebase, you can resolve the
conflict and run 'gs rebase continue' (or its shorthand 'gs rbc') to continue
the operation.
The command can be used in place of 'git rebase --continue' even if a git-spice
operation is not currently in progress.
Use the --no-edit flag to continue without opening an editor. Make --no-edit
the default by setting 'spice.rebaseContinue.edit' to false and use --edit to
override it.
Flags:
--[no-]edit Whether to open an editor to edit the commit message.
(🔧 spice.rebaseContinue.edit)
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+28
View File
@@ -0,0 +1,28 @@
Usage: gs repo (r) init (i) [flags]
Initialize a repository
A trunk branch is required. This is the branch that changes will be merged into.
A prompt will ask for one if not provided with --trunk.
Most branch stacking operations are local and do not require a network
connection. For operations that push or pull commits, a remote is required.
A prompt will ask for one during initialization if not provided with --remote.
Re-run the command on an already initialized repository to change the trunk or
remote. If the trunk branch is changed on re-initialization, existing branches
stacked on the old trunk will be updated to point to the new trunk.
Re-run with --reset to discard all stored information and untrack all branches.
Flags:
--trunk=BRANCH Name of the trunk branch
--remote=NAME Name of the remote to push changes to
--reset Forget all information about the repository
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+13
View File
@@ -0,0 +1,13 @@
Usage: gs repo (r) restack (r)
Restack all tracked branches
All tracked branches in the repository are rebased on top of their respective
bases in dependency order, ensuring a linear history.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+22
View File
@@ -0,0 +1,22 @@
Usage: gs repo (r) sync (s) [flags]
Pull latest changes from the remote
Branches with merged Change Requests will be deleted after syncing.
The repository must have a remote associated for syncing. A prompt will ask for
one if the repository was not initialized with a remote.
Flags:
--restack Restack the current stack after syncing
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.repoSync.closedChanges How to handle closed change requests. One of
'ask' and 'ignore'.
+27
View File
@@ -0,0 +1,27 @@
Usage: gs shell completion [<shell>]
Generate shell completion script
To set up shell completion, eval the output of this command from your shell's rc
file. For example:
# bash
eval "$(gs shell completion bash)"
# zsh
eval "$(gs shell completion zsh)"
# fish
eval "$(gs shell completion fish)"
If shell name is not provided, the current shell is guessed using a heuristic.
Arguments:
[<shell>] Shell to generate completions for.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+22
View File
@@ -0,0 +1,22 @@
Usage: gs stack (s) delete (d) [flags]
Delete all branches in a stack
Deletes all branches in the current branch's stack. This includes both upstack
and downstack branches.
The deleted branches and their commits are removed from the stack. This is a
convenient way to clean up completed or abandoned feature stacks.
As this is a destructive operation, you must use the --force flag to confirm
deletion.
Flags:
--force Force deletion of the branches
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+27
View File
@@ -0,0 +1,27 @@
Usage: gs stack (s) edit (e) [flags]
Edit the order of branches in a stack
This operation requires a linear stack: no branch can have multiple branches
above it.
An editor opens with a list of branches in the current stack in-order, with the
topmost branch at the top of the file, and the branch closest to the trunk at
the bottom.
Modifications to the list will be reflected in the stack when the editor is
closed. If the file is cleared, no changes will be made. Branches that are
deleted from the list will be ignored.
Flags:
--editor=STRING Editor to use for editing the downstack. Defaults to Git's
default editor.
--branch=NAME Branch whose stack we're editing. Defaults to current
branch.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+18
View File
@@ -0,0 +1,18 @@
Usage: gs stack (s) restack (r) [flags]
Restack a stack
All branches in the current stack are rebased on top of their respective bases,
ensuring a linear history.
Use --branch to rebase the stack of a different branch.
Flags:
--branch=NAME Branch to restack the stack of
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+70
View File
@@ -0,0 +1,70 @@
Usage: gs stack (s) submit (s) [flags]
Submit a stack
Change Requests are created or updated for all branches in the current stack.
Use --dry-run to print what would be submitted without submitting it.
For new Change Requests, a prompt will allow filling metadata. Use --fill to
populate title and body from the commit messages. The --[no-]draft flag marks
the CR as draft or not. Use the 'spice.submit.draft' configuration option to
mark new CRs as drafts (or not) by default, skipping the prompt.
For updating Change Requests, use --[no-]draft to change its draft status.
Without the flag, the draft status is not changed.
Use --no-publish to push branches without creating CRs. This has no effect if a
branch already has an open CR.
Use --update-only to only update branches with existing CRs, and skip those that
would create new CRs.
Use --nav-comment=false to disable navigation comments in CRs, or
--nav-comment=multiple to post those comments only if there are multiple CRs in
the stack.
Flags:
-n, --dry-run Don't actually submit the stack
-c, --fill Fill in the change title and body from the commit
messages
--[no-]draft Whether to mark change requests as drafts
--[no-]publish Whether to create CRs for pushed branches. Defaults
to true. (🔧 spice.submit.publish)
-w, --web Open submitted changes in a web browser. Accepts
an optional argument: 'true', 'false', 'created'.
(🔧 spice.submit.web)
--nav-comment=true Whether to add a navigation comment to the change
request. Must be one of: true, false, multiple.
(🔧 spice.submit.navigationComment)
--force Force push, bypassing safety checks
--no-verify Bypass pre-push hooks when pushing to the remote.
-u, --[no-]update-only Only update existing change requests, do not create
new ones
-l, --label=LABEL,... Add labels to the change request. Pass multiple
times or separate with commas.
--no-web Alias for --web=false.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.submit.draft Default value for --draft when creating change
requests.
spice.submit.label Default labels to add to change requests.
spice.submit.listTemplatesTimeout
Timeout for listing CR templates
spice.submit.navigationCommentStyle.marker
Marker to use for the current change in navigation
comments. Defaults to '◀'.
spice.submit.navigationCommentSync
Which navigation comment to sync. Must be one of:
branch, downstack.
spice.submit.template Default template to use when multiple templates are
available
spice.submit.updateOnly Default value for --update-only in batch submit
operations.
+21
View File
@@ -0,0 +1,21 @@
Usage: gs top (U) [flags]
Move to the top of the stack
Checks out the top-most branch in the current branch's stack. If there are
multiple possible top-most branches, a prompt will ask you to pick one. Use the
-n flag to print the branch without checking it out.
Flags:
-n, --dry-run Print the target branch without checking it out
--detach Detach HEAD after checking out
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.checkout.verbose Print information about the checked out branch.
+17
View File
@@ -0,0 +1,17 @@
Usage: gs trunk [flags]
Move to the trunk branch
Flags:
-n, --dry-run Print the target branch without checking it out
--detach Detach HEAD after checking out
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.checkout.verbose Print information about the checked out branch.
+24
View File
@@ -0,0 +1,24 @@
Usage: gs up (u) [<n>] [flags]
Move up one branch
Checks out the branch above the current one. If there are multiple branches
with the current branch as base, a prompt will allow picking between them.
Use the -n flag to print the branch without checking it out.
Arguments:
[<n>] Number of branches to move up.
Flags:
-n, --dry-run Print the target branch without checking it out
--detach Detach HEAD after checking out
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.checkout.verbose Print information about the checked out branch.
+22
View File
@@ -0,0 +1,22 @@
Usage: gs upstack (us) delete (d) [flags]
Delete all branches above the current branch
Deletes all branches above the current branch in the stack, not including the
current branch. The current branch remains unchanged.
This is a convenient way to clean up abandoned or completed parts of a feature
stack.
As this is a destructive operation, you must use the --force flag to confirm
deletion.
Flags:
--force Force deletion of the branches
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+39
View File
@@ -0,0 +1,39 @@
Usage: gs upstack (us) onto (o) [<onto>] [flags]
Move a branch onto another branch
The current branch and its upstack will move onto the new base.
A prompt will allow selecting the new base for the branch. Provide an argument
to skip the prompt. Use the --branch flag to target a different branch for the
move.
For example, given the following stack with B checked out, 'gs upstack onto
main' will have the following effect:
gs upstack onto main
┌── C ┌── C
┌─┴ B ◀ ┌─┴ B ◀
┌─┴ A ├── A
trunk trunk
Use 'gs branch onto' to leave the branch's upstack alone.
Arguments:
[<onto>] Destination branch
Flags:
--branch=NAME Branch to start at
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.branchPrompt.sort Sort branches by the given field. Common values
include 'refname', 'commiterdate', etc. Defaults to
branch name.
+22
View File
@@ -0,0 +1,22 @@
Usage: gs upstack (us) restack (r) [flags]
Restack a branch and its upstack
The current branch and all branches above it are rebased on top of their
respective bases, ensuring a linear history.
Use --branch to start at a different branch.
Use --skip-start to skip the starting branch, but still rebase all branches
above it.
Flags:
--skip-start Do not restack the starting branch
--branch=NAME Branch to restack the upstack of
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+74
View File
@@ -0,0 +1,74 @@
Usage: gs upstack (us) submit (s) [flags]
Submit a branch and those above it
Change Requests are created or updated for the current branch and all branches
upstack from it. If the base of the current branch is not trunk, it must have
already been submitted by a prior command. Use --branch to start at a different
branch.
Use --dry-run to print what would be submitted without submitting it.
For new Change Requests, a prompt will allow filling metadata. Use --fill to
populate title and body from the commit messages. The --[no-]draft flag marks
the CR as draft or not. Use the 'spice.submit.draft' configuration option to
mark new CRs as drafts (or not) by default, skipping the prompt.
For updating Change Requests, use --[no-]draft to change its draft status.
Without the flag, the draft status is not changed.
Use --no-publish to push branches without creating CRs. This has no effect if a
branch already has an open CR.
Use --update-only to only update branches with existing CRs, and skip those that
would create new CRs.
Use --nav-comment=false to disable navigation comments in CRs, or
--nav-comment=multiple to post those comments only if there are multiple CRs in
the stack.
Flags:
-n, --dry-run Don't actually submit the stack
-c, --fill Fill in the change title and body from the commit
messages
--[no-]draft Whether to mark change requests as drafts
--[no-]publish Whether to create CRs for pushed branches. Defaults
to true. (🔧 spice.submit.publish)
-w, --web Open submitted changes in a web browser. Accepts
an optional argument: 'true', 'false', 'created'.
(🔧 spice.submit.web)
--nav-comment=true Whether to add a navigation comment to the change
request. Must be one of: true, false, multiple.
(🔧 spice.submit.navigationComment)
--force Force push, bypassing safety checks
--no-verify Bypass pre-push hooks when pushing to the remote.
-u, --[no-]update-only Only update existing change requests, do not create
new ones
-l, --label=LABEL,... Add labels to the change request. Pass multiple
times or separate with commas.
--no-web Alias for --web=false.
--branch=NAME Branch to start at
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
Configuration (🔧):
spice.submit.draft Default value for --draft when creating change
requests.
spice.submit.label Default labels to add to change requests.
spice.submit.listTemplatesTimeout
Timeout for listing CR templates
spice.submit.navigationCommentStyle.marker
Marker to use for the current change in navigation
comments. Defaults to '◀'.
spice.submit.navigationCommentSync
Which navigation comment to sync. Must be one of:
branch, downstack.
spice.submit.template Default template to use when multiple templates are
available
spice.submit.updateOnly Default value for --update-only in batch submit
operations.
+13
View File
@@ -0,0 +1,13 @@
Usage: gs version [flags]
Print version information and quit
Flags:
--short Print only the version number.
Global Flags:
-h, --help Show help for the command
--version Print version information and quit
-v, --verbose Enable verbose output ($GIT_SPICE_VERBOSE)
-C, --dir=DIR Change to DIR before doing anything
--[no-]prompt Whether to prompt for missing information
+4 -9
View File
@@ -25,6 +25,10 @@ func (*upstackOntoCmd) Help() string {
return text.Dedent(`
The current branch and its upstack will move onto the new base.
A prompt will allow selecting the new base for the branch.
Provide an argument to skip the prompt.
Use the --branch flag to target a different branch for the move.
For example, given the following stack with B checked out,
'gs upstack onto main' will have the following effect:
@@ -36,15 +40,6 @@ func (*upstackOntoCmd) Help() string {
trunk trunk
Use 'gs branch onto' to leave the branch's upstack alone.
Use --branch to move a different branch than the current one.
A prompt will allow selecting the new base.
Use the spice.branchPrompt.sort configuration option
to specify the sort order of branches in the prompt.
Commonly used field names include "refname", "commiterdate", etc.
By default, branches are sorted by name.
Provide the new base name as an argument to skip the prompt.
`)
}