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_FORMAT environment variable provides persistent configuration for scripts.
  • --json convenience 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() and out.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 the BUT_OUTPUT_FORMAT environment variable.
  • Automatic selection defaults to Human for terminals and Shell for redirected stdout.
  • OutputChannel in crates/but/src/utils/output_channel.rs centralizes format-specific routing and JSON buffering.
  • JSON mode requires explicit write_value() calls and supports --status-after for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →