# Understanding gh-stack Internal Architecture: A Deep Dive into cmd/, internal/, and tui/

> Explore the gh-stack internal architecture: cmd for CLI, internal for core logic, and tui for interactive interfaces. Understand the separation of concerns in this Go codebase.

- Repository: [GitHub/gh-stack](https://github.com/github/gh-stack)
- Tags: internals
- Published: 2026-08-03

---

**The gh-stack internal architecture organizes its Go codebase into three distinct layers—`cmd/` for CLI entry points, `internal/` for core business logic, and `internal/tui/` for interactive terminal interfaces—enabling clean separation between command parsing, Git/GitHub operations, and user-facing views.**

The gh-stack tool is a Go-based GitHub CLI extension that manages stacked pull requests. Its internal architecture follows standard Go project conventions by separating concerns into discrete packages that handle command-line interfacing, core domain logic, and terminal user interfaces. This modular design allows developers to test individual components in isolation while maintaining clear boundaries between user input, business operations, and presentation layers.

## Command Layer: The cmd/ Package

The `cmd/` directory serves as the entry point for all user interactions, implementing the **Cobra** command framework to parse arguments and delegate work to internal packages.

In [`cmd/root.go`](https://github.com/github/gh-stack/blob/main/cmd/root.go), the top-level command (`gh stack`) is created and sub-commands are grouped into logical categories including stack management, remote operations, and navigation utilities. Each sub-command follows a consistent pattern: an `Options` struct holds flag values, a `NewCmd(cfg)` constructor returns a `*cobra.Command`, and the `RunE` field invokes a private `runX` function containing the actual logic.

For example, [`cmd/init.go`](https://github.com/github/gh-stack/blob/main/cmd/init.go) parses the initial branch list, initializes a new stack using `stack.Load` and `stack.Save`, and prints status messages via the injected configuration. All commands receive a **`*config.Config`** instance that supplies I/O streams (`Out`, `Err`, `In`), color helpers (`ColorSuccess`, `ColorError`), and test hooks (`SelectFn`, `ConfirmFn`, `InputFn`) for mocking interactive prompts in unit tests.

## Core Engine: The internal/ Package

The `internal/` directory contains the domain logic, organized into focused sub-packages that handle Git operations, GitHub API integration, stack persistence, and shared utilities.

### Git Operations (internal/git)

The **`Ops`** interface defined in [`internal/git/gitops.go`](https://github.com/github/gh-stack/blob/main/internal/git/gitops.go) abstracts every Git operation required by gh-stack, including branch creation, fetching, pushing, and rebasing. The default implementation (`defaultOps`) forwards calls to the real `git` binary via helper functions (`run`, `runSilent`), while tests can substitute `git.MockOps` via `git.SetOps` for deterministic validation without a real repository.

Key methods include `CurrentBranch` and `BranchExists` for repository queries, `FetchBranch` and `FetchBranches` for explicit refspec handling with graceful missing-remote support, and `Push` which builds per-branch `--force-with-lease` arguments. The rebasing methods (`Rebase`, `RebaseOnto`, `RebaseContinue`) encapsulate conflict resolution logic and automatically invoke auto-resolve helpers when conflicts arise.

### GitHub API Abstraction (internal/github)

The `ClientOps` interface in [`internal/github/client_interface.go`](https://github.com/github/gh-stack/blob/main/internal/github/client_interface.go) defines the subset of GitHub API calls required by gh-stack, including PR creation, stack creation, async merge operations, and merge-queue detection. The concrete implementation is instantiated via `github.NewClient` using the repository's host, owner, and name, while `MockClient` in [`mock_client.go`](https://github.com/github/gh-stack/blob/main/mock_client.go) provides a test double that can be injected via `Config.GitHubClientOverride`.

### Stack File Management (internal/stack)

The stack state persists as **`.git/gh-stack`** JSON. The `stack.Stack` struct models a complete stack (trunk plus ordered branches), while `stack.BranchRef` holds each branch's HEAD SHA, base SHA, and optional PR metadata.

Core responsibilities include **loading and saving** via `stack.Load` (which reads the file and records a SHA-256 checksum) and `stack.Save` (which acquires an exclusive lock, checks for stale data via `checkStale`, and writes updated JSON). Concurrency control is implemented through platform-specific file locking in [`lock_unix.go`](https://github.com/github/gh-stack/blob/main/lock_unix.go) and [`lock_windows.go`](https://github.com/github/gh-stack/blob/main/lock_windows.go). Helper methods like `ActiveBranches`, `MergedBranches`, and `NearestSurvivingBranch` provide the logic required by commands such as `submit`, `sync`, and `modify`.

### Shared Utilities (internal/config)

The `config.Config` struct centralizes terminal handling, color theme management, and test instrumentation. Additional utilities include [`branch/name.go`](https://github.com/github/gh-stack/blob/main/branch/name.go) for branch slugification (e.g., `DateSlug`), [`modify/state.go`](https://github.com/github/gh-stack/blob/main/modify/state.go) for the interactive modify command's state machine, and [`pr/template.go`](https://github.com/github/gh-stack/blob/main/pr/template.go) for discovering repository PR templates.

## Terminal UI Layer: The internal/tui/ Package

The `internal/tui/` directory houses the **Bubble Tea**-based interactive views used by commands like `gh stack view` and `gh stack submit`. Each view resides in its own sub-folder following the Model-Update-View pattern.

### checkoutview

Located in `internal/tui/checkoutview/`, this view displays a list of branches for users to select a checkout target. It implements `tea.Model` with fields for selection state and uses `lipgloss` for styling via [`styles.go`](https://github.com/github/gh-stack/blob/main/styles.go).

### modifyview

The `internal/tui/modifyview/` package drives the interactive modify session, showing the current stack, pending actions, and allowing users to reorder or drop branches. Key files include [`model.go`](https://github.com/github/gh-stack/blob/main/model.go) for state management, [`status.go`](https://github.com/github/gh-stack/blob/main/status.go) for operation feedback, and [`help.go`](https://github.com/github/gh-stack/blob/main/help.go) for contextual keybindings.

### submitview

Found in `internal/tui/submitview/`, this view presents a preview of PRs that will be submitted, enables editing of titles and descriptions through an integrated editor, and confirms operations before execution. It coordinates with the GitHub client and Git operations layers through the shared `Config` instance.

## Cross-Cutting Interaction Patterns

The layers communicate through well-defined interfaces and shared configuration objects. When a user executes `gh stack submit`, the flow proceeds as follows:

1. [`cmd/submit.go`](https://github.com/github/gh-stack/blob/main/cmd/submit.go) parses flags and obtains a `*config.Config` instance.
2. The command loads the stack file via `stack.Load(git.RootDir())`.
3. If `--interactive` is set, the command instantiates `submitview.NewModel(cfg, sf.Stacks[0])` and starts a Bubble Tea program.
4. Upon confirmation, the command invokes `cfg.GitHubClient()` for API operations and `git.Ops` methods (`Push`, `Rebase`, etc.) for repository modifications.
5. Finally, `stack.Save` persists changes using file locking to prevent race conditions.

### Example: Programmatic Branch Addition

```go
cfg := config.New()
gitRoot, _ := git.RootDir()
sf, _ := stack.Load(gitRoot)

// Create a new branch on top of the current stack head.
branch := "feature-xyz"
base := sf.Stacks[0].ActiveBaseBranch(branch)
git.CreateBranch(branch, base)

// Update the stack file.
newRef := stack.BranchRef{Branch: branch, Base: base}
sf.Stacks[0].Branches = append(sf.Stacks[0].Branches, newRef)
stack.Save(gitRoot, sf)

```

*Relevant sources:* `config.New` in [`internal/config/config.go`](https://github.com/github/gh-stack/blob/main/internal/config/config.go), `git.CreateBranch` in [`internal/git/gitops.go`](https://github.com/github/gh-stack/blob/main/internal/git/gitops.go), and `stack.Load`/`stack.Save` in [`internal/stack/stack.go`](https://github.com/github/gh-stack/blob/main/internal/stack/stack.go).

### Example: Running the Interactive Modify UI

```go
cfg := config.New()
gitRoot, _ := git.RootDir()
sf, _ := stack.Load(gitRoot)

// Build the model and start the TUI.
m := modifyview.NewModel(cfg, sf.Stacks[0])
p := tea.NewProgram(m)
if _, err := p.Run(); err != nil {
    cfg.Errorf("modify UI failed: %v", err)
}

```

*Relevant sources:* `modifyview.NewModel` in [`internal/tui/modifyview/model.go`](https://github.com/github/gh-stack/blob/main/internal/tui/modifyview/model.go), Bubble Tea's `tea.NewProgram`, and `Config` error helpers.

## Summary

- **The `cmd/` layer** handles CLI parsing, flag validation, and command routing using Cobra, remaining thin by delegating to internal packages.
- **The `internal/` layer** contains the core engine, including Git abstraction via the `Ops` interface, GitHub API clients, stack file persistence with concurrency control, and shared configuration utilities.
- **The `internal/tui/` layer** provides interactive Bubble Tea views for checkout, modify, and submit operations, communicating with core logic through the `Config` object.
- **Cross-cutting concerns** like I/O streams, color themes, and test hooks are centralized in `config.Config`, enabling consistent behavior and testability across all layers.

## Frequently Asked Questions

### What is the purpose of the `internal/` directory in gh-stack?

The `internal/` directory encapsulates the core business logic that should not be imported by external packages, following Go's visibility conventions. It contains implementations for Git operations (`internal/git`), GitHub API interactions (`internal/github`), stack file management (`internal/stack`), and shared utilities (`internal/config`), ensuring that command handlers remain focused on argument parsing and delegation.

### How does gh-stack handle Git operations without tight coupling to the binary?

According to the source code in [`internal/git/gitops.go`](https://github.com/github/gh-stack/blob/main/internal/git/gitops.go), gh-stack defines an `Ops` interface that abstracts all Git operations. The production code uses `defaultOps` which shells out to the real `git` binary, but the architecture allows swapping in `git.MockOps` via `git.SetOps` during unit tests. This dependency injection pattern enables deterministic testing without requiring actual Git repositories or network operations.

### What framework powers the interactive UI components in gh-stack?

The terminal UI is built on **Bubble Tea**, a Go framework based on The Elm Architecture. Views in `internal/tui/checkoutview/`, `internal/tui/modifyview/`, and `internal/tui/submitview/` implement the `tea.Model` interface with `Init`, `Update`, and `View` methods, using `lipgloss` for styling and `bubbles` for reusable components like lists and text inputs.

### How does gh-stack prevent concurrent modifications to the stack file?

The `internal/stack` package implements file-based concurrency control using platform-specific locking mechanisms in [`lock_unix.go`](https://github.com/github/gh-stack/blob/main/lock_unix.go) and [`lock_windows.go`](https://github.com/github/gh-stack/blob/main/lock_windows.go). When `stack.Save` writes to `.git/gh-stack`, it acquires an exclusive lock, verifies that the file hasn't been modified since loading (via SHA-256 checksum comparison in `checkStale`), and only then commits the changes, preventing race conditions when multiple gh-stack processes run simultaneously.