This document describes Lazygit's architectural patterns, layers, and design principles. It explains how the major components interact and the responsibilities of each layer. For details on specific subsystems, see:
Lazygit follows a layered architecture where the GUI layer sits at the center, coordinating between user interactions and Git operations. The system is structured to maintain clear separation between concerns while allowing for flexible state management across multiple repository worktrees.
Diagram: Lazygit Layered Architecture with Code Entities
Sources: pkg/gui/gui.go1-151 pkg/app/app.go pkg/commands/git.go1-189 pkg/gui/context.go1-376
Lazygit uses a context stack pattern (ContextMgr) to manage UI navigation and state. Each view has an associated context that handles rendering, keybindings, and focus behavior. The stack enables modal navigation where contexts can be pushed and popped, similar to a navigation stack in mobile applications.
Diagram: Context Management System
The ContextMgr.ContextStack field maintains the navigation history. When pushing a SIDE_CONTEXT, all other contexts are removed from the stack. When pushing a MAIN_CONTEXT, only other main contexts are removed, preserving the side context. This ensures that pressing escape returns you to the appropriate parent context.
Sources: pkg/gui/context.go17-376 pkg/gui/gui.go232-259
Controllers define keybindings and their handlers, while helpers contain shared business logic. This separation prevents circular dependencies and promotes code reuse.
Diagram: Controller and Helper Dependencies
All controllers and helpers receive a HelperCommon struct (field name c) containing dependencies like the logger, translation set (Tr), and user config. Controllers access helpers via self.c.Helpers.RefreshHelper, while helpers can access other helpers the same way.
Sources: pkg/gui/controllers/helpers/helpers.go pkg/gui/gui.go723-742 docs/dev/Codebase_Guide.md69-72
Git operations flow through a multi-layer pipeline that separates business logic from command construction and execution.
Diagram: Git Command Execution Pipeline
Each Git operation type (e.g., WorkingTreeCommands, BranchCommands) has its own struct with methods like Stage(), Checkout(), etc. These methods use GitCmdObjBuilder to construct GitCmdObj instances, which represent a single Git command with its arguments. The OSCommand layer then executes these commands and returns results. Loaders parse Git output into model structs.
Sources: pkg/commands/git.go19-189 pkg/commands/git_commands/ docs/dev/Codebase_Guide.md8-9
The application layer (pkg/app/) handles:
When Lazygit starts, main.go invokes app.Run(), which validates the Git version, loads configuration, and determines the initial repository path before creating and running the GUI.
Sources: pkg/app/app.go README.md834-841
The GUI layer (pkg/gui/) is the architectural center. The Gui struct (pkg/gui/gui.go63-151) coordinates all subsystems:
| Field | Type | Responsibility |
|---|---|---|
g | *gocui.Gui | Terminal UI rendering and event loop |
State | *GuiRepoState | Current repository state (models, modes, contexts) |
RepoStateMap | map[Repo]*GuiRepoState | State for each worktree |
helpers | *helpers.Helpers | Business logic helpers |
c | *helpers.HelperCommon | Common dependencies |
Config | config.AppConfigurer | User configuration |
PopupHandler | types.IPopupHandler | Popup creation and management |
The GUI layer implements the event loop pattern: keybindings trigger handlers in controllers, which invoke helpers to perform operations, which use Git commands, and finally the refresh system updates the UI.
Sources: pkg/gui/gui.go63-151 pkg/gui/layout.go12-201
The GitCommand struct (pkg/commands/git.go20-44) aggregates all Git operation types:
Each command struct (e.g., BranchCommands) focuses on one Git subsystem and provides methods that construct and execute Git commands. The Loaders struct contains loaders for parsing Git output into models.
The OSCommand (pkg/commands/oscommands/) provides cross-platform OS operations like clipboard access, file operations, and generic command execution.
Sources: pkg/commands/git.go19-189 pkg/commands/oscommands/
Lazygit maintains separate state for each repository worktree in RepoStateMap:
Diagram: Multi-Repository State Management
When switching repositories or worktrees, Lazygit restores the previous state from RepoStateMap, preserving the selected item, context stack, active modes (filtering, cherry-picking), and view configuration.
Sources: pkg/gui/gui.go79-81 pkg/gui/gui.go232-259 pkg/gui/gui.go540-602
The resetState() method (pkg/gui/gui.go540-602) either reuses existing state or creates new state:
Sources: pkg/gui/gui.go540-602
Lazygit uses a three-tier configuration system:
~/.config/lazygit/config.yml.git/lazygit.yml and parent directory .lazygit.yml filesThe configuration hierarchy is validated against a JSON schema (pkg/jsonschema/). When switching repositories, Lazygit reloads repository-specific config files (pkg/gui/gui.go334-342).
Diagram: Configuration Hierarchy and Flow
Some configuration options (like Git.AutoFetch, Refresher.RefreshInterval) require a restart to take effect. When these change, Lazygit shows a warning popup (pkg/gui/gui.go486-536).
Sources: pkg/config/ pkg/gui/gui.go334-342 pkg/gui/gui.go486-536
Keybindings are defined at multiple levels:
GetKeybindings()GetInitialKeybindings() (pkg/gui/keybindings.go78-418)Diagram: Keybinding System
The resetKeybindings() method is called during startup and when the config is reloaded. It clears all existing keybindings and re-registers them. Custom command keybindings are prepended to take precedence over default keybindings.
Sources: pkg/gui/keybindings.go78-548 pkg/gui/services/custom_commands/
The refresh system is managed by RefreshHelper (pkg/gui/controllers/helpers/refresh_helper.go). After most Git operations, controllers call gui.c.Refresh() to reload affected models:
Diagram: Refresh System Flow
The RefreshMode determines execution behavior:
SYNC: refresh blocks the UI threadASYNC: refresh runs in backgroundBLOCK_UI: refresh runs in background but blocks user inputSources: pkg/gui/controllers/helpers/refresh_helper.go pkg/gui/view_helpers.go127-164
Lazygit follows seven design principles documented in VISION.md:
| Principle | Description | Architectural Impact |
|---|---|---|
| Discoverability | Make features easy to find and understand | Tooltips, menu items, keybinding hints in view titles |
| Simplicity | Simple use cases should be dead-simple | Sensible defaults, minimal prompts for common operations |
| Safety | Protect users from irreversible mistakes | Confirmation prompts, undo system (docs/Undoing.md) |
| Power | Support complex workflows | Custom commands, interactive rebase, patch operations |
| Speed | Enable lightning-fast workflows | Minimal keypresses, keybinding consistency, muscle memory |
| Conformity with git | Align with Git's behavior | Honor git config, avoid storing Lazygit-specific state |
| Think of the codebase | Maintainability matters | Clear separation of concerns, avoid feature bloat |
These principles often conflict (e.g., safety vs. speed), requiring judgment calls. The general approach is to default to safe and simple behavior while providing configuration options for power users to optimize for speed.
Sources: VISION.md1-105
Lazygit distinguishes between views (UI rendering units defined by gocui) and windows (logical screen sections). Multiple views can occupy the same window (e.g., commits view and reflog commits view both occupy the "commits" window).
Diagram: Windows, Views, and Contexts
The WindowViewNameMap (pkg/gui/gui.go247) tracks which view is currently visible in each window. Tabs at the top of windows allow switching between views. When a context is activated, its associated view becomes visible in its window.
Sources: pkg/gui/gui.go232-259 pkg/gui/view_helpers.go60-118 docs/dev/Codebase_Guide.md74-76
Lazygit uses two testing approaches:
*_test.go) for individual functions/methodsIntegration tests use a special test mode where Lazygit reports when it transitions from "busy" to "idle" (docs/dev/Busy.md), allowing tests to wait for operations to complete before making assertions. Tests run against multiple Git versions (2.32.0 minimum, 2.38.2, 2.44.0, latest) to ensure compatibility.
Sources: pkg/integration/README.md docs/dev/Busy.md1-79 CONTRIBUTING.md217
Refresh this wiki