539 Commits
Author SHA1 Message Date
Stefan Haller 40cb4bb24d Extract a single helper for the paths a node's diff is limited to
The files and commit files panels each had their own copy of this, one of
which used to be missing the previous path of a rename. Growing them
apart again is the last thing we want, since the next commit needs to
teach both of them about renames that cross a directory boundary.

The files panel version only returned paths for the filtered case, and
left it to WorktreeFileDiffCmdObj to derive the rest from the node; now
that all callers pass the paths in, that command doesn't need to know
about renames at all.
2026-08-17 09:32:30 +02:00
Stefan HallerandClaude Opus 5 80ad2db71a Recognize worktrees among the files from the worktrees model
Finding out which of the files are worktrees of ours had its own answer
to where this repo's worktrees are, walking the directory that git keeps
them in. The worktrees panel asks git itself, and that is the better
answer: it is the one git gives for the same question elsewhere in the
app, and it doesn't need to know where git records what.

The model that panel fills is all the files need, so mark them from it.
That takes the work out of the file loader, whose other two callers were
paying for it without wanting it, and it costs no git call at all: both
models are written on the UI thread, so whichever of the two refreshes
lands second marks the files against the other's fresh data.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 11:26:58 +02:00
Stefan HallerandClaude Opus 5 c8bc1928f2 Get the GitHub token from gh instead of resolving it in-process
go-gh reads gh's config file once per process and answers from that
snapshot for the rest of the process's life. gh rewrites the file
whenever the active account changes, and stores the active account's
token either in it or in the system keyring, depending on the account.
A lazygit that has been running for a while therefore consults a
snapshot that no longer describes reality: it either keeps using a
token for an account that is no longer active, or, when the snapshot
was taken while a keyring-backed account was active, finds no token at
all and silently stops showing pull requests until it is restarted.

Asking gh resolves the token afresh on every refresh, from whichever of
the environment, the keyring or the config file currently holds it.
go-gh's lookup stays behind as a fallback for setups without the gh
binary, where it still picks up GH_TOKEN and friends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 19:13:06 +02:00
Stefan Haller 5481436d8c Honor the conflict-marker-size gitattribute
Ask git for the attribute of every conflicted file whenever we load the
file status, so that we recognize the markers it actually wrote. Files
that are set up this way are precisely the ones whose regular content
tends to contain marker-looking lines, so matching a run of at least
seven characters instead is not an option: we'd take the file's own
content for markers and then never consider its conflicts resolved.

One `git check-attr` call covers all conflicted files at once; asking per
file would take seconds when hundreds of files are conflicted, and it
would hurt worst on Windows, where spawning a process is expensive.
Because the lookup rides along with the file status, it costs nothing
when there are no conflicts, and editing .gitattributes during a merge
takes effect on the next refresh.
2026-08-08 12:43:42 +02:00
Stefan HallerandClaude Opus 5 f141fcc570 Open the repo when lazygit is started in its .git dir
Running lazygit in a .git dir got you told you were in a bare repo,
which you weren't: the worktree was sitting right there, one directory
up. git's own convention is that a git dir called .git belongs to the
directory holding it — that's how `git worktree list` names the main
worktree — so ask that directory, and if it is a worktree, open the repo
we were really being asked about.

The git dirs that aren't called .git keep the answer they had. A linked
worktree's and a submodule's do have a worktree, but nothing we look at
says where, so we would be guessing; a bare repo's has none to find.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan HallerandClaude Opus 5 d19af37ee7 Tell git where the repo is when it can't find it itself
git finds a repo by looking for a .git in the directory a command runs
in. Lazygit runs its commands in the work tree, so that normally works —
but not when the git dir lives somewhere else entirely, which is what
core.worktree and --work-tree are for. Lazygit chdir'd into such a work
tree and then ran commands that couldn't see any repo from there, so
opening a repo with core.worktree set panicked on startup. It only
worked with --git-dir because that leaves GIT_DIR in the environment for
every command to inherit.

Work out at startup whether git can find the repo from its work tree,
and when it can't, put GIT_DIR and GIT_WORK_TREE on every command the
repo's builder produces. As with the working directory the builder pins
(527124d0e0), these also go into the process env — subprocesses don't
come through the builder — but the commands don't read them from there,
because the process env belongs to whichever repo we have switched to
since.

Working out whether git can find the repo means asking git, rather than
reading the .git file, whose contents can spell the same directory
differently than git does. The extra query is skipped for a repo whose
git dir is simply its .git directory, which is nearly all of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan HallerandClaude Opus 5 34d41b5d51 Don't let our repo answer for a different one
GIT_DIR and GIT_WORK_TREE tell git where our repo is, and every command
we run inherits them — including the ones we point at a submodule or
another worktree. git resolves those against our repo instead, and says
nothing about it: with GIT_DIR set, `git -C mysub log -1` reports the
superproject's commit. So opening lazygit with --git-dir/--work-tree
quietly broke resolving submodule conflicts, stashing and resetting a
submodule, and detaching another worktree; the worktree list came back
claiming every worktree shared our git dir.

Drop the two variables from the commands that address another repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan HallerandClaude Opus 5 616d75a1fa Run Reset in the parent module the way the other commands do
Reset told git to change directory with -C while runInParentModule does
it by setting the command's working directory, but they were computing
the same directory for the same reason. Use the helper, so that there is
one place that knows what running in a nested submodule's parent means.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan HallerandClaude Opus 5 3d80e466ce Say why runInParentModule can name a relative directory
Its working directory resolves against the process rather than against
the repo the command builder pins commands to, which is only safe
because nothing but foreground commands come through here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan HallerandClaude Opus 5 0ce248d1bf Recognize a repo that has no work tree instead of bailing out
git makes `rev-parse --show-toplevel` fatal when there's no work tree,
so asking for it together with everything else meant we never got an
answer at all for a bare repo: GetRepoPaths returned an error, nobody
ever saw IsBareRepo() == true, and lazygit either died with a stack
trace or decided we weren't in a repository. That's what you got for
opening it in a directory holding a bare repo and a .git file pointing
at it, which is a normal way to keep a repo and its worktrees together.

Ask again without --show-toplevel when the first query fails: the other
queries work fine without a work tree, so if they now succeed we know
we're in a bare repo, and the existing prompt offering to open a recent
repo does its job. If they fail too we're not in a repo at all, and the
first error already says so.

--is-bare-repository is gone from the query: a work tree implies
core.bare is false, so it could only ever come back false there, and
what matters to us is whether there is a work tree to show, which is
what we now go by.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan HallerandClaude Opus 5 e17ed2484c Use the work tree as the repo path when it is the main worktree
RepoPath() is meant to be the same as WorktreePath() when we're in the
main worktree, but we derived it from the git dir's location instead.
That is only the same thing when the git dir lives inside the work tree.
With core.worktree, --work-tree, or a .git file pointing at a repo dir
that isn't called .git, it lands on a directory that isn't a worktree at
all, and the repo name we show follows it there.

A worktree that has the repo's common git dir to itself is the main
worktree, so use its path. That subsumes the submodule case, whose git
dir lives under the superproject's .git/modules but is still the
submodule's own common dir; --show-superproject-working-tree is now only
needed for a linked worktree of a submodule.

The existing bare repo test asserted a git output that can't occur (a
work tree and --is-bare-repository=true at once), but the rest of it is
the shape of a repo opened with --git-dir/--work-tree, where the new
repo path is the correct one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan HallerandClaude Opus 5 180039e78c Add a repo paths test for a repo with a separate work tree
When the work tree lives somewhere else entirely — set up with
core.worktree or --work-tree — we're still in the main worktree, so
RepoPath() should be the work tree, as its own doc comment says. Instead
we derive it from the git dir's location, which lands somewhere that
isn't a worktree at all, and the repo name follows it.

The ACTUAL lines are indented as they will be once the EXPECTED ones
replace them, rather than as gofumpt wants them while the comment
markers are still splitting the struct's alignment. That leaves this one
file not gofumpt-clean until the next commit, in exchange for a diff
there that shows only the lines that actually change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan HallerandClaude Opus 5 d2d5bdc2bc Identify the current and main worktree by git dir, not by path
`git worktree list` reports the main worktree as the common git dir with
a trailing "/.git" removed, which equals the working tree only when the
git dir sits inside it. In a submodule, a bare repo, or a repo using
core.worktree it doesn't, so comparing the reported path against the
working tree path matches nothing: no worktree is recognized as current
or as main. Most visibly, inside a submodule lazygit claimed we were in
a linked worktree named after the submodule, and offered to remove that
"worktree".

Comparing git dirs identifies a worktree unambiguously, so use that.
A worktree whose directory is gone has no git dir to compare, and there
we still have nothing better than its path.

The submodule tests were asserting the linked-worktree suffix in the
status view; it is gone now, and the repo name still says which
submodule we're in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan HallerandClaude Opus 5 e1b8ef048a Add a worktree loader test for being in a submodule
A submodule's git dir doesn't live inside its working tree, and `git
worktree list` reports it by its git dir. Lazygit compares that against
the working tree path, so it recognizes neither the current nor the main
worktree, and the UI ends up claiming we're in a linked worktree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan HallerandClaude Opus 5 7cbd93f945 Give the worktree loader tests their repos' git dirs
The scenarios describe their repo by its paths but leave the git dirs
empty, which no repo has. Unused for now; the loader is about to want
them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller ff26f61ffd Carry aggregate check state with GitHub pull requests
GitHub exposes a combined status for the head commit without requiring
individual check contexts. Include that rollup in the existing request
and startup cache so every consumer sees the same state without making a
second network request.
2026-07-31 09:01:44 +02:00
Stefan Haller 67ec23e08a Isolate GitHub pull-request response parsing
The fetch currently combines transport, JSON decoding, and model
conversion, which makes response changes difficult to verify without
exercising the network. Put the deterministic work behind a small parser
so later payload changes can be covered with raw GraphQL fixtures.
2026-07-31 09:01:44 +02:00
Stefan Haller 8731d8a51b Rework the custom pager config (rename to diff renderer)
For a long time lazygit has used the term "custom pager" to refer to
what's really a "diff renderer". A pager is a program that allows you to
view output page by page (hence the name), e.g. less; lazygit's custom
diff renderers are not pagers. It used the term only because the feature
is implemented using git's GIT_PAGER env var, but that's an
implementation detail.

Rename the 'git.pagers' config to 'git.diffRenderers', and restructure
its elements while we're at it to make things clearer:

- Add a 'type' field to explicitly specify which type of diff renderer
  it is (the two fundamentally different ones are 'stdinFilter' and
  'extDiff').
- Add a third type, 'rawGit', which has an 'args' field that makes it
  easy to use 'git --color-words' as a custom renderer
- Unify the old 'pager' and 'externalDiffCommand' fields to a single
  'command' field for both types

Existing config files are migrated automatically.
2026-07-31 08:42:51 +02:00
Stefan Haller 2694aa774a Extract GitCommandBuilder method AddCommonDiffArgs
We were setting the same arguments in five places; extract this to one.
2026-07-31 08:42:51 +02:00
Stefan Haller e756511042 Move dragged commits in one rebase
Let the todo-move primitives take a distance instead of hardcoding a
single row, by iterating the one-row move in memory. Dropping a commit
several rows away thus rewrites the todo file once and, outside of an
interactive rebase, runs a single rebase rather than one per row.
2026-07-31 08:37:28 +02:00
Stefan Haller f15f713c68 Support absolute paths when detecting edit preset from EDITOR env var 2026-07-30 17:31:56 +02:00
Stefan Haller 87d9537de9 Support a {{diffContext}} template variable in external diff command
Useful for passing the context size to external diff commands like
difftastic.
2026-07-23 17:13:02 +02:00
Stefan HallerandClaude Fable 5 e0b8dbf48c Resolve the refresh's file reads against its repo root
The refresh workers read a few files at paths relative to the process
working directory: the submodule config read of .gitmodules, the files
refresh's check for conflict markers, and the submodule stash's
existence check. Git commands are pinned to the repo their instance was
created for, but these Go file reads still followed the cwd, so a
background refresh crossing a repo switch would read the new repo's
files while computing data for the old one. Join them with the worktree
root of the instance they belong to. (Most git-state file reads —
working tree state, rebase todos, bisect info — already resolve
against RepoPaths and need no change.)

This also fixes the submodule stash's existence check for nested
submodules: it stat'ed submodule.Path, which is relative to the parent
module, against the repo root — now it uses the submodule's full path,
matching the stash command right below it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan HallerandClaude Fable 5 132f656480 Run nested-submodule commands in the parent module's directory explicitly
Deleting a nested submodule (and updating its URL) chdir'd the whole
process into the parent module, ran its git commands there, and chdir'd
back. Only those commands need to run there, and a process-wide chdir
leaks the parent module's directory into any command another goroutine
spawns during that window (e.g. a background refresh's). Set the
directory on the commands themselves instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller 197916aafb Suppress command logs for git calls related to the ctrl+f command 2026-07-19 19:22:48 +02:00
Stefan Haller 33a2dc302e Suppress command logs for tag commands
The command log is supposed to show only commands initiated by the user;
these are commands that we run to get information for rendering, so they
pollute the log and are confusing.
2026-07-19 19:19:47 +02:00
Stefan HallerandClaude Fable 5 7360a8459d Give the GitHub GraphQL requests a timeout
The http.Client used for fetching pull requests had no timeout, so on a
network that silently drops packets a request could stay in flight
until the OS-level TCP timeouts kick in, which can take many minutes.
The fetch has no visible status, so nothing tells the user it is still
running; bounding it keeps the refresh's worst case short, and the next
refresh simply tries again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 13:51:38 +02:00
Stefan HallerandClaude Opus 4.8 f84ada4941 Show renamed files in the custom patch builder
When loading the files of a commit we passed --no-renames, so a rename
showed up as a separate delete and add rather than a single R entry.
That made it impossible to work with a rename that also modifies the
file: the modifications were spread across a full deletion and a full
addition instead of appearing as the handful of lines that actually
changed. The staging view already shows renames and lets you stage
their hunks, so there was no good reason for the patch builder to
differ; the flag was only there because the commit-file parser couldn't
cope with the rename record format.

Switch the commit-file loader and the per-file diff to --find-renames,
teach the parser about the rename record (a status followed by two
paths), and carry the previous path through the patch builder so the
diff for a rename is loaded with both paths, which is what makes git
emit the rename in the first place.

A whole-file selection keeps the rename in the header, so the rename
moves or is discarded together with the file's contents. A partial
selection instead strips the rename metadata and points the header at
the new path, so applying the patch only changes the contents and
leaves the rename in place; the blob index line is kept so that a 3-way
apply can still fall back to a blob merge.

Discarding a renamed file from a commit now discards both the new and
the old path, so the new file is removed and the old one is restored.

Changing the rename similarity threshold refreshes the commit files
panel too, not just the files panel, so that a rename can turn into a
delete and add or back. It is disabled while building a patch, however,
because the patch builder caches each file's diff by path and would
desync if a rename changed into a delete and add underneath it.

Finally, copying a file's diff from the commit files panel now passes
both paths for a rename, so the copied diff shows the rename instead of
a new-file add.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 13:05:09 +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 HallerandClaude Opus 4.8 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 HallerandClaude Opus 4.8 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 HallerandClaude Opus 4.8 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 HallerandClaude Opus 4.8 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 HallerandClaude Opus 4.8 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
Henry MaddocksandStefan Haller 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 MaddocksandStefan Haller 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 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 HallerandClaude Opus 4.7 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 HallerandClaude Opus 4.7 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
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 SimardandStefan Haller 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
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 DuffieldandStefan Haller 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
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