Why Git Needs a Visible Interface

Lazygit is a terminal UI for Git commands, and its subject is not a new version-control system but the difficult space between Git's capability and a person's immediate intention. This opening asks why a tool that still relies on Git can make everyday work feel more understandable, and it gives the reader a practical model for judging that difference: inspect the repository state, select the intended unit of change, invoke a Git-native transition, and inspect the state that comes back. The aim is not to make Git's consequences disappear. It is to make them visible early enough that the user can act deliberately.

Git is powerful partly because it represents many kinds of change: edits in a working tree, content selected into the index, commits arranged in a graph, branches pointing into that graph, and collaboration through remotes. The difficulty is that these are related but not identical states. A command can be correct while still being hard to aim. Interactive rebasing asks the user to edit a TODO file; partial staging can require reasoning about an arcane patch; switching branches can encourage a stash or a temporary commit even when the working copy could have remained intact. These are not failures of Git's underlying model. They are failures of the interface between intention and that model.

Lazygit addresses that interface in a terminal, where space is limited and keyboard input is fast. Its central design choice is to keep repository objects and their consequences together on screen: a file or commit can be selected in one view, its diff or history can appear in another, and an action can return the user to an updated representation of the same repository. That choice creates the theme for this series: Git becomes easier to use when state, intent, transition, and feedback form one visible loop.

flowchart LR A["Repository state"] --> B["Visible selection"] B --> C["User intent"] C --> D["Git transition"] D --> E["Refreshed state"] E --> F["Inspect or continue"] F --> B

The friction is a problem of translation

Consider three familiar moments. You have made two logically separate edits in one file and want only one of them in the next commit. You have received review feedback on an older commit and want the correction to remain easy to inspect before the branch is merged. Or you need to move from one branch to another while keeping unfinished work available. Git has primitives for all three situations: index manipulation, interactive rebase and fixup commits, and worktrees. What is difficult is translating a human statement—“stage this part,” “put this correction there,” or “let me work on both”—into the right sequence of Git state changes.

Lazygit makes that translation concrete. Its feature surface is arranged around files, commits, branches, worktrees, remotes, stashes, diffs, and the reflog rather than around a flat list of commands. Selecting a file can expose its staged and unstaged differences. Selecting a commit can expose the files and patch that produced it. Selecting a branch can expose its relationship to an upstream or another branch. The point is not that every object is always visible at once; the point is that a selection has a meaningful referent, and the main view supplies evidence about what that referent contains.

This is why the tool's usefulness cannot be measured simply by counting shortcuts. A shortcut is valuable when it preserves the user's relationship with the state being changed. Pressing a key to stage a line is different from pressing a key that silently stages a file: the former expresses a narrower intention. Reordering commits from a visible list is different from asking a user to infer the same operation from a text file: the former makes the affected objects easier to inspect before the rewrite. Creating a worktree is different from hiding unfinished changes in a stash: the former changes the topology of the workspace so the parallel work remains explicit.

The terminal setting also explains a tension. A graphical application can spend more space on labels and mouse affordances; a terminal UI must use compact panels, keybindings, focus, and contextual help. Lazygit's vision therefore treats discoverability as an engineering problem. Tooltips, a keybindings menu, visible effects, visual markers, and prominent indications of an in-progress rebase all compensate for the fact that a keyboard-driven interface cannot explain everything in the frame at once. The interface is compact, but it is not supposed to be cryptic.

Convenience is valuable only when the model survives

The project's design principles make the trade-off explicit. Simplicity says that common work—staging files, committing, pulling, and pushing—should be easy, with sensible defaults and few unnecessary options. Power says that complex but common flows, such as interactive rebasing, should not force a user back to the command line. For rare edge cases, the custom-command system provides an escape hatch instead of adding another permanent built-in control. These goals point in opposite directions: more capability can mean more menus, more settings, and more concepts to remember.

Safety pulls against speed in the same way. A confirmation before a hard-to-reverse action adds a pause, but it also creates a deliberate boundary around actions such as force-pushing or discarding work. Undo and the Escape key offer ways out of transient mistakes. The project's stated resolution is not to eliminate the conflict but to make safety and simplicity the defaults, then let experienced users choose faster or more powerful paths through configuration or separate bindings. That is a product judgment, not a mathematical guarantee. A confirmation can prevent an accidental press; it cannot make a remote push reversible.

Conformity with Git is the constraint that keeps convenience from becoming concealment. Lazygit invokes Git and reads the repository state Git records. It aims to honor Git configuration, represent repository states rather than inventing a second source of truth, and avoid unnecessary application-specific session state. That means a reader who understands what the index, a commit graph, a branch reference, or a reflog entry means can transfer that understanding into lazygit. The interface reduces operational friction, but it does not remove the underlying model.

The same principle explains why lazygit's “undo” is powerful but bounded. It reads the reflog and can walk back recorded commit, branch, checkout, or rebase movements, including actions performed outside the application. Yet the reflog does not describe every kind of change. The documented undo feature does not cover working-tree or stash changes, cannot reverse a push to a remote, and does not undo the creation of a branch simply because the branch is not a reflog movement in the required form. It also does not support undoing a rebase while that rebase is still in progress. The honest mental model is therefore not “lazygit has a universal undo button,” but “lazygit can interpret some Git history of reference movement and offer a guided reversal.”

The visible loop has a real boundary

The loop in the diagram is useful because each step asks a different question. Repository state asks what Git currently knows: which files differ, which commits exist, which branch is checked out, or which operation is in progress. Visible selection asks which object or portion of an object is the subject of attention. User intent narrows that selection into an operation: stage, commit, rebase, create a fixup, switch worktrees, or inspect a conflict. Git transition is the actual mutation, usually performed by Git itself. Refreshed state is the new evidence shown after the operation. Finally, inspect or continue asks whether the result is acceptable or whether an interrupted operation needs another step.

The loop matters because a user can be wrong at more than one point. The wrong file can be selected. A whole hunk can be mistaken for one logical change. A suggested fixup base can be ambiguous. A conflict can be marker-free while still being semantically wrong. A refreshed panel can prove that Git accepted a command without proving that the resulting code expresses the intended behavior. A visible interface helps with selection and feedback; it does not supply human judgment about the meaning of the code.

That boundary becomes especially important when lazygit is extended. Configuration can change panel layout, colors, keybindings, editors, and diff presentation. Custom commands can receive selected commits, files, branches, or ranges, ask for prompts, run shell commands, and show output in a log or popup. These capabilities are valuable precisely because Git workflows vary, but they introduce additional execution surfaces. An external command, shell function, authentication tool, or diff renderer is part of the user's environment, not a safety layer supplied by lazygit. The more a workflow leaves the built-in path, the more important it is to keep the command, its arguments, and its output observable.

The same caution applies to speed. A responsive UI is not merely pleasant; it must avoid showing a stale repository as though it were current. Lazygit separates the event-loop work that owns the interface from worker and background work that runs Git commands or refreshes data. It tracks when user-triggered work is still busy, sends updates back to the UI thread, and drops refresh results that belong to a repository the user has already left. Those mechanisms support the visible loop, but they do not make every operation instantaneous. Large repositories, slow Git commands, network access, terminal limitations, and external tools remain part of the experience.

The practical consequence is a way to approach lazygit without memorizing its entire surface. Begin with the state that is visible, not with the command you happen to remember. Decide whether your intention concerns a file, a patch, a commit, a branch topology, or an in-progress operation. Choose the narrowest action that expresses that intention. Then read the refreshed state and ask what it proves, what it does not prove, and whether the next step is continuation, review, recovery, or escalation to Git's own command line.

Lazygit matters because it treats that sequence as the product. Its promise is not that Git can be made simple in the abstract. Its promise is that the distance between a person's intention and Git's state can be shortened without hiding the transition between them. The rest of this series examines where that promise holds, how it is implemented in the interaction model, and where the consequences still demand deliberate judgment.

References