GitButler CLI Output Formatting Modes: Human, Shell, JSON, and None Explained
GitButler supports four distinct CLI output formatting modes—Human, Shell, JSON, and None—that control how command results are rendered, selectable via --format flags or the BUT_OUTPUT_FORMAT environment variable.
The gitbutlerapp/gitbutler repository implements a flexible output system in its Rust-based CLI (the but command) to accommodate both interactive terminal users and automation scripts. Understanding these GitButler CLI output formatting modes allows you to integrate the tool seamlessly into shell scripts, CI pipelines, or interactive workflows.
The Four Output Format Modes
GitButler’s output behavior is governed by the OutputFormat enum defined in crates/but/src/args/mod.rs. Each variant corresponds to a specific use case, from rich human-readable text to complete silence.
Human Mode (Default Terminal Output)
Human mode produces verbose, color-rich text designed for direct reading. This is the default when --format is omitted and stdout is attached to a terminal. In this mode, GitButler uses pagers when appropriate and includes formatting enhancements like colors and indentation to improve readability.
Shell Mode (Script-Friendly Plain Text)
Shell mode emits minimal, plain-text output ideal for capturing into shell variables. When stdout is redirected or when explicitly selected with --format shell, GitButler strips decorative formatting and returns concise strings that can be directly assigned in bash scripts.
JSON Mode (Machine-Readable Structured Data)
JSON mode outputs structured, machine-readable data via out.write_value(). Enabled with --json or --format json, this mode suppresses normal write! calls and requires commands to explicitly serialize data structures. This is the preferred format for integration with tools like jq or for programmatic processing.
None Mode (Silent Operation)
None mode silences all standard output, equivalent to redirecting to /dev/null. Selected with --format none, this mode is useful for "fire-and-forget" operations where you only care about the exit status, not the command feedback.
How Output Format Selection Works
The format selection logic resides in crates/but/src/args/mod.rs, where the OutputFormat enum and CLI arguments are defined:
pub enum OutputFormat {
#[default] Human,
Shell,
Json,
None,
}
The CLI exposes multiple control mechanisms for this setting:
#[clap(
long,
short = 'f',
env = "BUT_OUTPUT_FORMAT",
conflicts_with = "json",
default_value = "human"
)]
pub format: OutputFormat,
--format <mode>(or-f) sets the mode directly.BUT_OUTPUT_FORMATenvironment variable provides persistent configuration for scripts.--jsonconvenience flag forces JSON mode and conflicts with--format.
The entry point in crates/but/src/lib.rs resolves the final format by checking for the JSON flag first:
let output_format = if args.json { OutputFormat::Json } else { args.format };
When no format is specified, GitButler automatically selects Human if stdout is a terminal, or Shell if stdout is redirected (e.g., piped to another program).
The OutputChannel Implementation
All output routing flows through the OutputChannel struct in crates/but/src/utils/output_channel.rs. This centralizes write operations and enforces format-specific behavior:
match self.format {
OutputFormat::Human | OutputFormat::Shell => {
// write to pager or stdout (human-readable)
}
OutputFormat::Json | OutputFormat::None => {
// JSON is written via `write_value`; otherwise writes are ignored
Ok(())
}
}
Commands use specialized methods to emit data appropriately:
out.for_human()andout.for_shell()for text output.out.write_value(&value)for JSON serialization.
The OutputChannel also implements JSON buffering via start_json_buffering() and take_json_buffer() to support the --status-after flag, which combines mutation results with subsequent workspace status in a single JSON payload.
Practical Examples
Interactive Human Output
# Default colored output for terminal reading
but status
Shell Variable Capture
# Capture branch removal confirmation for scripting
result=$(but rm-branch myfeature --format shell)
echo "Removed branch: $result"
JSON Processing with jq
# Extract branch names and HEAD commits
but list-branches --json | jq '.[] | {name: .name, head: .head}'
Or configure via environment variable:
export BUT_OUTPUT_FORMAT=json
but list-branches | jq '.[] | .name'
Silent Execution
# Execute without any output
but prune --format none
Combined Status After Mutation
# Rebase and append workspace status in single JSON output
but rebase myfeature --status-after --json
Summary
- Four modes control GitButler CLI output: Human (rich text), Shell (plain text), JSON (structured data), and None (silent).
- Configuration uses
--format,-f,--json, or theBUT_OUTPUT_FORMATenvironment variable. - Automatic selection defaults to Human for terminals and Shell for redirected stdout.
- OutputChannel in
crates/but/src/utils/output_channel.rscentralizes format-specific routing and JSON buffering. - JSON mode requires explicit
write_value()calls and supports--status-afterfor combined output payloads.
Frequently Asked Questions
How do I force JSON output in GitButler?
Use the --json flag for any command, or set the BUT_OUTPUT_FORMAT=json environment variable. According to the source code in crates/but/src/args/mod.rs, the --json flag takes precedence and conflicts with the --format option, ensuring unambiguous machine-readable output.
What is the difference between Human and Shell mode?
Human mode includes colors, pagination, and verbose formatting intended for direct terminal reading, while Shell mode strips all decorations to produce minimal plain text suitable for variable capture in scripts. The OutputChannel routes both through stdout but applies different formatting layers based on the selected variant.
How does GitButler handle output when stdout is redirected?
When stdout is not attached to a terminal and no format is explicitly specified, GitButler automatically selects Shell mode. This logic is implemented in the argument handling code within crates/but/src/lib.rs, ensuring that piped or redirected commands receive parseable plain text by default.
Can I combine --status-after with JSON formatting?
Yes. When using --status-after with --json, GitButler temporarily buffers the mutation's JSON output via start_json_buffering() and take_json_buffer() methods in OutputChannel, then combines it with the subsequent workspace status into a single JSON payload. This allows atomic operations to report both their result and the new repository state in one machine-readable structure.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →