Commit graph

1496 commits

Author SHA1 Message Date
Stefan Haller 1935117141 Add pty support on Windows via ConPTY
Replace the StartPty stub with a real ConPTY implementation:
CreatePipe + CreatePseudoConsole + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
+ CreateProcess. Pagers and external diff tools now get real terminal
behavior instead of being handed pipes.

One Windows-specific quirk worth flagging: ConPTY does not EOF the
output pipe when the child exits; conhost keeps it alive until
ClosePseudoConsole is called explicitly. A background waiter goroutine
calls ClosePseudoConsole as soon as proc.Wait returns, so callers see
EOF on outRead — restoring the Unix master-fd-EOFs-when-slave-closes
semantics they depend on.

The ErrPtyUnsupported sentinel and the no-pty fallback in newPtyTask
are gone now that both platforms have a real implementation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller c85f7530bb Abstract pty startup behind a platform-specific primitive
Move the pty master behind a small interface (Read/Write/Close/Resize),
and push the actual startup into a platform-specific StartPty function
in pkg/commands/oscommands. The Unix implementation still uses
creack/pty; the Windows implementation is a stub that returns
ErrPtyUnsupported, at which point newPtyTask falls back to a plain cmd
task — matching the existing Windows behavior.

The primitive lives in oscommands rather than pkg/gui because the
cmd_obj_runner pty handler (also in oscommands) is going to consume it
too, and tasks → oscommands is the existing dependency direction.

Same observable behavior on every platform; this just carves out a seam
for a real ConPTY implementation on Windows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller 8ec4283e55 Abstract task command over *exec.Cmd
Windows ConPTY can't attach a child process to a pseudoconsole via
os/exec — Go's stdlib doesn't expose PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
(golang/go#62708). The ConPTY path has to call CreateProcess directly,
so it can't hand an *exec.Cmd back to the task runner.

Widen NewCmdTask to accept a small Cmd interface satisfied by both
*exec.Cmd (via the ExecCmd adapter) and the Windows ConPTY command type
we're about to add. Change TerminateProcessGracefully to take
*os.Process, which both cmd shapes can provide.

Behavior is unchanged on every platform.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller 4fcf6ddb5f Revert "Add GlobalArg/GlobalArgIf to GitCommandBuilder"
After the previous commit this is no longer needed.

This reverts commit 94db69f64b.
2026-07-02 16:49:36 +02:00
Stefan Haller ccaa96b29d Suppress optional locks by default again, except foreground refresh
Commit d94f2f05 dropped the GIT_OPTIONAL_LOCKS=0 env var that we used
to set on every git command, and re-added lock suppression only as a
--no-optional-locks flag on the background files refresh. The intent
was sound — a foreground `git status` should persist git's refreshed
stat-cache — but the change was too broad: it stopped suppressing
optional locks for every other command too.

The one that bites is the main-view diff. When a folder containing
submodules is selected, we render `git diff --submodule -- <dir>`, and
`--submodule` makes git run `git status` inside each submodule to
describe its "modified" state. That status now grabs the submodule's
index.lock. It runs as a PTY task on its own goroutine, so it races
any submodule-mutating action the user triggers — e.g. resetting a
submodule runs `git -C <submodule> stash`, which then fails with
"index.lock: File exists". This is what made submodule/reset_folder
flaky. `git status` is in fact the only command that takes the
optional lock, but the env var also covered its use inside `git diff
--submodule`, inside PTY-run commands, and inside git's own submodule
child processes — none of which a per-command flag reaches cleanly.

Invert the polarity to match how it worked before d94f2f05: the git
command builder disables optional locks on every command by default,
and the single command that benefits from taking the lock — the
foreground files refresh — opts back in. This restores the original
contention avoidance (including against the user's terminal git) while
keeping d94f2f05's stat-cache-persistence win for the foreground
refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:05:08 +02:00
Stefan Haller 29b70105c2 Reintroduce the gitCmdObjBuilder wrapper
This reverts commit eb988395e6, which removed the wrapper on the
grounds that it no longer had anything to do but delegate. We're about
to give it a job again: hosting a git-specific default environment
variable on every command. Restore the scaffolding first, as a pure
behaviour-preserving step, so the behaviour change that follows is
minimal.
2026-07-02 15:29:57 +02:00
Stefan Haller 050225ffe6 Show per-side commit logs for submodule conflicts in the main view
When a conflicted submodule is selected, the main view shows the commits
each side added relative to their common ancestor as two indented logs,
labelled current and incoming, so it's clear which commit each side would
resolve to.

The logs aren't truncated (the view scrolls). If a side added no commits
of its own (e.g. it was rewound to an ancestor of the other), its head
commit is shown instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:12:36 +02:00
Stefan Haller afe4d14106 Resolve submodule conflicts through a picker
When both sides of a merge moved a submodule's gitlink, git reports it as "UU".
Pressing space used to fall into the submodule no-op guard and pop the confusing
"Nothing to stage..." error, and enter just entered the submodule, which does
nothing to resolve the superproject conflict.

Treat a conflicted submodule like the other non-textual conflicts: both space
and enter now open a picker offering the two candidate commits, "current" and
"incoming", each labelled with its summary. `git checkout --ours/--theirs` is a
no-op on gitlinks, so we resolve by checking the submodule out at the chosen
commit and staging it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:12:36 +02:00
Stefan Haller 13817665aa Add end-to-end test for shell command quoting on Windows
The unit tests only assert the arguments lazygit constructs; they can't
catch cmd.exe's own quote-stripping, which is where #5560 actually
manifested. This test builds a small editor executable, places it and
the file it opens at paths containing spaces, runs it through real
cmd.exe via NewShell, and checks the editor received the intended args.
It runs only on Windows.

Co-Authored-By: Antoine Gaudreau Simard <a.simard@multidev.net>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:11:00 +02:00
Stefan Haller 6b311ccb62 Fix quoting of shell commands on Windows
lazygit builds a shell command by interpolating Quote'd arguments into a
template and running the result via `cmd /c`. Several things were wrong
on Windows:

- Quote emitted bash-style `\"…\"` quoting, which cmd.exe doesn't
  understand. Making it usable at all previously required a fragile
  round-trip through str.ToArgv and re-escaping.
- The assembled command line was handed to `cmd /c` without `/s`, so
  cmd's default rules stripped the wrong quotes once the line contained
  more than two of them (e.g. a quoted editor path at a location with
  spaces, plus a quoted filename that also contains spaces).
- Shell metacharacters were escaped with `^` (`&` → `^&`, etc.), which
  neutralised command chaining, pipes, redirection and `%VAR%` expansion
  in custom commands.

Quote now emits the standard Windows convention directly, and NewShell
hands cmd.exe the fully-assembled line verbatim via SysProcAttr.CmdLine,
wrapped as `cmd /s /c "<command>"`. The /s flag strips exactly the outer
quote pair we add, leaving each argument's own quoting intact. With the
`^` escaping gone, metacharacters in a custom command reach cmd as the
author intended; this also removes the spurious `^` reported in #3092.

Fixes #5560
Fixes #2427
Fixes #4147

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:11:00 +02:00
Stefan Haller e0fcdf1c3f Demonstrate that shell metacharacters are mangled on Windows
On Windows, NewShell escapes shell metacharacters (`&`, `|`, `<`, `>`,
`%`) with `^` and splits the command into separate arguments. The
operators in a custom command therefore never reach cmd as operators,
so command chaining (`&&`), pipes, redirection and `%VAR%` expansion all
silently break (#2427, #4147, #5113; the stray `^` is also what #3092
reports).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:11:00 +02:00
Stefan Haller 41efc9a37a Demonstrate that Quote produces invalid Windows quoting
On Windows, Quote wraps arguments in bash-style `\"…\"` and rewrites
embedded double quotes as `"'"'"`. Neither convention is understood by
cmd.exe or CommandLineToArgvW, so commands built from quoted arguments
are mis-parsed once they contain quotes or spaces (#5560).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:11:00 +02:00
Stefan Haller cfb46f440c Add HeadCommitIdx helper function
Not used yet, we'll need it in the next commit.
2026-06-22 09:16:57 +02:00
Stefan Haller d673a0f3b9 Add tests for IsHeadCommit 2026-06-22 09:03:14 +02:00
Stefan Haller eb988395e6 Remove the now-redundant gitCmdObjBuilder wrapper
The wrapper existed to add a git-specific env var to every command. Now
that that's gone, its New/NewShell/Quote methods just delegated to the
inner builder. The only remaining git-specific behavior — the command
runner — is attached in the constructor via CloneWithNewRunner, which
already returns a complete builder, so we can return that directly and
drop the wrapper struct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:14:24 +02:00
Stefan Haller d94f2f05ac Only pass --no-optional-locks for background status refreshes
We set GIT_OPTIONAL_LOCKS=0 for every git command we run. That env var
only affects `git status`: it tells git not to take the optional lock it
would otherwise use to write the index back after refreshing the cached
stat information. The intent was to avoid contending for index.lock with
git commands the user runs in a terminal.

The downside is that our `git status` never persists the refreshed
stat-cache. So whenever the working tree's cached stat info goes stale
(e.g. editing files and discarding the changes, or a checkout), every
subsequent status re-hashes the affected files to confirm they're clean,
and stays slow until something else writes the index (such as the user
running `git status` in a terminal).

Fix this by only suppressing optional locks for refreshes that run
unattended in the background; foreground refreshes triggered by a user
action now run a plain `git status` that writes the refreshed index back,
just like the command line does. Background refreshes keep passing
--no-optional-locks so they still can't cause lock contention.

RefreshOptions gains a Background flag that the background routines set,
threaded down to the status command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:14:24 +02:00
Stefan Haller 94db69f64b Add GlobalArg/GlobalArgIf to GitCommandBuilder
This can be used to add a git argument that goes before the git
subcommand.
2026-06-19 18:14:24 +02:00
Stefan Haller cb12cb2f6b Add Status.RefsSnapshot
A cheap fingerprint of local branches and HEAD that future code can poll to
detect when refs have moved externally.

Branches come from a porcelain for-each-ref. HEAD is read directly from
.git/HEAD: that avoids spawning a child process and captures the symref-or-hash
distinction we need to tell "detached at X" apart from "on a branch pointing at
X" — they share a commit hash, which is exactly the situation at the end of a
rebase when HEAD reattaches to the branch. The reftable backend doesn't keep a
real .git/HEAD (it writes a fixed stub), so when we see that stub or the file is
unreadable we fall back to porcelain commands, which are backend-agnostic.

Uses DontLog so a future polling caller won't spam the command log. Not yet
wired up to any caller.
2026-06-19 18:07:48 +02:00
Stefan Haller d81b6d9e1d Log CPU time of external commands in addition to wall-clock time
For certain kinds of performance investigations it is useful to see this, and
doesn't terribly pollute the log, so just do this always.
2026-06-19 18:07:48 +02:00
Stefan Haller 785c8a712c Explain when a submodule has nothing stageable
A submodule that only has dirty or untracked content (no new commit) can't
be staged from the parent repo, but it still shows up as having unstaged
changes. Pressing stage on it therefore briefly flashed as staged and then
reverted, without explaining why nothing was staged.

Detect this case (via `git submodule status`, where a '+' prefix marks a
stageable commit change) in the shared stage/unstage decision: if the only
thing that looks stageable is such a submodule, don't try to stage it.
Instead unstage if there's anything staged to unstage, so the toggle stays
symmetric; otherwise show an error explaining that there's nothing to stage.

Because the decision is shared, this covers both the stage (space) and
stage-all (a) keybindings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:12:09 +02:00
Stefan Haller b76c1072ff Offer direnv .envrc approval from inside lazygit
When a user switches into a repo whose .envrc hasn't been approved with
`direnv allow`, the previous behavior was to drop a "blocked" error
popup and leave the user to fix it externally. That meant opening a
terminal, running `direnv allow`, and then either restarting lazygit or
switching repos and back to refresh the env — easy to get wrong, easy
to forget.

When `direnv export json` exits non-zero, follow up with `direnv status
--json` to ask direnv whether the current directory has a not-yet-
allowed .envrc, and if so, get its path. Then show a confirmation popup
with the .envrc contents inline so the user can read what they're
approving. Confirming runs `direnv allow <path>` and re-runs the load
so the new env reaches subprocesses immediately; cancelling leaves the
env unloaded (the same state as before this commit when direnv refused
to load the .envrc).

Using `direnv status --json` instead of parsing the "is blocked"
stderr line means we rely on direnv's structured output rather than
its human-readable error format, which is more stable across versions
and avoids assumptions about output formatting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 09:05:01 +02:00
Stefan Haller bb8955f2de Load direnv environment when switching repos
When a user opens a repo from the recent-repos menu or jumps between
worktrees inside lazygit, only the env vars present at process startup
reach subprocesses. That breaks pre-commit hooks and other tools whose
dependencies are pulled in by a per-repo .envrc — users were left with
read-only operations because the env their shell would normally load via
direnv never made it into lazygit's git invocations.

Shell out to `direnv export json` after each chdir and apply the JSON
delta via os.Setenv/Unsetenv. direnv tracks the previous load in its own
DIRENV_DIFF env var, so the delta also unloads vars from the old repo
when entering one without a matching .envrc. If direnv isn't on PATH the
call is a no-op, so users who don't use direnv pay nothing and users who
do need no config to opt in. Any stderr direnv emits (loading messages,
"blocked .envrc" errors, etc.) goes to the command log.

The integration test puts a fake direnv on PATH and asserts that a value
it exports reaches a custom command after switching repos. Wiring this
up needed runner.go to support `{{actualPath}}` placeholders in
ExtraEnvVars, mirroring the existing support for ExtraCmdArgs, so the
test can prepend a fixture-relative directory to PATH.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 09:05:01 +02:00
Henry Maddocks dd0d90837d Add support for git flow using git-flow-next
git-flow-next (https://github.com/gittower/git-flow-next) uses a
different config schema than legacy git-flow:
gitflow.branch.<type>.prefix instead of gitflow.prefix.<type>.
Recognize both schemas in GetGitFlowPrefixMap by querying each and
merging into a single prefix → branchType map. GitFlowEnabled now
consults the merged map so a next-only setup counts as enabled.

When both schemas configure the same prefix, the legacy entry wins.
In normal usage both schemas agree, so the rule mainly matters as a
deterministic tie-breaker.
2026-05-26 11:57:09 +02:00
Henry Maddocks 415015c66a Pull git-flow prefix parsing into a config-level helper
Lift the inline parsing in FinishCmdObj into parseGitFlowPrefixMap on
ConfigCommands. The caller now does a direct map lookup against the
parsed prefix → branchType map instead of iterating the raw config
output and suffix-matching. This is preparation for adding git-flow-next
support, which needs to merge a second config schema into the same map.

One incidental change: a branch name without a slash now returns
NotAGitFlowBranch immediately, rather than falling through the
line loop with an empty suffix. Previously a configured
gitflow.prefix.X whose value happened to equal the entire branch
name could match — never a useful outcome.
2026-05-26 11:05:22 +02:00
Stefan Haller 692f56a61b Optimize regex compilations
Compile them only once at startup. I didn't measure if this makes a difference,
but it's easy to do, and now that we potentially need to check them more often,
it might be worth it.
2026-05-10 15:28:14 +02:00
Stefan Haller d955ba8fb8 Remove now unused code
Doing this in a separate commit makes the previous commit's diff easier to read.
2026-05-10 15:28:14 +02:00
Stefan Haller 1c79fe24d0 Support GitHub Enterprise for the pull-requests feature
The branches-panel PR icons only worked for github.com remotes. There was no
fundamental reason — the auth library we already vendor (cli/go-gh) supports
enterprise tokens out of the box (GH_ENTERPRISE_TOKEN, gh auth's keyring),
and the user-facing 'services' config has long been the documented way to
tell lazygit "this domain is a github service" for the View-PR-URL feature.

The fetcher just hardcoded github.com in three places:
- a substring check on the remote URL to decide we're "in a github repo",
- the GraphQL endpoint (always api.github.com/graphql), and
- the auth lookup (always against the default host).

Plumb the resolved web domain through instead. Detection now goes through
the hosting_service ("is this remote's provider 'github'?"), which means a
user with services: { 'git.acme.com': 'github:git.acme.com' } configured
gets PR icons on their GHE remotes too.

Replacing the substring check with a provider check also tightens a latent
bug in getGithubRemotes: it previously accepted any remote whose URL parsed
with the default regex, including gitlab and bitbucket — masked today only
by the InGithubRepo gate, but exposed once the gate goes away.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:28:14 +02:00
Stefan Haller 21d58085f4 Expose service info (provider, host, owner, repo) from the hosting service
The github pull-request fetcher needs to know whether a given remote is a
github-type service, which host its API lives on, and which owner/repo to
query against. Today the fetcher hardcodes the first two ("does the URL
contain github.com" and "https://api.github.com/graphql") and re-derives
owner/repo from the remote, which precludes GitHub Enterprise and makes
the fetch entry point take more arguments than it needs.

Add an accessor on the hosting service manager that exposes the already-
resolved service domain together with the parsed owner/repo, so callers
can answer all of these questions without reaching into the manager's
internals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:28:14 +02:00
Stefan Haller d0ed65e0fd Add PushUrls field to models.Remote
Add some tests for RemoteLoader while we're at it; we didn't have any.
2026-05-04 13:09:47 +02:00
Stefan Haller 8dbdd74400 Fix linter warnings about ignoring errors from bufio.Scanner 2026-05-03 16:57:24 +02:00
stk 7fbc2a8c14 Close temp file handles before cleanup on Windows
Windows cannot remove files while handles are still open. In
TestOSCommandFileType we created files and immediately called RemoveAll without
closing handles, which left untracked artifacts (e.g. "testFile" and "file with
spaces") in the working tree. Close the handles and assert RemoveAll succeeds so
cleanup failures are visible.
2026-05-02 17:59:27 +02:00
Stefan Haller 196e0a3c17 Copy gocui files into lazygit repo under pkg/gocui
I copied all files except dot files (.github and .gitignore), the _examples
folder, and go.mod/go.sum.

At some point we may want to copy the files back to the gocui repo when other
clients (e.g. lazydocker) want to use the newer versions of them.
2026-04-30 14:29:08 +02:00
Antoine Gaudreau Simard 3d14881f05 perf(branches): use for-each-ref ahead-behind for git >= 2.41
Greatly improves performance for showDivergenceFromBaseBranch config
Fixes #5532
2026-04-27 07:09:23 +02:00
Stefan Haller 4ab36461cb Tolerate trailing newlines in AddCoAuthorToDescription
Callers currently hand this function a trimmed description, so the output is
always clean. An upcoming change to the commit-panel getters will stop trimming
at the callsite (so that whitespace typed by the user round-trips through the
preservation file exactly), at which point the description can end with one or
more newlines. Without this change, a user who presses Enter after their
description body and then invokes "Add co-author" would end up with two blank
lines between the body and the trailer instead of the expected one.
2026-04-21 18:51:17 +02:00
majiayu000 a42d79f228 retry on ref lock errors during fetch/pull
Extends the existing retry logic to handle transient lock-related errors beyond just .git/index.lock. This now also retries when encountering "cannot lock ref" or "cannot update ref" errors, which can occur intermittently during fetch/pull operations in large repositories.

Related to #5124

Signed-off-by: majiayu000 <1835304752@qq.com>
2026-04-20 09:04:50 +02:00
Bradly Chang 6eda4c0aab
Fix case-insensitive remote URL matching for GitHub PRs
Normalizes the repository owner to lowercase during the PR
mapping.
This ensures that PR icons and integration features work correctly even
when the local git remote URL casing differs from the official
repository casing on GitHub.
2026-04-10 01:36:10 +08:00
Jesse Duffield 1c89398288 Add GitHub commands and model for fetching PR status
Add GitHubCommands struct with GraphQL-based PR fetching, and
GithubPullRequest model. Wire HostingService and GitHub command
structs into GitCommand.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-04-01 09:13:55 +02:00
Jesse Duffield 4cd72b09a2 Refactor hosting service to support repo name extraction
Extract parseRemoteUrl helper to avoid duplication between
getRepoURLFromRemoteURL and new getRepoNameFromRemoteURL.
Add repoNameTemplate to ServiceDefinition and GetRepoName() to
HostingServiceMgr. Add GetRepoInfoFromURL for callers that need
owner/repo without a full service lookup.
2026-03-31 15:48:28 +02:00
phanium 41cd78af48 Fix panic when branch.autosetuprebase is set
It actually expect `branch.<name>.*`

`man git-config | rg "\s+branch\.\S+\$"` to show all possible keys.
2026-03-31 09:58:57 +08:00
Stefan Haller 2a6648f0f6 Remove empty directories after discarding files
When discarding untracked files, remove any directories that have become empty
because of this.
2026-03-30 17:50:28 +02:00
Stefan Haller b4dae453af Extract helper method removeFiles 2026-03-30 17:50:28 +02:00
Stefan Haller 46d8437bc2 Change test setup to allow nested directories 2026-03-30 17:50:28 +02:00
Matthijs Kooijman 590d6f2f80 Fix patch commands when git config has color=always
This is the same option as used by pkg.commands.git_commands.diff.GetDiff().
2026-03-30 10:42:26 +02:00
Stefan Haller 4d46f5af94 Improve performance for discarding range selection of many files
This is probably the less severe case, but it could still be an issue for people
who have many modified top-level files they want to discard, and have
showRootItemInFileTree set to false; they could select all those files by
pressing 'v' and '>'.
2026-03-27 14:34:47 +01:00
Stefan Haller ad31400818 Improve performance of discarding changes in large directories
Previously it would iterate over all changed files and call git checkout or git
reset for each one, which can take forever if there are hundreds or thousands of
files. Now it batches these into a single command if possible (taking care of
still passing the individual path names to the git call rather than just the
directory, which is necessary for making it work correctly when filtering --
this was actually broken for the "Discard unstaged changes" command, which is
fixed here).
2026-03-27 14:34:47 +01:00
Stefan Haller e434f5b5e9 Add runGitCmdOnPaths utility
Useful when we need to call git with potentially tons of arguments that might
exceed the OS' command-line length limit.
2026-03-27 14:34:47 +01:00
Stefan Haller 5b829a6721 Add unit tests for WorkingTreeDiscard{All,Unstaged}DirChanges
We have integration tests for this functionality, but those only test the
behavior, not the performance. In these unit tests you can see that we make
individual calls to git checkout and git reset for each file, which is very slow
when there are lots of files.
2026-03-27 14:34:47 +01:00
Stefan Haller 4aa455e4eb Cleanup: better check for which files are removed
Previously it would only check that *if* removeFile was called, the passed
argument was the expected one; but it didn't check whether it was called at all.
Improve this by recording the file names that are removed, and checking them at
the end of each scenario.

This is going to be even more important for the tests that we are about to add
in the next commit, because for those there can be several calls to removeFile
in a single scenario.
2026-03-27 14:34:47 +01:00
Stefan Haller 706a6c0474 Cleanup: use oscommands.RemoveFile rather than plain os.Remove
This makes it mockable for tests, and is consistent with other uses in this
file.
2026-03-27 14:34:47 +01:00
Stefan Haller a930f74397 Remove go-git repo from GitCommon
It is no longer used now.
2026-03-27 14:08:07 +01:00
Stefan Haller 6f9d234116 Remove go-git repo from ConfigCommands 2026-03-27 14:08:07 +01:00
Stefan Haller 1da87b9a7c Don't use go-git to parse branches
As with the previous commit, the code to parse `git config --get-regex` output
on our end isn't so bad.
2026-03-27 14:04:51 +01:00
Stefan Haller 23f973496f Don't use go-git to parse remotes
It only takes a few lines of code to parse remotes on our end from the output of
`git config --get-regexp`.
2026-03-27 14:00:58 +01:00
Stefan Haller 6bfcab3d89 Fix selection after staging an added line
In some cases, staging an added line could result in the previous deleted line
to become selected, rather than the next added line.
2026-03-19 16:57:53 +01:00
Stefan Haller 8fbc70bf84 Fix staging only some lines of a block of consecutive changes
This fixes the problem; a consequence of this change is that given the following
scenario:

@@ -1,3 +1,3 @@
 1
-2
+2b
 3

staging only the line `+2b` will put it *before* the unchanged `2` line, rather
than after it as you might expect (the changed unit tests demonstrate this).
Since this should be a pretty uncommon scenario, I guess it is an ok compromise.

As you can see in the changed tests, while the behavior of what gets staged is
fixed now, it doesn't always correctly select the next line to stage. We'll
address this in the next commit.
2026-03-19 16:57:53 +01:00
Zak Siddiqui 7d32e45f73 Add backward cycling support for all branches log view
This commit implements the ability to cycle backward through different
all branches log visualization modes, complementing the existing forward
cycling functionality. Users can now press 'A' (Shift+a) to cycle in
reverse through the available log graph views, improving navigation
efficiency when they overshoot their desired view.

Changes:
- Added RotateAllBranchesLogIdxBackward() method to BranchCommands for
  backward rotation through log command candidates
- Introduced AllBranchesLogGraphReverse keybinding configuration with
  default key 'A' (uppercase)
- Implemented switchToOrRotateAllBranchesLogsBackward() handler in
  StatusController that mirrors forward cycling logic
- Added English translation for "Show/cycle all branch logs (reverse)"

The implementation uses modulo arithmetic with proper handling of
negative indices to ensure seamless backward cycling through the
available log visualization options.
2026-03-08 17:06:17 +01:00
cobyfrombrooklyn-bot 30154aa9c5 Fix #5302: Create .git/info directory before writing exclude file
When .git/info directory does not exist (can happen with bare clones
or manual deletion), the Exclude function failed with 'no such file
or directory'. Added os.MkdirAll to create the directory before
opening the exclude file.

Added integration test exclude_without_info_dir that removes .git/info
before attempting to exclude a file.
2026-03-08 16:23:39 +01:00
Ruud Kamphuis 727b4b56dd Show branch name and detached HEAD in worktrees tab
The worktrees tab now displays the checked-out branch (or detached HEAD
state) for each worktree, making it much easier to see which worktree
holds which branch.

This is useful when you need to "unlock" a branch that's occupied by
another worktree: you can now clearly see which worktrees are on a
branch vs detached, without having to select each one individually.

Also rename the "(main)" label to "(main worktree)" to avoid confusion
with the common "main" branch name, and move it after the branch column.
2026-03-08 12:51:46 +01:00
Jesse Duffield 615c566ac4 Filter file views rather than search
Change working tree files and commit files panels to use filtering
(reducing the list) instead of search (highlighting matches). This
matches the behavior of other filterable views.

The text filter matches against the full file path, not just the
filename, which is more useful for navigating large directory trees.

When toggling a directory for a custom patch while a text filter is
active, only the visible filtered files in the directory are affected,
consistent with how staging a directory in the files panel works.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:04:31 +01:00
Dawid Pietrykowski 48a8a71d31 Fix gitignore path collisions
Paths added to ignore/exclude files need to be prefixed with a forward
slash to point to a specific file in the directory tree.

Without that prefix a file at root called `file` (added to `.gitignore`
as `file`) would match with `./file` and `./src/file`.
2026-01-28 21:10:29 +01:00
Jesse Duffield 6f6545e44f Pass todo flag to EditRebaseTodo
Not used yet, we pass an empty string everywhere, to match the previous
behavior. Just extracting this into a separate commit to make the next one
smaller.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-01-28 10:15:51 +01:00
Jesse Duffield de98258e39 Add ActionFlag field to Commit
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-01-28 10:15:45 +01:00
Stefan Haller 8d36c8b3c1 Don't log the "git rev-list" call when marking bisect commits
When marking commits as good or bad during a bisect, a "git rev-list" call would
appear in the Command log. This is confusing, it's an internal detail that is
not interesting for the user to see.
2026-01-25 11:47:07 +01:00
Stefan Haller 7f957f059b Don't log the "git ls-remote" call when opening a PR
When opening a PR, a "git ls-remote" call would appear in the Command log. This
is confusing, it's an internal detail that is not interesting for the user to
see.
2026-01-11 20:02:08 +01:00
Yadi Abdalhalim 24e9197be2 feat: add Codeberg as a supported git hosting service
Codeberg is a Gitea-based git hosting service that uses the same URL
patterns for pull requests and commits. This adds native support so
users don't need to manually configure it.
2025-12-21 14:10:59 +01:00
Mikhail Akimov d6ff7f152d Fix to support creating merge requests for Gitlab/Github repositories cloned with an SSH config alias 2025-12-06 11:52:55 +01:00
riyueguang 13b4e16e6a chore: fix function name in comment
Signed-off-by: riyueguang <rustruby@outlook.com>
2025-12-06 18:28:58 +08:00
boqishan 89a0542a97 refactor: use strings.Builder and strings.Repeat to simplify code
Signed-off-by: boqishan <boqishan@126.com>
2025-12-03 18:34:21 +01:00
Stefan Haller 26453b26cf Remove unused function GetHashesAndCommitMessagesFirstLine 2025-11-30 14:06:31 +01:00
Stefan Haller 11a6a73be5 Fix deleting a remote tag when a remote branch with the same name exists 2025-11-27 19:52:18 +01:00
Stefan Haller 7809823064 Fix deleting a remote branch when a remote tag with the same name exists 2025-11-27 19:51:24 +01:00
Stefan Haller 1c1676a5d9 Use git ls-files to list all file suggestions 2025-11-21 07:57:56 +01:00
Stefan Haller f8a48b61fc Suppress output from background fetch
However, show it when there was an error. This is important for the case that a
fork that you have as a remote was deleted, in which case the command log is the
only way to get notified about that.
2025-11-15 18:27:18 +01:00
Stefan Haller eb91a43d58 Test properties of cmdObj for Push 2025-11-15 18:25:52 +01:00
Stefan Haller 9272276247 Don't log the GetDiff command
This is used for internal purposes and shouldn't pollute the command log.
2025-11-15 18:15:24 +01:00
phanium d88f95275f Modernize all codes
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
2025-11-15 10:46:23 +01:00
hrzlgnm f729e2ca05
Update dead link for debugging regex to regex101.com 2025-11-07 16:00:22 +01:00
hrzlgnm d5c0d945f8
Open pull requests in browser with extra leading slashes removed
This allows for having extra slashes in git urls, for example to avoid
warnings with older bitbake fetcher implementations in yocto. Which
warns about a missing / in git urls
2025-11-07 16:00:14 +01:00
kyu08 64bcc72e45 Specify return value where named return value is used
After [v0.9.0](https://github.com/mvdan/gofumpt/releases/tag/v0.9.0),
gofumpt prohibits "naked return" for the sake of clarity. This makes
more readable when "named return value" is used.
For more infomation for "prohibition of naked return":
https://github.com/mvdan/gofumpt/issues/285.
2025-10-20 07:56:56 +02:00
Stefan Haller 62854026a3 Add no-ff merge option
This will put whatever git's default merge variant is as the first menu item,
and add a second item which is the opposite (no-ff if the default is ff, and
vice versa).

If users prefer to always have the same option first no matter whether it's
applicable, they can make ff always appear first by setting git's "merge.ff"
config to "true" or "only", or by setting lazygit's "git.merging.args" config to
"--ff" or "--ff-only"; if they want no-ff to appear first, they can do that by
setting git's "merge.ff" config to "false", or by setting lazygit's
"git.merging.args" config to "--no-ff". Which of these they choose depends on
whether they want the config to also apply to other git clients including the
cli, or only to lazygit.
2025-10-19 21:24:28 +02:00
Stefan Haller 4e0194d8f7 Replace MergeOpts struct with MergeVariant enum
- Squash and FastForwardOnly are mutually exclusive, and instead of asserting
  this at runtime, model the API so that they can't be passed together.
- FastForwardOnly is unused, so remove it; however, we are going to need --ff
  and --no-ff in the next commit, so add those instead.
- Instead of putting the enum into the MergeOpts struct, replace the struct by
  the enum. We can reintroduce the struct when we add more arguments, but for
  now it's an unnecessary indirection.
2025-10-19 21:24:28 +02:00
Stefan Haller e44d6ec330 Replace paging config with an array of pagers 2025-10-14 12:17:13 +02:00
Stefan Haller 765c9eb85c Add PagerConfig
This is an object that is owned by Gui, is accessible through GuiCommon.State(),
and also passed down to GitCommand, where it is mostly needed. Right now it
simply wraps access to the Git.Paging config, which isn't very exciting, but
we'll extend it in the next commit to handle a slice of pagers (and maintain the
currently selected pager index), and doing this refactoring up front allows us
to make that change without having to touch clients.
2025-10-14 12:17:13 +02:00
Stefan Haller ed05470732 Cleanup: remove unused method ConfiguredPager
This could have been removed in commit 9657b4346f.
2025-10-14 12:17:13 +02:00
Karen Grigoryan e6f3313d9a Fix support for Git copy status when status.renames=copies
Fixes #4890

When Git is configured with status.renames=copies, it can produce
status codes starting with "C" (copy) in addition to "R" (rename).
The file loader was only checking for "R" prefixes, causing copy
operations to be parsed incorrectly and breaking file staging.

This fix extends the status parser to handle both "R" and "C"
prefixes, ensuring proper support for Git's copy detection feature.

Fixes file staging issues when using status.renames=copies configuration.
2025-10-14 11:57:12 +02:00
Stefan Haller c1e52fc807 Fix dropping submodule changes from a commit
Our logic to decide if a file needs to be checked out from the previous commit
or deleted because it didn't exist in the previous commit didn't work for
submodules. We were using `git cat-file -e` to ask whether the file existed, but
this returns an error for submodules, so we were always deleting those instead
of reverting them back to their previous state.

Switch to using `git ls-tree -- file` instead, which works for both files and
submodules.
2025-10-10 12:55:58 +02:00
Stefan Haller 5d02cba721 Show "Log (x of y)" in the title bar when there is more than one branch log command 2025-10-10 12:49:42 +02:00
Stefan Haller 220cde1376 Fix potential crash when reloading the config after removing a branch log command
When cycling to the last branch log command, and then editing the config to
remove one or more log commands, lazygit would crash with an out of bounds panic
when returning to it. Fix this by resetting the log index to 0 when it is out of
bounds. (I think resetting to 0 is better than clamping, although it doesn't
matter much.)
2025-10-10 12:49:42 +02:00
Stefan Haller 19f88290a2 Change allBranchesLogCmdIndex to an int
I know that uint was chosen to document that it can't be negative (not sure why
the "8" was chosen, though). That's pointless in languages that don't enforce
this though: you can subtract 1 from uint8(0) and you'll get something that
doesn't make sense; that's not any better than getting -1. I'm not a fan of
using unsigned types in general (at least in languages like go or C++), and they
usually just make the code more cumbersome without real benefits.
2025-10-10 12:49:42 +02:00
Stefan Haller 99a83efc34 Set LC_MESSAGES too
I'm not sure this is necessary, but it doesn't hurt.
2025-10-09 13:10:58 +02:00
Stefan Haller b755f52754 Use C locale instead of en_US.UTF-8 to force English language
Some users reported that en_US.UTF-8 is not available on their systems, leading
to warnings in the command log. "C" also forces the language to English, and is
guaranteed to be available everywhere.
2025-10-09 13:10:02 +02:00
Krystof Gartner 703f053a7e Add merge options menu
Replace merge-tool with merge options menu that allows resolving all
conflicts for selected files as ours, theirs, or union, while still
providing access to the merge tool.
2025-10-09 08:45:58 +02:00
Stefan Haller e585accd37 Give better feedback when checking out the previous branch
Previously, the feedback you got when pressing "-" was just a "Checking out..."
status in the bottom line. This was both easy to miss if you are used to looking
for an inline status in the branches panel, and it didn't provide information
about which branch was being checked out, which can be annoying in very large
repos where checking out takes a while, and you only see at the end if you are
now on the right branch.

Improve this by trying to figure out which branch was the previously checked out
one, and then checking it out normally so that you get an inline status next to
it (as if you had pressed space on it). There are cases where this fails, e.g.
when the previously checked out ref was a detached head, in which case we fall
back to the previous behavior.
2025-10-02 18:28:17 +02:00
Stefan Haller 2b152dcf5e Don't log the git rev-list command that we use for IsBranchMerged
When you delete a branch, we call this function to determine whether we need to
ask for confirmation of not. We don't want to log this, because it's not a
command that a user would normally use as part of this operation.
2025-09-20 11:07:43 +02:00
Stefan Haller c35e3776dd Fix staging when using the new useExternalDiffGitConfig config
This new config was introduced in 0.55, but it didn't work in the staging view
or custom patch building view.
2025-09-17 20:37:14 +02:00
Stefan Haller ee81a8e3c2 Add hash field to models.StashEntry 2025-08-25 19:23:30 +02:00
Stefan Haller 61d5b1646d Cleanup: bring stash loader test up to date
It didn't actually test how it parses the unix time stamps, and the test only
succeeded because the code is lenient against it missing.
2025-08-25 19:23:30 +02:00
Stefan Haller 40e989467f Add a user config for using git's external diff command for paging
This is similar to using lazygit's Git.Paging.ExternalDiffCommand config, except
that the command is configured in git. This can be done either with git's
`diff.external` config, or through .gitattributes, so it gives a bit more
flexibility.
2025-08-20 10:39:55 +02:00
Stefan Haller 9657b4346f Remove the git.paging.useConfig option
Many people don't understand what this means, which is apparent from the amount
of issues that got filed because of this. Let's get rid of it to avoid this
confusion. People will have to configure their pager twice if they want to use
it both on the command line and in lazygit, which I think is not a big deal.
2025-08-19 10:43:28 +02:00
Stefan Haller 89d50fa229 Use external diff command in stashes panel
This was forgotten in 6266e19623.
2025-08-19 09:48:11 +02:00