Tags can only be pushed one at a time. Let the push command of the tags
panel work on a range selection of tags too. It asks for one remote and
pushes all selected tags to it with a single git command.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This lets pushing a range selection of tags push them all with a single
git command.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tags can only be deleted one at a time. If many of them have piled up,
for example backup tags made before rewriting a branch, deleting them
takes a lot of keypresses.
Let the delete menu work on a range selection of tags. It deletes the
selected tags with one git command for the local tags and one for the
remote tags, and asks for a single remote for all of them. After
deleting local tags, collapse the range selection to its first line;
otherwise it would select the tags that moved up into the place of the
deleted ones.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Deleting a remote tag and deleting a local and remote tag each have
their own copy of the code that asks for the remote and for a
confirmation. Deleting a range selection of tags needs to change it in
the same way for both.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This lets deleting a range selection of tags delete them all with a
single git command, the same way it works for branches.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Deleting and pushing a range selection of tags will need to show their
operation on all the selected tags. Move the code from BranchesHelper
into a generic function that takes the items and the key of the context
that shows them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When somebody else rebases a stack and force-pushes it, pulling the
topmost branch leaves the branches below it diverged. They can be
updated with `f`, but pushing the stack offers to push the branches
below along with the current one, and pulling should be symmetric.
When pulling, find the branches stacked below the current one that can
be updated without losing anything. These are those that are behind
their upstream branches, and those that diverged from them only because
they were rewritten. If there are any, show a menu that lists them and
offers to update them in addition to pulling the current branch, or to
pull only the current branch. The current branch is pulled as before.
The decision is based on the last fetch, not on a new one, so that the
menu appears right away. If the remote has changed since then, a branch
might not be offered although it could be updated; it then shows as
diverged after the pull, and pulling again offers it. The update itself
fetches the upstream branches and checks them again, so it never loses
commits.
Branches that are checked out in another worktree are not offered,
since updating them would change the files there.
The branches below are updated before the current branch is pulled. If
one of them pointed into the commits that a rebasing pull rebases, the
pull would move it when rebase.updateRefs is set, and updating it
afterwards would fail. If updating them fails, the current branch isn't
pulled.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pushing a stack and fast-forwarding a range of branches each show their
operation on all the branches involved, with their own copy of the same
code. Pulling a stack will need it too, so move it to BranchesHelper.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FastForwardBranches looks up the worktrees of the branches in the model
on the UI thread, and then does the rest on a worker, with the branches
shown as being fast-forwarded. The next commit needs to update branches
as part of pulling the current one, in the pull's own task.
Move everything except the inline status into PrepareFastForward, which
returns the part to run on a worker.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If a main branch is checked out in another worktree, auto-forwarding
after a fetch skips it. People who keep the main branch checked out in
their main worktree, and work on feature branches in linked worktrees,
therefore never get it forwarded.
Fast-forward such a branch in the worktree it is checked out in, as `f`
does. Skip the worktree if it has changes to tracked files, since
nobody asked for its files to change. Also skip it if it is mid-rebase
or mid-bisect, because its HEAD is then detached from the branch. A
branch in the current worktree is never forwarded. This covers the case
of the current worktree being mid-rebase on a main branch, whose ref
would otherwise be forwarded under the rebase.
Auto-forwarding now writes the same reflog message as `f`
("lazygit: update to upstream branch").
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If `f` is used on several branches that are checked out in other
worktrees, the first worktree in which the fast-forward fails stops the
whole operation. For example, an untracked file that the fast-forward
would overwrite leaves the branches after it untouched, even though
nothing is wrong with them.
Try all the branches, and report the errors of the failed ones
together. The checks that refuse to fast-forward any of the branches
still run before anything is moved.
The next commit uses forwardBranches for auto-forwarding branches in
other worktrees, where stopping at the first failure is worse. A
worktree that fails every time would prevent the ones after it from
ever being forwarded. For this, forwardBranches also takes the git
commands to use, since a background worker doesn't block switching
repos.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AutoForwardBranches runs in the Then callback of the refresh after a
fetch. This is on the UI thread, so the git command for updating the
branches blocks the UI while it runs. This is fine for a single
update-ref, but the next commit adds a git status and a git merge for
each branch that is checked out in another worktree.
Keep reading the branches from the model on the UI thread, and move the
git work to a worker.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The worktree loader associates a worktree that is mid-rebase or
mid-bisect with the branch involved, even though its HEAD is detached.
Fast-forwarding such a branch therefore ran the fast-forward in that
worktree. This moved the detached HEAD and put the upstream commits
into the rebase in progress, while the branch stayed where it was.
Remember in the worktree model when its branch comes from a rebase or
bisect, and refuse to fast-forward the branch in that case. Updating
only the ref is no option for a rebase; the rebase writes the branch
when it finishes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Branches that lazygit forwards to their upstream move by a direct ref
write, so no git command turns up in their reflog to explain it. Until
now the entry had no message either, leaving `git reflog <branch>` with
nothing but a blank line about it. Name lazygit and what it did, so that
a branch which moved on its own can be traced.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
After somebody else rebased and force-pushed a stack of branches, every
branch of the stack has to be brought back to its upstream, and pressing
`f` on them one by one is as tedious as the checking out and pulling it
replaces.
Let `f` work on a range selection. The upstream branches are fetched with
one `git fetch` per remote, and the branches that aren't checked out
anywhere move in a single `git update-ref` call. All the selected
branches are looked at before any of them is moved, so a branch that has
to be refused leaves the others alone rather than updating the stack
halfway.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When somebody else rebases a branch and force-pushes it, the local
branch is left diverged from it, so `f` refuses to touch it. To get back
in sync, the branch has to be checked out and pulled, and for a stack of
branches that means doing it once per branch.
Such a branch has none of its own work in it, though: the commits it is
ahead by are the old versions of the ones that are now on the remote
branch. So when the branch has diverged and none of the commits it is
ahead by is ours, reset it to its upstream instead of refusing. This is
the same state that checking the branch out and pulling it would produce,
as rebase drops commits that the upstream already has.
A branch that isn't checked out anywhere is reset with the same
`git update-ref` that a fast-forward uses. One that is checked out
somewhere is reset with `git reset --keep`, and only when that worktree
has no changes to tracked files, so that the files under the user's feet
change no more than they have to. `--keep` also refuses to overwrite an
untracked file that the reset would bring in, which `--hard` would do
silently.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fast-forwarding a branch that isn't checked out ran a single
`git fetch <remote> refs/heads/<branch>:<branch>`, so git's own refusal
to move a local branch backwards served as the safety check. A later
commit widens the set of branches the command accepts, and for that
lazygit has to make that decision itself.
Fetch the remote branch on its own, updating only its remote-tracking
branch. Then ask git whether the branch is an ancestor of it, and only
then move the branch: with `git update-ref` when it isn't checked out
anywhere, guarded by the hash we knew it to have, and with
`git merge --ff-only` in its worktree when it is checked out there.
Keeping `git pull --ff-only` for the latter case would fetch a second
time.
Fast-forwarding a branch that isn't checked out in a worktree had no
integration test at all, so add one. The second new test covers a repo
that keeps no reflogs, because a later commit starts reading those and
this case has to keep working without them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Most of the logic of this controller belongs in a helper, and the next
commit reshapes it, so move it over unchanged first to keep that diff
about the change itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The function asks git whether HEAD is an ancestor of a ref, and its name
says what the one caller wants to know. A later commit asks the same
question about a branch that isn't checked out, so let the caller name
both refs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When somebody else rebases a branch and force-pushes it, our branch
shows up as diverged, say ↓5↑3, and that looks exactly like a branch
that carries three commits of our own. The two want different treatment.
The first one only has to be moved to its upstream, while the second one
needs a decision about what to do with those commits. The panel doesn't
tell them apart, so users have to look at each branch in turn to find
out which one they are dealing with.
Determine for every diverged branch whether it has commits of its own,
and dim its divergence when it doesn't. This runs on a worker, as it
takes a few git commands per diverged branch, and a branch keeps the
answer from the previous load while a new one is in flight, so that the
display doesn't flicker. A later commit teaches the fast-forward command
to act on such a branch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The flag says whether this load is the one that kicks off the values
determined in the background afterwards. A later commit adds a second of
those next to the behind-counts, so name the flag after what it controls
rather than after its only user so far.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
After rebasing a stack of branches, every branch of the stack has to be
force-pushed, and so far the only way to do that in lazygit was to check
out each branch in turn and push it. For a stack of a dozen branches
that is a lot of work for a routine task.
When the current branch is pushed and there are branches below it in the
stack that have commits to push, show a menu that offers to push them
along with it. The menu lists the branches and how far each of them has
diverged from its remote branch, so that the user can see what is about
to happen. It doesn't show where each branch goes; the branches panel
doesn't show that for a normal push either. Pushing only the current
branch is the second entry. If any of the branches has diverged from its
remote branch, a single confirmation covers force-pushing all of them.
A branch is offered if its tip is a commit of the current branch that
isn't merged yet, it has an upstream that is stored locally, that
upstream is not gone, and it is ahead of its push destination. Branches
without an upstream are left out because a branch that was never pushed
can't be told apart from one that is meant to stay local. A branch that
is only behind is left out because pushing it would move the remote
branch back to an older commit; the lease doesn't catch that when the
remote-tracking branch is up to date.
Each branch is pushed to where `git push` would push it if it were
checked out, using the destination git reports in the %(push) field.
This honors push.default, remote.pushDefault and
branch.<name>.pushRemote without lazygit having to interpret them.
Branches going to the same remote are pushed in one command, with
--force-with-lease when the user confirmed force-pushing; the lease
checks each ref against its remote-tracking branch, so a coworker's
unseen push is still rejected. The current branch joins that command
when its remote-tracking branch is stored locally. Otherwise it needs a
plain push first, for example with --set-upstream, and that option
would apply to every refspec of a combined command; so in that case it
is pushed on its own as before, and the other branches follow in a
second command.
Pushing the current branch on its own still runs a bare `git push`, so
users who rely on push.default or remote.<name>.push for it see no
change. The push is non-atomic, as git defaults to; if one branch's
lease fails, the others still go through and the error names the one to
look at.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pushing the current branch has three cases: it has an upstream, it has
none but push.default is "current", or the user is prompted for one.
Each case ends in a push, and the first one also checks whether a force
push is needed. Let the three cases call a common callback instead, and
put the check and the push there. This makes it possible to run the same
three cases with a different callback, so that the branches stacked
below the current one can be pushed along with it.
The check returns false for a branch without an upstream, so the other
two cases behave as before.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
lazygit will offer to push these along with the current branch, so that
a rebased stack can be pushed in one go. The commits panel already marks
the tips of stacked branches, and this uses the same criterion. A branch
is below the given one if its tip is one of the loaded commits of that
branch that isn't merged into a main branch. This needs no git command.
In whole-graph mode the commit list also contains commits of other
branches, but those are marked as merged, so they don't count.
Not used yet.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PushOpts can only describe a push of the current branch. It takes the
branch's name and the upstream to push it to, and builds a single
refspec from them. Pushing several branches in one command needs one
refspec per branch, so let callers pass the refspecs themselves. The
sync controller, the only caller so far, builds the same single refspec
that PushCmdObj built before.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The text of the selected line is always bold. Some users don't like
this (#2304), but there is no way to turn it off. Setting
selectedLineBgColor doesn't help. Its attributes apply to the text too,
so it can add bold, but it can't take it away.
Add gui.theme.selectedLineFgColor for the text of the selected line, in
focused and unfocused views alike. Its attributes are added to those of
the text, and a color replaces the colors of the text. It is [bold] by
default, so nothing changes unless you set it; 'default' leaves the
text as it is.
Putting bold into the default of selectedLineBgColor instead wouldn't
work well. To turn it off, you would have to replace the whole list, and
lose the color that is computed from the terminal's background.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Some theme colors can't have one default that suits both dark and light
backgrounds. A gray background for the selected line of an inactive
view has to be lighter than the terminal's background if that is dark,
and darker if it is light.
Give such fields a default for each kind of background. Derive it from
the terminal's background color if the terminal tells us, so that it
keeps the same distance from the background however dark or light that
is, and takes on its tint. Otherwise, assume a black or white
background.
These defaults can't be the defaults of gui.theme. We would then have
to tell whether a value there came from the user or from the built-in
defaults, because only the user's value should win over a background
default. Keep them apart from the user config instead, and leave these
fields empty in the defaults of gui.theme, so that a value there always
comes from the user. Tests ensure that no field has both kinds of
defaults, and that both kinds of background set the same fields.
Start with inactiveViewSelectedLineBgColor: the background mixed with
30% white if it is dark (#4d4d4d on black), or with 15% black if it is
light (#d9d9d9 on white). A gray background shows where the selection
is in a view without the focus more clearly than the bold text we used
so far.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
While bisecting, the hashes of the commits outside the bisect range are
black. On a dark gray background this makes them recede, but on a black
one they are invisible, and on a light one they stand out more than any
other hash.
Draw them in faint text instead. The terminal shades faint text toward
its background, whatever that is.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A later commit shows the hashes of commits outside the bisect range in a
dimmer variant of the default text color. A darker shade of a terminal
palette color can't be derived, because we don't know what the palette
is, so leave the shading to the terminal and use its faint attribute,
SGR 2. In gookit/color that attribute is called OpFuzzy.
Terminals that don't implement SGR 2 render the text in its normal color.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A color that reads well on a dark background can be hard to read on a
light one, and the other way round. If you switch your terminal between
dark and light, there is often no single set of theme colors that works
for both (#4366).
Add two overrides of gui.theme, one for each kind of background.
gui.colorScheme, or else what the terminal tells us, decides which one
applies. A field that is set in the override replaces the one in
gui.theme. Author colors and branch color patterns are merged by entry
instead, so that an override doesn't have to repeat the entries it
doesn't change.
When the terminal's background changes, re-apply the theme and
re-render all views, not only the ones that show author colors.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Since gui.colorScheme exists, the name reads as if the function set that
config. That gets more confusing once gui.colorScheme decides which
theme the function applies.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
We are about to add overrides of gui.theme for dark and light
backgrounds. Author and branch colors need them too, because a color
that reads well on a dark background may be hard to read on a light
one. Move them into gui.theme, so that the overrides cover them without
a mechanism of their own.
The migration of gui.branchColors creates gui.branchColorPatterns, so it
now has to run before the moves.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If several patterns in gui.branchColorPatterns match a branch, the color
it gets is picked at random, and it can change from one render to the
next. The patterns are kept in a Go map, and Go randomizes the order in
which a map is iterated.
Keep the patterns in a list instead, in the order in which they are
written, and let the first match win. If a repo's config file has
patterns too, put them in front of those of the global config file,
because they are more specific.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gui.branchColors has been deprecated in favor of gui.branchColorPatterns
since 0.44.0. We are about to move gui.branchColorPatterns into
gui.theme, and the deprecated key would have to move along with it.
Migrate it instead, so that we can remove it.
gui.branchColors matched its keys against the part of a branch name
before the first slash. The pattern ^<key>(/|$), with the key escaped,
matches the same branches.
If gui.branchColorPatterns is set, gui.branchColors has no effect; in
that case the migration removes it. This check is done per file. So if
the global config sets gui.branchColorPatterns and a repo config sets
only gui.branchColors, the repo's colors were ignored so far, and now
they apply.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Renderers like delta and difftastic pick their colors for either a dark
or a light background, and they can't find out which one the terminal
has. Lazygit runs them with TERM=dumb, in a pty that doesn't answer
their queries. So the colors come from the config, and when the
terminal switches between dark and light, the diff keeps the ones it
has.
Add {{colorScheme}} to the commands of diff renderers. It is 'dark' or
'light', going by gui.colorScheme, or by the terminal if that is
'auto'. It can be passed to delta as --{{colorScheme}} and to
difftastic as --background={{colorScheme}}; other renderers can choose
between options with a template expression. When the terminal switches
between dark and light, render the diff again.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The next commit needs to render the diff again when the terminal
switches between dark and light, with the same care not to replace
whatever else the main view might show.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The command of a diff renderer can refer to values like the width it
renders at, as {{width}}. They are filled in by plain replacement, so a
command can't choose between options depending on them. The next commit
adds a value that needs this: whether the terminal is dark or light.
delta takes --dark or --light, but for other renderers the choice has to
be spelled out differently, for example as the name of a syntax theme.
Resolve the command as a Go template instead. The values become its
variables, so that {{if gt .width 160}} --side-by-side{{end}} works too.
To keep the existing commands working, a variable can still be written
without the leading dot.
A mistake in a template, such as a misspelled variable, now makes
resolving the command fail, instead of leaving the placeholder in it.
Check the commands when the config is loaded, by resolving each of them
with made-up values, so that the mistake shows up as an invalid config.
This also rejects a variable that the kind of renderer doesn't have,
such as {{columnWidth}} in the command of an external diff; until now,
it reached the renderer as it was.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The getters for the stdin filter and the external diff command each
take the values they fill in as parameters of their own, and each
builds the placeholders for them. The next commit checks the commands
when the config is loaded, and for that it needs to resolve a command
whatever its kind. A value that both kinds can use comes after that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The colors that lazygit derives from the names of authors are picked
for a dark background. On a light one, they are too pale to read;
against white, every author has a contrast ratio between 2.2:1 and
3.5:1.
Now that lazygit knows whether the terminal is dark or light, pick them
from a darker range of lightness when it is light. Against white, the
contrast ratio is now between 5.2:1 and 9.2:1, and against the
background of Solarized Light between 4.8:1 and 8.5:1. On a dark
background, nothing changes. If the terminal switches between dark and
light while lazygit is running, draw the commits again in the new
colors.
Add gui.colorScheme for terminals that don't tell us, and for anyone
who wants to override what they tell. It is 'auto' by default; 'dark'
and 'light' ignore what the terminal says.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If gui.authorColors names no color for an author, lazygit picks one
from a hash of their name, at an HSL lightness between 0.4 and 0.6.
HSL lightness says nothing about how bright a color looks, though. At
0.5 a yellow is glaring and a blue is nearly black, so whether an
author's initials can be read comes down to where their name happens
to hash to. Against a background of #1e1e1e, 45% of four thousand
names fall below a contrast ratio of 4.5:1 and 21% below 3:1, with the
worst at 1.46:1. Several issues have been raised about author colors
being too dark to read.
Use HSLuv instead. Its lightness tracks perceived brightness, so the
range can be narrow now that a number in it means something. Against
#1e1e1e, every author now lands between 4.7:1 and 7.7:1, so no name is
unlucky any more.
This changes the color of every author who isn't named in
gui.authorColors. On a light background, the new colors are uniformly
too pale, where before they were mostly too pale. The next commit gives
a light background a range of its own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If gui.authorColors names no color for an author, lazygit derives one
from a hash of their name. Checking whether these colors are readable
means looking at a repository, but the authors of a real one rarely
land near the edges of the range, so the worst cases go unseen.
Add cmd/author_colors_repo. It searches for names at the lowest and
highest lightness and saturation, at twelve hues, and commits once as
each of them. The names depend only on the hashing, so the same
repository shows the colors of any lazygit build and can be kept
around to compare them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The next commit adds a tool that creates a repository of authors at
the edges of the range their colors are picked from. To find such
names, it needs to know where a name lands in the range, and it
shouldn't keep a copy of the hashing that could drift from this one.
Later commits test the colors, and need the colors themselves for that
rather than the styles made from them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the config is reloaded, SetCustomAuthors replaces the styles of
authors, but the initials and names that were rendered with the old
styles stay cached. So do the pipes of the commit graph; each of them
carries the style of the author of the commit it starts at.
Drop these whenever the colors of authors change. The graph's cache
finds out by itself, by comparing a version number, so that nothing
has to remember to reset it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If gui.authorColors changes while lazygit is running, the authors that
are already on screen keep their old colors, both in the author column
and in the commit graph.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The styles from gui.authorColors and the ones derived from the names
of the other authors share a map, so the derived ones can't be dropped
without the configured ones. A later commit needs to drop the derived
ones on their own, when the terminal switches between dark and light.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lazygit doesn't know whether the terminal it runs in has a dark or a
light background. The colors of authors, for example, have to suit one
or the other, and are too pale to read on a light background.
Ask the terminal. Many terminals answer CSI ? 996 n with whether they
are dark or light, and send the same report again whenever that changes
while mode 2031 is on. Terminals differ in what they base this on,
though. kitty goes by its background color, but Ghostty goes by the dark
or light mode of the operating system, even with a theme that doesn't
follow it. So also ask for the background color with OSC 11. If the
terminal answers that, the background decides, and a report only makes
us ask for the background again. Some terminals answer OSC 11 but send
no reports; ask these for the background again whenever the terminal
gains focus.
Turn mode 2031 off whenever lazygit hands the terminal to another
program, so that the program doesn't receive the reports as typed text.
For the same reason, first wait for the answers to any queries that are
still outstanding, but for no longer than half a second. Locally, the
answers take a few milliseconds at most, but over ssh they take a
network round trip, and focusing the terminal and then pressing a key
that starts an editor fits into that.
Leave out the terminals that tcell doesn't send its own queries to,
except for Terminal.app and WezTerm; these are only asked for the
background color.
This doesn't make startup any slower. tcell sends its own queries in
Screen.Init and waits for the terminal to answer them. Ours go out just
before, so their answers arrive before tcell stops waiting, and the
color scheme is known before the first layout.
tcell has no support for any of this, and drops the answers when it
parses its input. Instead of adding support to tcell, wrap the tty that
tcell reads from and watch the input as it goes by.
For now, lazygit only writes the color scheme to the debug log.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Renders were kept from refreshing git's index because a pty-rendered
command on Windows is terminated at an arbitrary instruction when its
task stops, and one landing in the window where git holds index.lock to
write back refreshed stat information leaves that lock behind.
A render no longer runs in a pty there, and nothing kills it any more
either: the pipe to the renderer breaks, git's write fails, and git dies
through its own die path with its lock files cleaned up. So let the
refresh happen, and let renders heal stale stat info the way they do
everywhere else.
The teardown of a pseudoconsole took its reassurance about index.lock
from this, and renders were the reason it held. What still runs in a pty
there is a custom command asking for logWithPty and a command that may
be asked for a credential, so the claim no longer follows; drop it
rather than restate it for clients it was never about.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ConPTY doesn't carry a diff renderer's output to us as the renderer
wrote it. It parses the output into a screen buffer and re-encodes that
for the terminal side, and a sequence it can't represent there goes out
the moment it is parsed, separately from the text around it. The OSC
1717 records a renderer states its diff lines in therefore arrive
detached from the rows they describe, and the identity layer attributes
rows to the wrong diff line or to none.
Feed the renderer through a pipe there instead, so that its bytes reach
us unaltered. A stdin filter becomes a command of our own, since git
only invokes the one named by GIT_PAGER when it talks to a terminal; an
external diff renderer is git's own business either way and needs
nothing but the pipe.
Unix keeps the pty. A renderer reads the width to lay out to off it, so
taking it away would leave every configuration that doesn't name a
width rendering at whatever the renderer falls back to, and diff
renderers have worked on Unix far too long for that.
LAZYGIT_RENDER_WITHOUT_PTY asks for the piped path anyway, which is how
the integration tests cover it on a platform where they run at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LAZYGIT_COLUMNS told a diff renderer script the width of the view at a
time when a render on Windows ran without a pty, so that the script had
nowhere else to read it from. It was documented for that case only, and
the documentation went when Windows gained a pty; it has not been
mentioned since.
COLUMNS now tells every command rendering into a view the same thing,
in the variable git and the common renderers already read, and the
{{width}} template variable covers a renderer that reads neither. The
value LAZYGIT_COLUMNS carried was also the width before the layout
pass, which is not always the width the view ends up with.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tell a command that renders into a view how wide that view is, through
COLUMNS. git reads it in preference to the size of the terminal it is
talking to, so the diffstat now fills the view whether or not the render
has a terminal to offer.
A diff renderer that can't ask a terminal gets the width from it too;
difftastic, diff-so-fancy and delta all support COLUMNS, so we can stop
running git in a PTY and these will still work.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>