lazygit's default build disables compiler optimizations and its two task runners disagree
lazygit is a terminal interface for staging files, inspecting diffs, managing branches, rebasing, and resolving common Git tasks.
At a glance
- What is it?
- lazygit is a Go terminal UI for git, and the interesting parts of the repository are in its build files rather than its feature list. Both task runners compile with optimizations switched off, the Makefile and the justfile give the same work different names, they disagree about whether integration tests run on Windows, docs/ and schema/ each have a -master twin, and dependencies are committed to a vendor directory.
- Who is it for?
- lazygit suits a developer who wants a keyboard-driven git workflow and is willing to read the keybinding cheatsheet the project generates. It does not suit a Windows contributor expecting the documented test target to behave the way the justfile describes it, and it does not give you an optimized binary from the default build.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The default build target compiles with optimizations switched off
Both build recipes in this repository pass the same flags to the Go compiler:
go build -gcflags='all=-N -l'The justfile says why, in a comment directly above its build recipe: build lazygit with optimizations disabled, to make debugging easier. The Makefile's build target carries the identical command and no comment at all.
The consequence is that a binary produced by the obvious command is a debug build. The -N flag disables optimizations and -l disables inlining, across all packages rather than just this one. So a local ./lazygit that you profile, time, or hand to a colleague is not the binary that go install produces, and go install is a separate target that does not carry the flags.
Nothing is wrong with the arrangement. It is a sensible default for the person writing the code, and the release tooling is elsewhere. But it is a trap for anyone measuring, and the justfile comment is the only place in the repository that mentions it. If a local build feels slower than the packaged one, this is why.
The Makefile and the justfile disagree about Windows and about names
Two task runners sit in the top level, a Makefile and a justfile, and they are not the same file expressed twice.
The justfile is platform aware. Its test recipe is declared twice, once for unix with dependencies unit-test and e2e, and once for windows with unit-test alone, carrying the comment that on Windows, integration tests are not supported right now. The Makefile's test target reads test: unit-test integration-test-all, with no platform guard anywhere in the file.
The naming diverges too. The same work is integration-test-all in the Makefile and e2e in the justfile. A visible-UI integration run is integration-test-tui in one and e2e-tui in the other. The headless integration command in the justfile carries a thirty minute timeout, go test -timeout 30m pkg/integration/clients/*.go, while the Makefile's equivalent line omits the timeout.
For a contributor this is a coverage question rather than a style question. Scripts written against the justfile will attempt integration tests on Windows if they were ported to make, and a habit built on one runner quietly covers less on the other.
The lint target runs a shim script and a Go tool side by side
Formatting and linting reach their tools by two different mechanisms. The format target runs go tool gofumpt -l -w ., so gofumpt arrives as a Go tool dependency declared in the module rather than as something on your PATH. The lint target then runs two shell scripts:
./scripts/gofumpt-check.sh
./scripts/golangci-lint-shim.sh runThe first checks formatting, the second wraps golangci-lint. The name is the tell: it is a shim, which means the project expects the real binary to be somewhere the script has to find, and possibly to install. The configuration for that linter is .golangci.yml at the top level.
So there are three tool-provisioning paths in one repository: a Go tool directive, a shell script that checks, and a shim that runs. For a new contributor the common first failure is a lint error that is really a missing binary, and the shim's name is the only clue in the output that this is what happened.
The demo is a recorded terminal session, not an edited asset
The top level carries a demo/ directory holding three files: a README, a config.yml, and record_demo.sh. The Makefile exposes it as a target whose recipe is the script itself, demo/record_demo.sh, with the remaining make goals passed through as arguments.
That means the demonstration on the front page is generated by driving a real terminal against a real repository, with a config file supplying the settings. Regenerating it is an operation on somebody's working environment, not an edit to a file, and it will differ by machine unless the config pins everything the recording depends on.
The same requirement explains two things elsewhere in the tree. creack/pty is a direct dependency in go.mod, which is a pseudo-terminal library, and the integration tests live under pkg/integration with clients written as Go test files. A terminal UI cannot be tested without a terminal, so the project carries a pty implementation, a headless client runner with a thirty minute timeout, and a visible-UI mode for watching a single test run.
The bump targets for gocui and lazycore sit next to the demo target, which is the same idea applied to dependencies: scripts that move a pinned version forward.
docs/ and schema/ each have a -master twin, and nothing says which to edit
The top level contains four directories that look like two pairs: docs/ and docs-master/, schema/ and schema-master/. Two directory trees, each with a plain name and a suffixed name.
The plain names are the ones a contributor would find by searching, and the suffixed ones are the ones a release step would consume. The repository has a .goreleaser.yml at its top level, which is where packaging behaviour would be configured, and the presence of the generate target, go generate ./..., with its comment about auto-generated files covering the test list, cheatsheets, and the json schema, points the same way.
What the repository does not do is state which direction the copy runs. That matters most for schema/, because a JSON schema is something an editor reads. If you change the one your editor loads and the packaged artifact is built from the other copy, your validation change never ships, and nothing in the build fails.
Confirm which directory feeds the release before editing either. The generate target and the goreleaser configuration are where the answer lives.
Dependencies are committed to vendor/, so a version bump is a code diff
There is a vendor/ directory in the top level, and the Makefile has a target that creates it, running go mod tidy followed by go mod vendor.
That is a deliberate trade. A build no longer needs to reach a module proxy to compile, which makes offline work, hermetic containers, and reproducible binaries straightforward. The cost lands on dependency updates, because upgrading a library is no longer a line in go.mod and go.sum, it is a committed diff containing the library's own source. Reviewing that diff means reviewing somebody else's code rather than reading a version bump, and the size of it is the library's size.
The environment story is more fragmented than the dependency story. There are three Nix files, default.nix, shell.nix, and flake.nix, plus a flake.lock, plus a .devcontainer/, plus a .vscode/ directory. That is four ways to arrive at a working environment and no indication in the front page of which one the maintainer uses.
The go.mod itself asks for Go 1.25.0 and lists about thirty direct dependencies, from a terminal cell library to a fuzzy matcher to a git todo parser.
Three sponsor blocks and an avatar wall sit above the one-line description
The README opens before it describes anything. There is a special thanks block with Warp, the intelligent terminal, marked available for macOS and Linux, then Tuple, described as a screen sharing app for developers on macOS and Windows, then Subble, where the author notes he co-founded it to find unused and over-provisioned SaaS licences. Each block is separated by a horizontal rule.
Below that comes a Sponsors section with a long row of avatars, a paragraph saying maintenance of this project is made possible by all the contributors and sponsors, and then the actual description: a simple terminal UI for git commands.
The rest of the page is the argument for why that UI exists, and it is an argument about friction rather than features. It complains that interactive rebasing requires editing a TODO file in an editor, that staging part of a file means stepping through each hunk and sometimes editing a patch file by hand, and that stashing before a branch switch is required even when there would have been no conflict.
The funding paragraph is honest about sponsorship and silent about maintenance. There is no maintainer named and no governance file in the listing, while VISION.md, AGENTS.md, and CLAUDE.md sit in the top level unreferenced from the front page. Those three are where the project's own statements of direction and contributor instructions live, and they are further from the reader than the sponsor logos.
Editorial conclusion
lazygit suits a developer who wants a keyboard-driven git workflow and is willing to read the keybinding cheatsheet the project generates. It does not suit a Windows contributor expecting the documented test target to behave the way the justfile describes it, and it does not give you an optimized binary from the default build. Before benchmarking or shipping a local build, remember that make build and just build both disable optimizations while make install does not, pick one task runner for your own scripts, and check whether schema/ or schema-master/ is the copy your editor should be reading.
Frequently asked questions
What is a lazy Git?
lazygit is a terminal interface for staging files, inspecting diffs, managing branches, rebasing, and resolving common git tasks, described on its front page as a simple terminal UI for git commands. It is written in Go, MIT licensed, and its default branch is master.
Is LazyGit good for beginners?
The README makes no claim about skill level. What it argues is that git is capable but awkward in daily use, citing interactive rebasing that requires editing a TODO file in an editor, staging part of a file that means stepping through hunks, and editing a patch file by hand when a hunk will not split further.
how to install lazygit
The README links the Homebrew formula at formulae.brew.sh/formula/lazygit, the GitHub releases page, and a separate latest-release badge. The most recent releases are v0.65.1, v0.65.0, and v0.64.1, and the module requires Go 1.25.0.
how to use lazygit in neovim
The README does not document a Neovim integration. What the repository does ship is a JSON schema under schema/ and schema-master/, a .vscode/ directory, and AGENTS.md and CLAUDE.md at the top level alongside a vendor/ directory of committed dependencies.
Which is better, GitUI or lazygit?
The README does not compare lazygit with other git interfaces. It links a Go report card, Codacy grade and coverage badges, and a golangci-lint badge, and its build files point to generated cheatsheets and a JSON schema produced by go generate.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jesseduffield-lazygit)