# Lazygit Git Command Execution Layer Architecture: A Deep Dive into the Facade Pattern

> Explore lazy git's git command execution layer architecture. Discover how it uses a facade pattern, command builders, and a resilient runner for efficient Git interactions.

- Repository: [Jesse Duffield/lazygit](https://github.com/jesseduffield/lazygit)
- Tags: architecture
- Published: 2026-03-02

---

**Lazygit isolates every Git binary interaction behind a dedicated execution layer that combines a central facade, specialized command builders with environment injection, and a resilient runner featuring automatic retry logic for lock-related failures.**

The lazygit terminal UI relies on a sophisticated execution layer to interface with the Git binary without blocking the main thread. This architecture separates low-level OS command construction from high-level Git business logic through a facade pattern and dependency injection. Understanding this lazygit git command execution layer architecture reveals how the application handles repository locks, environment configuration, and cross-platform compatibility.

## The GitCommand Facade

The `GitCommand` struct in [`pkg/commands/git.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git.go) serves as the public entry point for all git-related functionality. It aggregates a collection of sub-command structs, each responsible for a specific domain.

```go
type GitCommand struct {
    Blame       *git_commands.BlameCommands
    Branch      *git_commands.BranchCommands
    Commit      *git_commands.CommitCommands
    // … many more
    Loaders Loaders
    RepoPaths *git_commands.RepoPaths
}

```

Construction happens through `NewGitCommand` or `NewGitCommandAux`, which first determines repository paths, changes the working directory, and opens the repository via `go-git`. The facade then creates a Git-specific command builder with `NewGitCmdObjBuilder` and wires each sub-command with shared dependencies including the `gitCommon` instance, config commands, and loaders.

## Git-Specific Command Construction

All Git commands are built via `gitCmdObjBuilder` defined in [`pkg/commands/git_cmd_obj_builder.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_cmd_obj_builder.go). This builder decorates the generic `oscommands.CmdObjBuilder` to add Git-specific environment handling.

```go
func (self *gitCmdObjBuilder) New(args []string) *oscommands.CmdObj {
    return self.innerBuilder.New(args).AddEnvVars(defaultEnvVar)
}

```

The builder automatically injects `GIT_OPTIONAL_LOCKS=0` into every command environment, preventing optional lock files that could block operations. It delegates the actual construction of `exec.Command` objects to the underlying `oscommands.CmdObjBuilder` located in [`pkg/commands/oscommands/cmd_obj_builder.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/oscommands/cmd_obj_builder.go), which handles platform-specific quoting and shell wrapping.

## Resilient Command Execution with Retry Logic

The `gitCmdObjRunner` in [`pkg/commands/git_cmd_obj_runner.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_cmd_obj_runner.go) decorates the generic runner to add Git-specific resilience. Its primary responsibility is detecting and recovering from stale index locks left by crashed Git processes.

When executing a command, the runner checks the output for the string `.git/index.lock`. If detected, it logs a warning and retries the command up to `RetryCount` times, sleeping `WaitTime` between attempts:

```go
if err == nil || !strings.Contains(output, ".git/index.lock") {
    return output, err
}
self.log.Warn("index.lock prevented command from running. Retrying …")
time.Sleep(WaitTime)

```

This ensures that transient lock files do not permanently block user operations, a common pitfall in Git automation.

## Domain-Specific Command Structs

The `pkg/commands/git_commands/` directory contains high-level command structs like `StatusCommands`, `BranchCommands`, and `CommitCommands`. Each receives the shared `gitCommon` instance via dependency injection, which bundles logging, version info, the command builder, OS commands, repo paths, and configuration.

For example, [`pkg/commands/git_commands/status.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/status.go) implements `StatusCommands.GetStatus()` to run `git status --porcelain=v2`, while [`pkg/commands/git_commands/branch.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/branch.go) implements `BranchCommands.Checkout(branchName)` to execute `git checkout <branch>`. All domain structs use the injected `gitCmdObjBuilder`, automatically inheriting the `GIT_OPTIONAL_LOCKS=0` environment variable and index-lock retry logic.

## Dependency Injection and Shared State

The `gitCommon` struct defined in [`pkg/commands/git_commands/common.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/common.go) acts as the dependency injection container for all Git command domains. It provides:

- **Logging** infrastructure for command tracing
- **Version information** via `git_commands.NewGitVersion`
- **Command building** capabilities through the builder interface
- **Repository paths** managed by [`pkg/commands/git_commands/repo_paths.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/repo_paths.go)
- **Configuration** including pager settings

Loaders such as `BranchLoader` and `CommitLoader` are thin helpers that use the shared command builder to lazily fetch data for the UI layer, keeping the command execution layer decoupled from presentation concerns.

## Practical Implementation Examples

### Creating a GitCommand and Fetching Repository Status

The following example demonstrates constructing the facade and retrieving porcelain status:

```go
cmn, _ := common.NewCommon()
gitVersion := git_commands.NewGitVersion(cmn)
osCmd := oscommands.NewOSCommand(cmn.Log)
gitConfig := git_config.NewGitConfig(osCmd)
pagerCfg := &config.PagerConfig{}

gitCmd, err := commands.NewGitCommand(cmn, gitVersion, osCmd, gitConfig, pagerCfg)
if err != nil {
    // handle error
}

// Fetch the porcelain-v2 status (runs `git status --porcelain=v2`)
status, err := gitCmd.Status.GetStatus()
if err != nil {
    // handle error
}
fmt.Println(status)

```

The `StatusCommands` struct internally creates the command using `gitCmdObjBuilder`, which adds `GIT_OPTIONAL_LOCKS=0` and enables retry on index-lock failures.

### Executing Arbitrary Git Commands

For direct command execution, use the builder and runner explicitly:

```go
builder := commands.NewGitCmdObjBuilder(cmn.Log, osCmd.Cmd)
cmdObj := builder.New([]string{"git", "rev-parse", "--abbrev-ref", "HEAD"})
output, err := osCmd.Cmd.NewRunner().RunWithOutput(cmdObj)
if err != nil {
    // handle error
}
fmt.Printf("Current branch: %s\n", strings.TrimSpace(output))

```

The `gitCmdObjRunner` automatically retries if the output contains `.git/index.lock`, transparently handling the transient failure.

## Summary

- **Facade Pattern**: The `GitCommand` struct in [`pkg/commands/git.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git.go) centralizes access to domain-specific command structs and loaders.
- **Environment Isolation**: The `gitCmdObjBuilder` automatically injects `GIT_OPTIONAL_LOCKS=0` into every command to prevent optional lock contention.
- **Resilient Execution**: The `gitCmdObjRunner` implements automatic retry logic for `.git/index.lock` errors, sleeping between attempts to allow lock clearance.
- **Dependency Injection**: The `gitCommon` struct shares state across all command domains in `pkg/commands/git_commands/`, promoting testability and loose coupling.
- **Layered Architecture**: Generic OS command construction in `pkg/commands/oscommands/` is cleanly separated from Git-specific business logic.

## Frequently Asked Questions

### How does lazygit handle stale Git index locks?

Lazygit's `gitCmdObjRunner` in [`pkg/commands/git_cmd_obj_runner.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_cmd_obj_runner.go) automatically detects `.git/index.lock` strings in command output. When detected, it logs a warning and retries the command up to the configured `RetryCount`, sleeping `WaitTime` between attempts to allow the lock to clear.

### Why does lazygit set GIT_OPTIONAL_LOCKS=0 on every command?

The `gitCmdObjBuilder` in [`pkg/commands/git_cmd_obj_builder.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_cmd_obj_builder.go) injects this environment variable to disable optional lock files that Git might otherwise create. This prevents background Git operations from blocking lazygit's commands, improving responsiveness in the terminal UI.

### How are git commands structured in the codebase?

Commands follow a layered architecture: the `GitCommand` facade aggregates domain-specific structs (like `StatusCommands` in [`pkg/commands/git_commands/status.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/status.go)) that use an injected builder. The builder creates command objects, and the runner executes them with retry logic, creating clear separation between construction, execution, and business logic.

### What files define the core execution layer components?

The execution layer spans several files: [`pkg/commands/git.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git.go) defines the facade, [`pkg/commands/git_cmd_obj_builder.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_cmd_obj_builder.go) handles Git-specific construction, [`pkg/commands/git_cmd_obj_runner.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_cmd_obj_runner.go) implements retry logic, [`pkg/commands/oscommands/cmd_obj_builder.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/oscommands/cmd_obj_builder.go) manages OS-level command creation, and [`pkg/commands/git_commands/common.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/common.go) provides the shared `gitCommon` dependency container.