... and import stefanhaller's tcell fork for real rather than just replacing it
This solves the problem that people trying to
"go install github.com/jesseduffield/lazygit@latest" would get the error
go: github.com/jesseduffield/lazygit@latest (in github.com/jesseduffield/lazygit@v0.40.0):
The go.mod file for the module providing named packages contains one or
more replace directives. It must not contain directives that would cause
it to be interpreted differently than if it were the main module.
The proper fix is to actually have these two functions share code,
or for views to be able to manage their own heights based on their contents.
But I want to get this out for the sake of a Lazygit Anniversary release.
For all videos but the first video in the readme we want to use mp4 because it's faster, better quality,
smaller, and allows you to play/pause (don't quote me on the smaller part).
HOWEVER: github won't let us reference mp4s stored in our repo from the readme, like it does for gifs
(who knows why). This is annoying because it prevents us from easily re-recording things if the UI
changes. So I've got the logic for recording to mp4 but I'm thinking of sticking to gifs for now
Now that we refresh upon focus, we can scrap this file watching code.
Stefan says few git UIs use file watching, and I understand why: the
reason this code was problematic in the first place is that watching
files is expensive and if you have too many open file handles that
can cause problems.
Importantly: this code that's being removed was _already_ dead.
When opening lazygit with `lazygit log` the worktrees view was appearing in front of the files view.
This is because it had higher precedence than the files view in the ordered view mapping, and
that was because it originally was in the branches window so it was further down the list.
The reason this didn't cause issues on typical startup is that the files context is activated at the
start so it is brought to the front.
I've been thinking about this for a while: I think it looks really cool if nuking your working tree
actually results in a nuke animation.
So I've added an opt-out config for it
This allows to do the equivalent of "git rebase --onto <target> <base>", by
first marking the <base> commit with the new command, and then selecting the
target branch and invoking the usual rebase command there.
We are in the outsideFilterModeBindings section here; all of these handlers are
wrapped in a OutsideFilterMode guard in a loop below. No need to add one
manually here.
It's tricky to get this right for reflog commits wrt what's the current branch
for each one; so just disable it entirely here, it's probably not something
anybody needs here.
We want to mark all local branch heads with a "*" in the local commits panel, to
make it easier to see how branches are stacked onto each other. In order to not
confuse users with "*" markers that they don't understand, do this only for the
case where users actually use stacked branches; those users are likely not going
to be confused by the display. This means we want to filter out a few branch
heads that shouldn't get the marker: the current branch, any main branch, and
any old branch that has been merged to master already.
The model will be used for logic, so the full hash is needed there; a shortened
hash of 8 characters might be too short to be unique in very large repos. If
some view wants to display a shortened hash, it should truncate it at
presentation time.
- check out a non-main branch before we start
- add authors to expected commits so that we can see whether the commits show an
asterisk
- explicitly check what the top line displays after bisecting has started
This shows that the detached head shows an asterisk, which we don't want. We'll
fix that in the next commit.
This test not only tests the correct handling and display of the updateRef
command, but also the visualization of branch heads in the commits panel. Since
we are about to change the behavior here, extend the test so that a master
commit is added (we don't want this to be visualized as a branch head), and then
a stack of two non-main branches. At the end of this branch we only want to
visualize the head commit of the first.
Update-ref commands have an empty sha, and strings.HasPrefix returns true when
called with an empty second argument, so whenever an update-ref command is
present in a rebase, all commits from there on down were drawn with a green sha.
From the go 1.19 release notes:
Command and LookPath no longer allow results from a PATH search to be found relative to the current directory. This removes a common source of security problems but may also break existing programs that depend on using, say, exec.Command("prog") to run a binary named prog (or, on Windows, prog.exe) in the current directory. See the os/exec package documentation for information about how best to update such programs.
We've been sometimes using lo and sometimes using my slices package, and we need to pick one
for consistency. Lo is more extensive and better maintained so we're going with that.
My slices package was a superset of go's own slices package so in some places I've just used
the official one (the methods were just wrappers anyway).
I've also moved the remaining methods into the utils package.
In the presentation layer, when showing branches, we'll show worktrees against branches if they're
associated. But there was a race condition: if the worktree model was refreshed after the branches model,
it wouldn't be used in the presentation layer when it came time to render the branches.
A better solution would be to have some way of signalling that a particular context needs to be refreshed
and after all the models are done being refreshed, we then refresh the contexts. This will prevent
double-renders
Afero is a package that lets you mock out a filesystem with an in-memory filesystem.
It allows us to easily create the files required for a given test without worrying about
a cleanup step or different tests tripping on eachother when run in parallel.
Later on I'll standardise on using afero over the vanilla os package
When switching worktrees (which we can now do via the branch view) we re-layout the windows and their views.
We had the worktree view ahead of the file view based on the Flatten() method in context.go, because it used
to be associated with the branches panel.
We want to be using forward slashes everywhere internally, so if we get a path from windows
we should immediately convert it to use forward slashes.
I'm leaving out the recent repos list because that would require a migration
There are quite a few paths you might want to get e.g. the repo's path, the worktree's path,
the repo's git dir path, the worktree's git dir path. I want these all obtained once and
then used when needed rather than having to have IO whenever we need them. This is not so
much about reducing time spent on IO as it is about not having to care about errors every time
we want a path.
This fixes pkg/integration/tests/worktree/rebase.go which was failing on old git versions due to a difference in
order of branches that don't have recency values
We now always re-use the state of the repo if we're returning to it, and we always reset the windows to their default tabs.
We reset to default tabs because it's easy to implement. If people want to:
* have tab states be retained when switching
* have tab states specific to the current repo retained when switching back
Then we'll need to revisit this
Older versions of git don't support the -z flag in `git worktree list`.
So we're using newlines.
Also, we're not raising an error upon error because that triggers another refresh,
which gets us into an infinite loop
For marking as good or bad, the current commit is pretty much always the one you
want to mark, not the selected. It's different for skipping; sometimes you know
already that a certain commit doesn't compile, for example, so you might
navigate there and mark it as skipped. So in the case that the current commit is
not the selected one, we now offer two separate menu entries for skipping, one
for the current commit and one for the selected.
This can be useful if you want to find the commit that fixed a bug (you'd use
"broken/fixed" instead of "good/bad" in this case), or if you want to find the
commit that brought a big performance improvement (use "slow/fast"). It's pretty
mind-bending to have to use "good/bad" in these cases, and swap their meanings
in your head.
Thankfully, lazygit already had support for using custom terms during the bisect
(for the case that a bisect was started on the command-line, I suppose), so all
that's needed is adding a way to specify them in lazygit.
Now that we run code concurrently in our loaders, we need to handle that in our tests.
We could enforce a deterministic ordering by mocking waitgroup or something like that,
but I think it's fine to let our tests handle some randomness given that prod itself
will have that randomness.
I've removed the patch test file because it was clunky, not providing much value, and
it would have been hard to refactor to the new pattern
Previously our synchronous refreshes took far longer because nothing
was happening concurrently. We now run refresh functions concurrently
and use a wait group to ensure they're all done before returning
We're:
* using concurrency with wait groups
* avoiding regex
* processing lines of input as they come rather than storing everything in one string
* avoiding an inner loop by creating a mapping of remote names to branches
The speedup is most noticeable on first load, when we haven't yet fetched out main branches.
I saw a speedup from 105ms to 60ms. On subsequent loads the gain is more modest;
54ms to 40ms
Notably, the reflog view is taking ages here because it's got a
few thousand lines to write to the view.
In future we should only populate the view's viewport.
This reverts commit 90613056ce, or the part that removed
a goroutine at least.
Reverting because this has caused an infinite wait for push/pull on windows.
We'll need to find out why that happens separately
On german/french/spanish keyboards, typing [ requires modifier
keys like AltGr, so the `mod==0` condition is wrong.
Fixes#2573
ch != 0 is useless because IsPrint is implemented this way:
if uint32(r) <= MaxLatin1 {
return properties[uint8(r)]&128 != 0
}
with properties[0] set to 1 (so, bit 7 not set)
-> 0 is not printable.
Previously we used a single-line prompt for a tag annotation. Now we're using the commit message
prompt.
I've had to update other uses of that prompt to allow the summary and description labels to
be passed in
Previously, we would only show the authors based on local commits, but sometimes you want to set a commit author
to that of a commit on another branch. Now, so long as you've viewed the branch's commits, the author will appear
as a suggestion.
Previously we applied a right-align on the first column of _all_ menus, even though we really
only intended for it to be on the first column of the keybindings menu (that you get from pressing
'?')
After going and adding labels for all of these I found out that 'improvement' should be 'enhancement' and 'bugfix' should be 'bug'
but I don't know how to bulk update them (and I can't rename because the desired labels already exist).
I'll work that out later, this is good enough for now
The true issue was that we were focusing the line in the view before it gets resized in the layout function.
This meant if the view was squashed in accordion mode, the view wouldn't know how to set the cursor/origin to
focus the line.
Now we've got a queue of 'after layout' functions i.e. functions to call at the end of the layout function,
right before views are drawn.
The only caveat is that we can't have an infinite buffer so we're arbitrarily capping it at 1000 and dropping
functions if we exceed that limit. But that really should never happen.
This fixes the issue in accordion mode where the current line wasn't in the viewport upon focus.
It doesn't perfectly fix it: the current line always appears at the top of the view. But it's good enough
to cut a new release. The proper fix is to only focus the line after the view has had its height adjusted.
This happens consistently for my when I close my MacBook's lid. It seems that
MacOS locks the user's keychain in this case, and since I have my keychain
provide the pass phrases for my ssh keys, fetching fails because it tries to
prompt me for a pass phrase.
This all worked correctly already, we have the FailOnCredentialRequest()
mechanism specifically for this situation, so all is great. The only problem was
that it was trying to pause the ongoing task while prompting the user for input;
but the task is nil for a background fetch (and should be).
It said "Press tab to toggle focus", which is wrong for people who remapped
their togglePanel key binding to something else. Print the actual key binding
instead.
As discussed in https://github.com/jesseduffield/lazygit/pull/2599, it
makes more sense to have the user specify whether they want verbose
commits from their own git config, rather than lazygit config.
This means that we can remove all the code (including test coverage)
associated with the custom verbose flag, and lazygit will just inherit
the .gitconfig settings automatically.
---
Tested visually locally, as well as running the tests that all pass.
As discussed in https://github.com/jesseduffield/lazygit/pull/2599, it
makes more sense to have the user specify whether they want verbose
commits from their own git config, rather than lazygit config.
This means that we can remove all the code (including test coverage)
associated with the custom verbose flag, and lazygit will just inherit
the .gitconfig settings automatically.
We have a use-case to rebind 'm' to the merge action in the branches panel. There's three ways to handle this:
1) For all global keybindings, define a per-panel key that invokes it
2) Give a name to all controller actions and allow them to be invoked in custom commands
3) Allow checking for merge conflicts after running a custom command so that users can add their own 'git merge' custom command
that matches the in-built action
Option 1 is hairy, Option 2 though good for users introduces new backwards compatibility issues that I don't want to do
right now, and option 3 is trivially easy to implement so that's what I'm doing.
I've put this under an 'after' key so that we can add more things later. I'm imagining other things like being able to
move the cursor to a newly added item etc.
I considered always running this hook by default but I'd rather not: it's matching on the output text and I'd rather something
like that be explicitly opted-into to avoid cases where we erroneously believe that there are conflicts.
Run integration tests with various different git versions, more or less
randomly picked from our range of supported versions. Based on
@Ryooooooga's work in #2459, but also restructured a bit.
All tests pass for all git versions, but only after cherry-picking
#2747.
I decided to go with @Ryooooooga's approach and do it without using
docker. I also didn't use docker locally; to reproduce the various
failures that I had to fix, I simply installed the respective git
versions locally and used something like
`PATH=~/git-versions/2.25.1/bin:$PATH
./scripts/run_integration_tests.sh`.
It seems that older git versions would drop empty commits when rebasing. Since
this aspect is not relevant to what we're testing here, fix this by simply
avoiding empty commits in these tests.
The test apply_in_reverse_with_conflict.go fails in git versions 2.30.8 and
earlier. Apparently the output "Applied patch to 'file2' cleanly" was only added
more recently. It's not essential that we check this output.
Save has been deprecated for a while, push is the recommended way to save a
stash. Push has been available since 2.13, so we can use it without problems.
Older versions of git don't support the -b option yet. However, no version of
git complains about the -c option, even when the init.defaultBranch config is
not supported.
For older git versions we won't be able to support any other main branch than
"master", so hard-code that in Init.
This doesn't fix anything for older versions yet; see the next commit for that.
Now that we are running each test 6 times on CI, the risk of flakiness
is higher. I want to fix these tests for good but it'l take time, so
we're just retrying for now
The code in getHydratedRebasingCommits relied on the assumption that the
git-rebase-todo file contains full SHAs. This has only been true from 2.25.2 on,
before that it would contain abbreviated SHAs. Fix this by storing fullCommits
in a slice instead of a map, and using a linear search.
I don't know why we're getting index.lock errors but they're impossile to stop
anyway given that other processes can be calling git commands. So we're retrying
a few times before re-raising. To do this we need to clone the command and the current
implementation for that is best-effort.
I do worry about the maintainability of that but we'll see how it goes.
Also, I thought you'd need to clone the task (if it exists) but now I think not;
as long as you don't call done twice on it you should be fine, and you shouldn't
be done'ing a task as part of running a command: that should happen higher up.
It's not clear what was happening but it seemed like we sometimes weren't
fully writing to our stdout buffer (which is used for the error message)
even though we had returned from cmd.Wait().
Not sure what the cause was but removing an unnecessary goroutine fixed it.
I've simplifiied the code because it was too complex for the current requirements, and this fixed the misc/initial_open
test which was occasionally failing due to a race condition around busy tasks
The global counter approach is easy to understand but it's brittle and depends on implicit behaviour that is not very discoverable.
With a global counter, if any goroutine accidentally decrements the counter twice, we'll think lazygit is idle when it's actually busy.
Likewise if a goroutine accidentally increments the counter twice we'll think lazygit is busy when it's actually idle.
With the new approach we have a map of tasks where each task can either be busy or not. We create a new task and add it to the map
when we spawn a worker goroutine (among other things) and we remove it once the task is done.
The task can also be paused and continued for situations where we switch back and forth between running a program and asking for user
input.
In order for this to work with `git push` (and other commands that require credentials) we need to obtain the task from gocui when
we create the worker goroutine, and then pass it along to the commands package to pause/continue the task as required. This is
MUCH more discoverable than the old approach which just decremented and incremented the global counter from within the commands package,
but it's at the cost of expanding some function signatures (arguably a good thing).
Likewise, whenever you want to call WithWaitingStatus or WithLoaderPanel the callback will now have access to the task for pausing/
continuing. We only need to actually make use of this functionality in a couple of places so it's a high price to pay, but I don't
know if I want to introduce a WithWaitingStatusTask and WithLoaderPanelTask function (open to suggestions).
We had some test flakiness involving the index.lock file which is fixed by this commit.
We shouldn't be accessing newTaskID without the mutex, although I'm surprised that this
actually fixes the issue. Surely we don't have tasks (which typically render to the main
view) which use index.lock?
We had a race condition due to refreshing branches in two different places, one which refreshed reflog commits
beforehand. The race condition meant that upon load we wouldn't see recency values (provided by the reflog commits)
against the branches
I want to see how we go removing all retry logic within a test. Lazygit should be trusted to tell us when it's no longer busy,
and if it that proves false we should fix the issue in the code rather than being lenient in the tests
Turns out we're just running our refresh functions one after the other which isn't ideal but we can fix that separately.
As it stands this wait group isn't doing anything.
I don't know if this is a hack or not: we run a git command and increment the pending action
count to 1 but at some point the command requests a username or password, so we need to prompt
the user to enter that. At that point we don't want to say that there is a pending action,
so we decrement the action count before prompting the user and then re-increment it again afterward.
Given that we panic when the counter goes below zero, it's important that it's not zero
when we run the git command (should be impossible anyway).
I toyed with a different approach using channels and a long-running goroutine that
handles all commands that request credentials but it feels over-engineered compared to this
commit's approach.
The remote branches controller was using its own escape method meaning it didn't go through the flow of cancelling
an active filter. It's now using the same approach as the sub-commits and commit-files contexts: defining a parent
context to return to upon hittin escape.
Given that we now persist search/filter states even after a side context loses focus, we need to make it really
clear to the user that the context is currently being searched/filtered
This is a pickle: initially I wanted it so that a filter would cancel automatically if the current context lost focus.
But there are situations where you want to retain the focus, e.g. when a popup appears, or when you view the commits
of a branch. The issue is that when you view the commits of a branch, the branches context is removed from the context
stack. Even if this were not the case, you could imagine going branches -> sub-commits -> files -> sub-commits, where
in that case branches would definitely be off the stack upon navigating to the files context.
So because I'm too lazy to find a proper solution to this problem, I'm just making it so that filters in side contexts
are retained unless explicitly cancelled.
There's another edge case this commit handles which is that if I'm in the sub-commits context via the branches context
and start a search, then navigate to the reflog context and hit enter to get to the sub-commits context again, I need
to cancel the search before I switch. Likewise with the commit files context.
The first line of the diff pane would show branch heads (e.g.
commit dd9100ccc8b69a8b14b21a84e34854b5acfb871a (mybranch, origin/mybranch)
only when a pager is used. The reason is that the default of the --decorate
option to git show is "auto", which means to show the decoration only when
output goes to a tty. Lazygit uses a pty only when a pager is used, so the
decoration wouldn't show when no pager is used.
Since the branch head annotation is useful and we always want to see it, force
it by explicitly passing --decorate.
This solves three problems:
1. When the local main branch is behind its upstream, the merged status of
commits of a feature branch sitting on origin/main was not correct. This can
easily happen when you rebase a branch onto origin/main instead of main, and
don't bother keeping local main up to date.
2. It works when you don't have the main branch locally at all. This could
happen when you check out a colleague's feature branch that goes off of
"develop", but you don't have "develop" locally yourself because you normally
only work on "main".
3. It also works when you work on a main branch itself, e.g. by committing to it
directly, or by merging a branch locally. These local commits on a main
branch would previously be shown in green instead of red; this broke with
910a61dc46.
For consistency with the previous commit.
Note that this menu entry is used both for unstaged and for staged changes, and
for staged changes it is not quite accurate, as we are not discarding changes in
that case (just unstaging them). Not sure it's worth fixing this; it's still
better than "Delete", anyway.
The title was saying "Unstage lines", which was just wrong. The text said
"Delete lines", which can be seen as a bit misleading; we are only discarding
the changes to the selected lines, not deleting the lines themselves.
For consistency, rename the config variable skipUnstageLineWarning accordingly.
The assert package is already very good at displaying errors, including printing
a diff of expected and actual value, so there's no point in printing the same
information again ourselves.
This test is almost identical to swap_in_rebase_with_conflict.go, except that it
sets the commit that will conflict to "edit".
This test is interesting because there's special code needed to determine
whether an "edit" command conflicted or not, i.e. whether to show the "confl"
entry. In this case we do. We have lots of other tests already that have "edit"
commands that don't conflict, so that's covered already.
When stopping in a rebase because of a conflict, it is nice to see the commit
that git is trying to apply. Create a fake todo entry labelled "conflict" for
this, and show the "<-- YOU ARE HERE ---" string for that one (in red) instead
of for the real current head.
This test is interesting because it already behaves as desired: since git has
rescheduled the "pick" command, we do _not_ want to show a "conflict" entry in
this case, as we would see the same commit twice then.
We don't actually use it to do map lookups; we still iterate over it in the same
way as before. However, using a map makes it easier to patch elements; see the
next commit.
We use CommitFilesController also for the files of commits that we show
elsewhere, e.g. for branch commits, tags, or stashes. It doesn't make sense to
discard changes from those (for stashes it might be possible to implement it
somehow, but that would be a new feature), so we disallow it unless we are in
the local commits panel.
Discarding changes to an entire directory doesn't quite work correctly in all
cases; for example, if the current commit added files to the directory (but the
directory existed before) then those files won't be removed.
It might be possible to fix the command so that these cases always work for
directories, but I don't think it's worth the effort (you can always use a
custom patch for that), so let's display an error for now.
I don't know why we were setting the initial context to CurrentSideContext
and not just CurrentContext in the first place. If there is no current context
in either case it'll default to the files context. So the only issue is if
we anticipated that some random context would be focused and we didn't want to
activate that. But I can't think of any situation where that would happen.
Whenever we perform an action in a test, we should assert on the result before doing the next action.
This prevents issues where the test moves too fast for our code. It would be nice to not have to do this,
but for now that's the situation
A better refactor would be to allow matchers to assert against either a string or a slice of cells, so that I could have
the same ergonomics that I have elsewhere, but this is a start.
The root commit is special in that it has no parents. So we need to add a pipe that's headed for a commit
that doesn't actually exist i.e. the mythical empty tree commit. We're using the actual hash of that
pseudo-commit, but it's not being read anywhere.
The menuFromCommand option is a little complicated, so I'm adding an easy way to just use the command output directly,
where each line becomes a suggestion, as-is.
Now that we support suggestions in the input prompt, there's less of a need for menuFromCommand, but it probably still
serves some purpose.
In future I want to support this filter/valueFormat/labelFormat thing for suggestions too. I would like to think a little more
about the interface though: is using a regex like we currently do really the simplest approach?
We have not been good at consistent casing so far. Now we use 'Sentence case' everywhere. EVERYWHERE.
Also Removing 'Lc' prefix from i18n field names: the 'Lc' stood for lowercase but now that everything
is in 'Sentence case' there's no need for the distinction.
I've got a couple lower case things I've kept: namely, things that show up in parentheses.
I'll be honest, for all I know logging should be global in general: it is
a pain to pass a logger to any struct that needs it. But smart people on the
internet tell me otherwise, and I do like the idea of not having any global
variables lying around.
Nonetheless, I often need to log things when locally debugging and that's a
different kind of logging than the kind you would include in the actual
released binary. For example if I want to log something from gocui, I would
rather not have gocui depend on lazygit's logging setup.
Go really doesn't like us doing anything inheritance-y: it does not support open recursion meaning
it's really hard to re-use code. As such, here we're falling back to conditional logic.
This fixes an issue where our ListContextTrait was calling FocusLine which was intended to be
overridden by ViewportListContextTrait, but the subclassed function wasn't being called. I'm
not actually sure how this went wrong given that it was working fine in the past, but at any rate,
the new code is easy to follow.
Missed a spot a couple PR's ago. We had an integration test which caught this but which was skipped due
to index.lock file issues. The test was also broken for other reasons due to it not having been running
for a while, so I've fixed that up too.
By constructing an arg vector manually, we no longer need to quote arguments
Mandate that args must be passed when building a command
Now you need to provide an args array when building a command.
There are a handful of places where we need to deal with a string,
such as with user-defined custom commands, and for those we now require
that at the callsite they use str.ToArgv to do that. I don't want
to provide a method out of the box for it because I want to discourage its
use.
For some reason we were invoking a command through a shell when amending a
commit, and I don't believe we needed to do that as there was nothing user-
supplied about the command. So I've switched to using a regular command out-
side the shell there
If a given menu item has an associated keybinding of 'enter', hitting enter won't actually execute
that item unless your cursor is on it. This creates confusion, and so we're going to use a strikethrough
style to communicate that the keybinding is reserved for something else.
The reason for this is that now our labels for navigation keybindings are larger so they
take up more realestate. It's not the kind of thing a user needs to be told anyway,
anybody is going to try out hjkl and the arrow keys when a TUI opens up.
We could map from <up> to the single character up unicode rune but given you can rebind this stuff
I'd rather keep it simple
The only exception is when moving a custom patch for an entire commit to an
earlier commit; in this case the source commit becomes empty, but we want to
keep it, mainly for consistency with moving the patch to a later commit, which
behaves the same.
In all other cases where we rebase, it's confusing when empty commits are kept;
the most common example is rebasing a branch onto master, where master already
contains some of the commits of our branch. In this case we simply want to drop
these.
The option doesn't have any affect in these views, so we don't need to toggle it
here. But the problem was the HandleFocus call at the end: this would activate
the wrong view, so we need to avoid it here.
Show an error if the user tries to turn the option on, to let them know that it
doesn't work here.
It's not possible to reliably stage things into a custom patch when "ignore
whitespace" is on, so always treat it as off here (like we do in the staging
panel).
It looks like this is a regression that was introduced in 8edad826ca.
It defaults to {"master", "main"}, but can be set to whatever branch names
are used as base branches, e.g. {"master", "devel", "v1.0-hotfixes"}. It is
used for color-coding the shas in the commit list, i.e. to decide whether
commits are green or yellow.
Our refresh code may try to push a context. It does this in two places:
1) when all merge conflicts are resolved, we push a 'continue merge?' confirmation context
2) when all conflicts of a given file are resolved and we're in the merge conflicts context,
we push the files context.
Sometimes we push the confirmation context and then push the files context over it, so the user
never sees the confirmation context.
This commit fixes the race condition by adding a check to ensure that we're still in the
merge conflicts panel before we try escaping from it
Previously, when rebasing a branch onto a newer master, all commits from the
previous fork point up to its head were marked red (unpushed), including the
commits that are on master already. While this is technically correct from the
perspective of the current branch's upstream, it's not what most people expect,
intuitively; they want to see where the current branch starts, relative to
master. So all commits of master should be green, and then the commits of the
current branch in red.
I don't see a reason why this restriction to have the selection be always
visible was necessary. Removing it has two benefits:
1. Scrolling a list view doesn't change the selection. A common scenario: you
look at one of the commits of your current branch; you want to see the how
many'th commit this is, but the beginning of the branch is scrolled off the
bottom of the commits panel. You scroll down to find the beginning of your
branch, but this changes the selection and shows a different commit now - not
what you want.
2. It is possible to scroll a panel that is not the current one without changing
the focus to it. That's how windows in other GUIs usually behave.
Enabling the filter selects the first entry in the filtered commits view. It's
useful to have a test that checks this, as I almost broke it in the following
commit (it needs an added FocusLine call in the setFiltering function in
filtering_menu_action.go).
We now refresh the staging panel when doing an unscoped refresh, so that if we commit from the staging panel we escape
back to the files panel if need be. But that causes flickering when doing an unscoped refresh from other contexts,
because the refreshStagingPanel function assumes that the staging panel has focus. So we're adding a guard at the top
of that function to early exit if we don't have focus.
When cycling history, we want to make it so that upon returning to the original prompt, you get your text back.
Importantly, we don't want to use the existing preservedMessage field for that because that's only for preserving
a NEW commit message, and we don't want the history stuff of the commit reword flow to overwrite that.
When we use the one panel for the entire commit message, its tricky to have a keybinding both for adding a newline and submitting.
By having two panels: one for the summary line and one for the description, we allow for 'enter' to submit the message when done from the summary panel,
and 'enter' to add a newline when done from the description panel. Alt-enter, for those who can use that key combo, also works for submitting the message
from the description panel. For those who can't use that key combo, and don't want to remap the keybinding, they can hit tab to go back to the summary panel
and then 'enter' to submit the message.
We have some awkwardness in that both contexts (i.e. panels) need to appear and disappear in tandem and we don't have a great way of handling that concept,
so we just push both contexts one after the other, and likewise remove both contexts when we escape.
This begins a big refactor of moving more code out of the Gui struct into contexts, controllers, and helpers. We also move some code into structs in the
gui package purely for the sake of better encapsulation
We now refresh the staging panel when doing an unscoped refresh, so that if we commit from the staging panel we escape
back to the files panel if need be. But that causes flickering when doing an unscoped refresh from other contexts,
because the refreshStagingPanel function assumes that the staging panel has focus. So we're adding a guard at the top
of that function to early exit if we don't have focus.
When cycling history, we want to make it so that upon returning to the original prompt, you get your text back.
Importantly, we don't want to use the existing preservedMessage field for that because that's only for preserving
a NEW commit message, and we don't want the history stuff of the commit reword flow to overwrite that.
When we use the one panel for the entire commit message, its tricky to have a keybinding both for adding a newline and submitting.
By having two panels: one for the summary line and one for the description, we allow for 'enter' to submit the message when done from the summary panel,
and 'enter' to add a newline when done from the description panel. Alt-enter, for those who can use that key combo, also works for submitting the message
from the description panel. For those who can't use that key combo, and don't want to remap the keybinding, they can hit tab to go back to the summary panel
and then 'enter' to submit the message.
We have some awkwardness in that both contexts (i.e. panels) need to appear and disappear in tandem and we don't have a great way of handling that concept,
so we just push both contexts one after the other, and likewise remove both contexts when we escape.
This broke with 40f6767cfc77; the symptom is that starting lazygit with a git
arg (e.g. "lazygit log") wouldn't activate the requested panel correctly. While
it would show in the expanded view, it didn't have a green frame, and keyboard
events would go to the files panel.
This fixes two problems with the "amend commit with staged changes" command:
1. Amending to a fixup commit didn't work (this would create a commmit with the
title "fixup! fixup! original title" and keep that at the top of the branch)
2. Unrelated fixup commits would be squashed too.
The added integration test verifies that both of these problems are fixed.
Instead of passing a bunch of different options in
PrepareInteractiveRebaseCommandOpts, where it was unclear how they interact if
several are set, have only a single field "instruction" which can be set to one
of various different instructions.
The functionality of replacing the entire todo file with our own is no longer
available; it is only possible to prepend todos to the existing file.
Also, instead of using different env vars for the various rebase operations that
we want to tell the daemon to do, use a single one that contains a json-encoded
struct with all available instructions. This makes the protocol much clearer,
and makes it easier to extend in the future.
At the moment it doesn't make a big difference, because the vast majority of
callers create a list of todos themselves to completely replace what git came up
with. We're changing this in the following commits though, and then it's helpful
to preserve merges.
We already show "merge" todo entries when starting an interactive rebase with
--rebase-merges outside of lazygit. Changing the type of a merge entry to "pick"
or "edit" doesn't make sense and shouldn't be allowed. Earlier in this branch we
have started to show "update-ref" entries, these can't be changed either (they
can be moved, though).
You might argue that it should be possible to change them to "drop", but in the
case of "update-ref" this doesn't make sense either, because "drop" needs a Sha
and we don't have one here. Also, you would then be able to later change it back
to "pick", so we would have to remember that this isn't allowed for this
particular drop entry; that's messy, so just disallow all editing.
It used to work on the assumption that rebasing commits in lazygit's model
correspond one-to-one to lines in the git-rebase-todo file, which isn't
necessarily true (e.g. when users use "git rebase --edit-todo" at the custom
command prompt and add a "break" between lines).
This is useful when working with stacked branches, because you can now move
"pick" entries across an update-ref command and you can tell exactly which
branch the commit will end up in.
It's also useful to spot situations where the --update-refs option didn't work
as desired. For example, if you duplicate a branch and want to rebase only one
of the branches but not the other (maybe for testing); if you have
rebase.updateRefs=true in your git config, then rebasing one branch will move
the other branch along. To solve this we'll have to introduce a way to delete
the update-ref entry (maybe by hitting backspace?); this is out of scope for
this PR, so for now users will have to type "git rebase --edit-todo" into the
custom command prompt to sort this out.
We will also have to prevent users from trying to turn update-ref commands into
other commands like "pick" or "drop"; we'll do this later in this branch.
So far the algorithm worked on the assumption that the output of the "git show"
command corresponds one-to-one to the lines of the rebase-todo file. This
assumption doesn't hold once we start to include todo lines that don't have a
sha (like update-ref), or when the todo file contains multiple entries for the
same sha. This should never happen normally, but it can if users manually edit
the todo file and duplicate a line.
The main reason for doing this (besides the reasons given for Status in the
previous commit) is that it allows us to easily convert from TodoCommand to
Action and back. This will be needed later in the branch. Fortunately,
TodoCommand is one-based, so this allows us to add an ActionNone constant with
the value 0.
This is unrelated to the changes in this PR, but since we are doing the same
thing for the commit.Action field in the next commit, it makes sense to do it
for Status too for consistency. Modelling this as an enum feels more natural
than modelling it as a string, since there's a finite set of possible values.
And it saves a little bit of memory (not very much, since none of the strings
were heap-allocated, but still).
We print this to the terminal after lazygit quits rather than showing it in a
panel at startup, so as to not annoy people too much. Hopefully it will still be
prominent enough this way.
Instead, query the platform defaults only if the config is empty. This will be
necessary later to distinguish an empty config from a default config, so that we
can give deprecation warnings.
The "open" command is supposed to behave in the same way as double-clicking a
file in the Finder/Explorer. The concept of jumping to a specific line in the
file doesn't make sense for this; use "edit" instead.
These files were renamed from os_windows_test.go to os_test_windows.go (etc.) in
95b2e9540a. Since then, the tests have no longer run, since go only looks for
tests in files ending with "test.go".
It isn't important that the file name ends with "_windows.go", since there are
already build constrains in the files themselves.
Pulled this out into a separate commit since it was unrelated to the
feature coming behind it.
This just cleans up the `commit_test.go` file slightly (for the method
that I was working on) so that the tests are built in a way that is
slightly more readable - testing each configuration option individually
without combining any of them.
When changing repos while debugging, the current working directory changes,
which means that a daemon lazygit doesn't find the debugger_config.yml file any
more when you do an interactive rebase. Fix this by using an absolute path for
the --use-config-file option.
Previously we would have tried to do the rebase, resulting in a long and
somewhat cryptic error message from git; now we check ourselves and show a less
intimidating message.
We refresh the view after reading just enough to fill it, so that we see the
initial content as quickly as possible, but then we continue reading enough
lines so that we can tell how long the scrollbar needs to be, and then we
refresh again. This can result in slight flicker of the scrollbar when it is
first drawn with a bigger size and then jumps to a smaller size; however, that's
a good tradeoff for a solution that provides both good speed and accuracy.
This fixes the problem that patching would stop at the first file that has a
conflict. We always want to patch all files.
Also, it's faster for large patches, and the code is a little bit simpler too.
The patch contains changes to two files; the first one conflicts, the second
doesn't. Note how it only applies changes to the first file at this point in the
branch; we'll fix this in the next commit.
This test would fail on master for multiple reasons.
All callers in this file now use reverseOnGenerate=false and
keepOriginalHeader=true, so hard-code that in the call to ModifiedPatchForLines
and get rid of the parameters.
The loop is pointless for two reasons:
- git apply --3way has this fallback built in already. If it can't do a
three-way merge, it will fall back to applying the patch normally.
- However, the only situation where it does this is when it can't do a 3-way
merge at all because it can't find the necessary ancestor blob. This can only
happen if you transfer a patch between different repos that don't have the
same blobs available; we are applying the patch to the same repo that is was
just generated from, so a 3-way merge is always possible. (Now that we fixed
the bug in the previous commit, that is.)
But the retry loop is not only pointless, it was actually harmful, because when
a 3-way patch fails with a conflict, git will put conflict markers in the
patched file and then exit with a non-zero exit status. So the retry loop would
try to patch the already patched file again, and this almost certainly fails,
but with a cryptic error message such as "error: main.go: does not exist in
index".
There's no reason to have two different ways of applying patches for whole-file
patches and partial patches; use --reverse for both. Not only does this simplify
the code a bit, but it fixes an actual problem: when reverseOnGenerate and
keepOriginalHeader are both true, the generated patch header is broken (the two
blobs in the line `index 6d1959b..6dc5f84 100644` are swapped). Git fails to do
a proper three-way merge in that case, as it expects the first of the two blobs
to be the common ancestor.
It would be possible to fix this by extending ModifiedPatchForLines to swap the
two blobs in this case; but this would prevent us from concatenating all patches
and apply them in one go, which we are going to do later in the branch.
We are going to add one more flag in the next commit.
Note that we are not using the struct inside patch_manager.go; we keep passing
the individual flags there. The reason for this will become more obvious later
in this branch.
This is the working tree state at the time the model commits were loaded. This
avoids a visual glitch with the "You Are Here" label appearing at times when it
is not supposed to.
Instead, derive it from context at display time (if we're rebasing, it's the
first non-todo commit). This fixes the problem that unfolding the current
commit's files in the local commits panel would show junk in the frame's title.
Along the way we make sure to only display the "<--- YOU ARE HERE" string in the
local commits panel; previously it would show for the top commit of a branch or
tag if mid-rebase.
If you ran this test enough times it would eventually fail; this happened
whenever the resulting squashed commit had a sha that happened to start with
"02". We test that "commit 02" does not appear in the diff window, but in that
case it did, at the very top of the window.
A better fix might be to change the commit message that we use in CreateNCommits
to something other than "commit XY", but that would require touching tons of
tests, so this is the easier fix.
It's not so much the total number of commits that matters here, it's just
whether we are on the first one. (This includes the other condition.)
This allows us to get rid of the condition in rebase.go.
Instead of rebasing from the commit below the current one and then setting the
current one to "edit", we rebase from the current one and insert a "break" after
it. In most cases the behavior is exactly the same as before, except that the
new method also works if the current commit is a merge commit. This is useful if
you want to create a new commit at the very beginning of your branch (by editing
the last commit before your branch).
There are two reasons for doing this:
1. The view cursor position is often out of sync with the selected line; see
first commit of this branch.
2. The highlighting is already turned off when the view loses focus, and never
turned back on thereafter. So just turn it off from the start then.
We already have this very convenient behavior of jumping to the next stageable
line after staging something. However, while this worked well for staging
single lines or hunks, it didn't work correctly when staging a range of lines;
in this case we want to start searching from the first line of the range.
While we try to keep the view's cursor position in sync with the context state's
selectedLineIdx (at least when pressing up or down), there are enough situations
where the two run out of sync; for example when initially opening the view, or
after staging a hunk, or when scrolling the view using the wheel. While it would
be possible to fix these situations to keep them always in sync, it doesn't seem
worth it, because the view's cursor position isn't really used for anything
else. So we rather special-case the SelectedLine/SelectedLineIdx functions of
ViewDriver to query the context state's selectedLineIdx directly if it is a
patch explorer context.
For users who have the rebase.autoSquash git config set to true, any regular
rebase will squash fixups in addition to rebasing. Not good -- we'll fix that in
the next commit.
A global ~/.gitconfig file can have influence on how integration tests behave;
in my case, I had the option "merge.conflictStyle" set to "diff3", which made
the integration test "cherry_pick_conflict" fail because the diff was different
from what the test expected.
Make this more robust by telling git to ignore the global config file when
running tests.
There's no need for refreshing anything; all that's needed is to re-focus the
selected list item. This way it will also work in other panels, which we are
about to add in the next commit.
I noticed that `$GIT_SEQUENCE_EDITOR` is overridden in `PrepareInteractiveRebaseCommand`
but not in `runSkipEditorCommand`.
Before this change, some commands such as `SquashAllAboveFixupCommits`
would not work when a different sequence editor, e.g.
[git-interactive-rebase-tool](https://github.com/MitMaro/git-interactive-rebase-tool)
is configured.
The 8.2 release of OpenSSH added support for FIDO/U2F hardware
authenticators, which manifests in being able to create new types of SSH
key, named `ecdsa-sk` nad `ed25519-sk`. This is relevant to lazygit,
as those SSH keys can be used to authorise git operations over SSH, as
well as signing git commits. Actual code changes are required for
correct support, as the authentication process for these types of keys
is different than the process for types supported previously.
When an operation requiring credentials is initialised with a U2F
authenticator-backed key, the first prompt is:
Enter PIN for ${key_type} key ${path_to_key}:
at which point the user is supposed to enter a numeric (and secret) PIN,
specific to the particular FIDO/U2F authenticator using which the SSH
keypair was generated. Upon entering the correct key, the user is
supposed to physically interact with the authenticator to confirm
presence. Sometimes this is accompanied by the following text prompt:
Confirm user presence for key ${key_type} ${key_fingerprint}
This second prompt does not always occur and it is presumed that the
user will know to perform this step even if not prompted specifically.
At this stage some authenticator devices may also begin to blink a LED
to indicate that they're waiting for input.
To facilitate lazygit's interoperability with these types of keys, add
support for the first PIN prompt, which allows "fetch", "pull", and
"push" git operations to complete.
If the remote name contains special regex-chars,
the compilation of the regex might fail.
Quoting the remoteName ensures that all special chars
in the remoteName are properly escaped before compiling
the regex.
test: add an integration test for checkout branch by name
fix: fix full ref name of detached head
refactor: refactor current branch loader
chore: use field name explicitly
Add Form field to CustomCommandObjects struct
Write user prompts responses to Form field
Ensure that map keys exists
Add form prompts integration test
Remove redundant index
- Supports configuring a custom confirmation prompt via `config.yml` for
custom keybindings. A new `CustomCommandPrompt.Body` field is
used to store the immutable body text of the confirmation popup.
- Adds a sample 'confirm' prompt to the example `config.yml`.
- Updates the `Prompts` section of the documentation to include
'confirm' prompt type and also describe which fields pertain to it
(i.e. `initialValue`).
Closes: https://github.com/jesseduffield/lazygit/issues/1858
Signed-off-by: Michael Mead <mmead.developer@gmail.com>
Show a proper notification popup once an update has been installed successfully
so the user knows we're done (so far a popup is only shown if an error occurred).
The popup also reminds him to restart lazygit for the changes to take effect.
Add a new 'Notification()' method to 'IPopupHandler' that makes it easier to
show a modal info message to the user. This is simply a convenience wrapper
around 'Ask()', so the popup can be closed using both 'Enter' and 'ESC'.
Check whether the auto-fetch interval configured is actually positive before
starting the background fetcher. If it is not, an error is logged. Also improve
the config option documentation a bit to make it easier to understand how to
disable auto-fetch.
Adds a new 'autoRefresh' option to the 'git' config section that allows user to
disable auto-refresh (defaults to on). If auto-refresh is enabled, the
refreshInterval is now checked before starting the timer to prevent crashes when
it is non-positive.
Fixes#1417
When using the "copy commit message to clipboard" action, the message will end
up in the clipboard with duplicate line breaks. The same issue also affects the
"Reword Commit" command. GetCommitMessage(), the function used to retrieve the
commit message first splits the output returned by git into separate lines -
without removing the line breaks. After removing the first line (which contains
the commit SHA), it joins the lines of the message itself back together - adding
a second set of line breaks along the way. Stop this from happening.
Fixes#1808.
When trying to download an update, a 'Could not find any binary at ...' error
message is shown erroneously. This happens since when checking the availability,
a response code of 403 ('Forbidden') instead of 200 ('OK') is expected. Since
'http.Head()' handles redirects automatically, there is no need to also accept
3xx status codes.
Fixes#1450.
more
and more
move rebase commit refreshing into existing abstraction
and more
and more
WIP
and more
handling clicks
properly fix merge conflicts
update cheatsheet
lots more preparation to start moving things into controllers
WIP
better typing
expand on remotes controller
moving more code into controllers
fix: 🐛 The root URI for Azure DevOps repositories contains _git
refactor so that we don't have conditional logic based on service definition
no need for this commend anymore
add comment
Fixed RegEx for HTTP remote git URL
Added Tests
pretty sure we can do this safely
Branches can now be colored based on their prefix, if it matches
a user defined prefix in the config file. If no user defined
prefix matches, then it will fallback to the defaults: green for
'feature', yellow for 'bugfix', and red for 'hotfix'. All
remaining branches will be set to the default text color.
If signing by GPG is enabled, the git commit command will be executed in
a subprocess, differently from when it is executed without GPG signing.
In case of an error, e.g. a failing pre-commit hook, the error needs to
be passed along, and not just ignored.
In case a commit fails, e.g. because a pre-commit hook returns an error,
lazygit will now remember the commit message and will suggest it during
the next commit (e.g. after fixing the error of the pre-commit hook).
Currently, when pushing or pulling a branch that has no tracking remote,
lazygit suggests the (hard-coded) remote named 'origin'. However, a
repository might not have a remote with this name, in which case the
suggestion makes no sense. This happens to me quite regularly when I
choose a more meaningful name than 'origin' for a remote.
This change keeps the current behavior by suggesting 'origin' when there
is either a remote with that name or no remote at all. However, when
'origin' does not exist, the name of the first remote is suggested.
Suggest existing remote for non-tracking branch
Currently, when pushing or pulling a branch that has no tracking remote,
lazygit suggests the (hard-coded) remote named 'origin'. However, a
repository might not have a remote with this name, in which case the
suggestion makes no sense. This happens to me quite regularly when I
choose a more meaningful name than 'origin' for a remote.
This change keeps the current behavior by suggesting 'origin' when there
is either a remote with that name or no remote at all. However, when
'origin' does not exist, the name of the first existing remote is
suggested.
The way the `if` expression in `deactivateContext` was composed,
it was possible to have it to evaluate to `true` even though the
`view` variable was `nil`.
As far as I can tell, this seems to be only possible during tests.
Nonetheless, I think the expression looks more "correct" this way.
Previous translation was completely unacceptable. As a native Polish speaker I consulted various Polish documentation files and translated almost all content.
My translation may contain minor errors due to the possible lack of context for some text.
This solves an issue where we could not open files with names that contained
spaces and single quotes.
It also solves an issue of variable expansion for files with some kind
of environment variables on the name e.g. '$USER.txt'
The proper fix will come out of this PR but it's not yet merged:
https://github.com/gdamore/tcell/pull/439/files
In the meantime I'm just going to directly edit this from my vendor
directory. If it ends up stretching a while I'll fork tcell properly
and use the fork.
Hard to choose between the lock with a defer unlock in an anonymous function
vs just having an explicit unlock at the end with additional unlocks before
any early returns. The former is less error prone, but the former is much more
readable, especially if the anonymous function would have needed to return
an error value.
I've been using this config option for years now so I don't think much of it,
but newcomers are going to find it annoying that hitting escape gets you out
of filtering/cherry-picking/patch-building mode, but also quits the app. So
if you want to exit all the modes you're in, you need to take care not to
press the key one too many times or the app will close.
We'll see if anybody gets mad about this change, but I think it's reasonable.
The only downside is that you won't be able to always quit by spamming the escape
key. If you're in a prompt panel, you'll need to hit escape to exit that, and
then 'q' at the top level. Or CTRL+C of course.
Checking is the associated rune of a key is printable solves our problem
of having panics whenever keys like `Home` or `Ctrl-W` are pressed.
Fixes#1175
This commit resolves issue with absence of ssh key prompting
to pull from or push to remote git repository.
I checked lazygit with this patch for successfully pull from
and push to https://gitweb.gentoo.org/repo/proj/guru.git repository.
While for lazygit-0.23.1 I'm not able to do that.
The check for Passphrase follows the Password because of
more long time before SSH key is prompt in terminal.
Otherwise after timeout "Password" prompt is appears.
Excuse me for google translated i18n dutch lines.
Bug: https://github.com/jesseduffield/lazygit/issues/534
Signed-off-by: band-a-prend <torokhov-s-a@yandex.ru>
support searching in line by line panel
move mutexes into their own struct
add line by line panel mutex
apply LBL panel mutex
bump gocui to prevent crashing when search item count decreases
> If you are mid-rebase, undo/redo is not supported, because the reflog doesn't enough contain information about what specific things have happened inside that rebase.
changed to
> If you are mid-rebase, undo/redo is not supported, because the reflog doesn't contain enough information about what specific things have happened inside that rebase.
This keybinding has been more pain than it's worth. Having a tab keybinding
to cycle tabs implies that you can shift+tab and when you shift+tab the
application exits because termbox, our dependency, doesn't know how to
interpret the escape sequence (so it takes it for an actual ESC key which
will exit lazygit at the top level).
If people get mad at me they can set nextBlock-alt to <tab> and they'll have
the functionality back :)
There has got to be a better way around this but if we're returning a Context
from a function (Context is an interface, not a concrete type), even if we
return nil, on the calling end it won't be equal to nil because an interface
value is a tuple of the type and the value meaning it's never itself nil,
unless both values in the tuple are nil.
So we're explicitly returning whether or not the underlying concrete type is nil.
When a user is not entering text into a prompt, the 'q' key should immediately
quit the application. On the other hand, the 'esc' key should cancel/close/go-back
to the previous context.
If we're at the surface level (nothing to cancel/close) and the user hits the
escape key, the default behaviour is to close the app, however we now have a
`quitOnTopLevelReturn` config key to override this.
I actually think from the beginning we should have made this config option
default to false rather than true which is the default this PR gives it,
but I don't want to anger too many people familiar with the existing behaviour.
The master branch of gocui contains stuff I added for lazynpm which changes how
the cursor is used. This will provide some benefits to lazygit as well but I
don't yet have the motivation to make the required changed in lazygit to support it.
So we're gonna be on the branch named 'simple' rather than master until I fix that up.
Add GetHeadCommitMessage to read the subject of the HEAD commit
Create PullPatchIntoNewCommit based heavily on PullPatchIntoIndex to
split the current patch from its commit and apply it in a separate
commit immediately after.
WIP to Squash - Fill format string with format string
WIP
It's true that parts of git are genuinely difficult to use, so
we don't need to overstate that difficulty in order to state the
value proposition of lazygit. If `git add -p` can't split a hunk
any further, one is not _completely_ stuck, but one does need to
edit the hunk in a way that, after years, I still need a few
attempts to get right. The fact that many otherwise-experienced
git users don't even know that one can do that is itself a
testament to the UX problems that lazygit is trying to address.
By default, search is now case insensitive.
If you include uppercase characters in your search string, the search
will become case sensitive. This means there is no way to do a case-
insensitive search of all-lowercase strings. We could add more support
for this but we'll wait until we come across the use case
I realized that the current example config in `Config.md` for a Colemak keyboard layout user will cause key conflicts in certain panels. This change addresses that issue.
The state object is sometimes undefined in the onclick method of the
line by line panel. Because we set it to nil in a bunch of places,
I've decided to just change the main context to 'normal' before setting
it to nil anywhere. That way the keybindings for the line by line panel
won't get executed and we won't get a segfault.
When staging lines (or doing anything that requires the main view to split into two)
we want to split vertically if there's not much width available in the window.
If there is enough width we will split horizontally. The aim here is to allow for
sufficient room in the side panel. We might need to tweak this or make it configurable
but I think it's set to a pretty reasonable default i.e. switching to split vertically
when the window width falls under 220
The issue here was that we were using a string task
but expecting to be able to set the origin straight after
to point at the conflict, but because it's async it was
actually resetting the origin to 0 after a little bit.
The proper solution here is maybe to add a flag to that thing
asking whether you want to reset main's origin. But I'm
too lazy to do that right now so instead I'm just using
setViewContent. That will probably cause issues in the future.
Up till now our approach to rendering things like file diffs, branch logs, and
commit patches, has been to run a command on the command line, wait for it to
complete, take its output as a string, and then write that string to the main
view (or secondary view e.g. when showing both staged and unstaged changes of a
file).
This has caused various issues. For once, if you are flicking through a list of
files and an untracked file is particularly large, not only will this require
lazygit to load that whole file into memory (or more accurately it's equally
large diff), it also will slow down the UI thread while loading that file, and
if the user continued down the list, the original command might eventually
resolve and replace whatever the diff is for the newly selected file.
Following what we've done in lazydocker, I've added a tasks package for when you
need something done but you want it to cancel as soon as something newer comes
up. Given this typically involves running a command to display to a view, I've
added a viewBufferManagerMap struct to the Gui struct which allows you to define
these tasks on a per-view basis.
viewBufferManagers can run files and directly write the output to their view,
meaning we no longer need to use so much memory.
In the tasks package there is a helper method called NewCmdTask which takes a
command, an initial amount of lines to read, and then runs that command, reads
that number of lines, and allows for a readLines channel to tell it to read more
lines. We read more lines when we scroll or resize the window.
There is an adapter for the tasks package in a file called tasks_adapter which
wraps the functions from the tasks package in gui-specific stuff like clearing
the main view before starting the next task that wants to write to the main
view.
I've removed some small features as part of this work, namely the little headers
that were at the top of the main view for some situations. For example, we no
longer show the upstream of a selected branch. I want to re-introduce this in
the future, but I didn't want to make this tasks system too complicated, and in
order to facilitate a header section in the main view we'd need to have a task
that gets the upstream for the current branch, writes it to the header, then
tells another task to write the branch log to the main view, but without
clearing inbetween. So it would get messy. I'm thinking instead of having a
separate 'header' view atop the main view to render that kind of thing (which
can happen in another PR)
I've also simplified the 'git show' to just call 'git show' and not do anything
fancy when it comes to merge commits.
I considered using this tasks approach whenever we write to a view. The only
thing is that the renderString method currently resets the origin of a view and
I don't want to lose that. So I've left some in there that I consider harmless,
but we should probably be just using tasks now for all rendering, even if it's
just strings we can instantly make.
By default, macs have 256 open files allowed by a given process.
This sucks when you end up with over 256 files modified in a repo
because after you've watched all of them, lots of other calls to
the command line will fail due to violating the limit.
Given there's no easy platform agnostic way to see what you've got
configured for how many files a process can have open, I'm going to
arbitrarily set the max to 200 and when we hit the limit we start
unwatching older files to make way for new ones.
WIP
This bug was caused how the timestamp was formatted for the patch file.
On Windows machines, ":" is an invalid character for a filename, but the
`stampNano` format for time contains ":".
This fix adjusts the time format to be the `stampNano` format with "."
subsituted for ":".
when it contains percentages.
This is a really strange one. It's a linting warning in my editor
and it doesn't stop me from compiling, but it breaks `go test`.
A basic file to reproduce what I'm talking about:
package main
import "fmt"
func main() {
notSprintf("test %s") // compiler complains here thinking %s needs a corresponding argument
}
func notSprintf(formatStr string, formatArgs ...interface{}) string {
if formatArgs != nil {
return formatStr
}
return fmt.Sprintf(formatStr, formatArgs...)
}
For this to work you'll need to put this in your ~/.zshrc (or equivalent rc file):
lg()
{
export LAZYGIT_NEW_DIR_FILE=/Users/jesseduffieldduffield/Library/Application\ Support/jesseduffield/lazygit/.lastd
lazygit "$@"
if [ -f $LAZYGIT_NEW_DIR_FILE ]; then
cd "$(cat $LAZYGIT_NEW_DIR_FILE)"
rm -f $LAZYGIT_NEW_DIR_FILE > /dev/null
fi
}
# adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/Dockerfile
# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster
ARGVARIANT=1-bullseye
FROMgolang:${VARIANT}
RUN go install mvdan.cc/gofumpt@latest
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.0
RUN golangci-lint --version
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
@@ -4,7 +4,6 @@ about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -23,10 +23,15 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows]
- Lazygit Version [e.g. v0.1.45]
- The last commit id if you built project from sources (run : ```git-rev parse HEAD```)
**Version info:**
_Run `lazygit --version` and paste the result here_
_Run `git --version` and paste the result here_
**Additional context**
Add any other context about the problem here.
**Note:** please try updating to the latest version or [manually building](https://github.com/jesseduffield/lazygit/#manual) the latest `master` to see if the issue still occurs.
<!--
If you want to try and debug this issue yourself, you can run `lazygit --debug` in one terminal panel and `lazygit --logs` in another to view the logs.
@@ -4,7 +4,6 @@ about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
@@ -18,3 +17,13 @@ A clear and concise description of any alternative solutions or features you've
**Additional context**
Add any other context or screenshots about the feature request here.
<!--
You may be able to add your desired feature with a custom command. Check out the examples here: https://github.com/jesseduffield/lazygit/wiki/Custom-Commands-Compendium
If a custom command does what you want but you still want to see the feature built-in to lazygit, feel free to paste the custom command into the issue to help us better understand the functionality you want.
We also encourage you to put up a PR yourself! Who cares if you've never written Go before, neither did any of the existing contributors before their first lazygit PR! Check out the PR tutorial here: https://www.youtube.com/watch?v=kNavnhzZHtk&ab_channel=JesseDuffield
Also check out the contributing guide here: https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md
- **Please check if the PR fulfills these requirements**
* [ ] Cheatsheets are up-to-date (run `go run scripts/cheatsheet/main.go generate`)
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] Docs (specifically `docs/Config.md`) have been updated if necessary
* [ ] You've read through your own file changes for silly mistakes etc
# ensure our vendor directory matches up with our go modules
run:|
go mod vendor && git diff --exit-code || (echo "Unexpected change to vendor directory. Run 'go mod vendor' locally and commit the changes" && exit 1)
- name:Check Integration Test List
# ensure our integration test list is up to date
run:|
go generate pkg/integration/tests/tests.go && git diff --exit-code || (echo "Integration test list not up to date. Run 'go generate pkg/integration/tests/tests.go' locally and commit the changes" && exit 1)
When contributing to this repository, please first discuss the change you wish
to make via issue, email, or any other method with the owners of this repository
before making a change.
before making a change.
## PR walkthrough
[This video](https://www.youtube.com/watch?v=kNavnhzZHtk) walks through the process of adding a small feature to lazygit. If you have no idea where to start, watching that video is a good first step.
## All code changes happen through Pull Requests
## So all code changes happen through Pull Requests
Pull requests are the best way to propose changes to the codebase. We actively
welcome your pull requests:
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests.
3. If you've added code that need documentation, update the documentation.
4.Make sure your code follows the [effective go](https://golang.org/doc/effective_go.html) guidelines as much as possible.
5.Be sure to test your modifications.
6. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
7. Issue that pull request!
4.Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
5.Issue that pull request!
If you've never written Go in your life, then join the club! Lazygit was the maintainer's first Go program, and most contributors have never used Go before. Go is widely considered an easy-to-learn language, so if you're looking for an open source project to gain dev experience, you've come to the right place.
## Running in a VSCode dev container
If you want to spare yourself the hassle of setting up your dev environment yourself (i.e. installing Go, extensions, and extra tools), you can run the Lazygit code in a VSCode dev container like so:
* you have the dev containers extension installed in VSCode
See [here](https://code.visualstudio.com/docs/devcontainers/containers) for more info about dev containers.
## Running in a Github Codespace
If you want to start contributing to Lazygit with the click of a button, you can open the lazygit codebase in a Codespace. First fork the repo, then click to create a codespace:
To run lazygit from within the integrated terminal just go `go run main.go`
This allows you to contribute to Lazygit without needing to install anything on your local machine. The Codespace has all the necessary tools and extensions pre-installed.
## Code of conduct
Please note by participating in this project, you agree to abide by the [code of conduct].
[code of conduct]: https://github.com/jesseduffield/lazygit/blob/master/CODE-OF-CONDUCT.md
## Any contributions you make will be under the MIT Software License
In short, when you submit code changes, your submissions are understood to be
under the same [MIT License](http://choosealicense.com/licenses/mit/) that
covers the project. Feel free to contact the maintainers if that's a concern.
## Report bugs using Github's [issues](https://github.com/jesseduffield/lazygit/issues)
We use GitHub issues to track public bugs. Report a bug by [opening a new
issue](https://github.com/jesseduffield/lazygit/issues/new); it's that easy!
## Go
This project is written in Go. Go is an opinionated language with strict idioms, but some of those idioms are a little extreme. Some things we do differently:
1. There is no shame in using `self` as a receiver name in a struct method. In fact we encourage it
2. There is no shame in prefixing an interface with 'I' instead of suffixing with 'er' when there are several methods on the interface.
3. If a struct implements an interface, we make it explicit with something like:
```go
var_MyInterface=&MyStruct{}
```
This makes the intent clearer and means that if we fail to satisfy the interface we'll get an error in the file that needs fixing.
### Code Formatting
To check code formatting [gofumpt](https://pkg.go.dev/mvdan.cc/gofumpt#section-readme) (which is a bit stricter than [gofmt](https://pkg.go.dev/cmd/gofmt)) is used.
VSCode will format the code correctly if you tell the Go extension to use `gofumpt` via your [`settings.json`](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson)
by setting [`formatting.gofumpt`](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#gofumpt-bool) to `true`:
```jsonc
// .vscode/settings.json
{
"gopls":{
"formatting.gofumpt":true
}
}
```
To run gofumpt from your terminal go:
```
go install mvdan.cc/gofumpt@latest && gofumpt -l -w .
```
## Internationalisation
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `self.c.Tr.YourNewText`, etc). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
Note, we use 'Sentence case' for everything (so no 'Title Case' or 'whatever-it's-called-when-there's-no-capital-letters-case')
## Debugging
The easiest way to debug lazygit is to have two terminal tabs open at once: one for running lazygit (via `go run main.go -debug` in the project root) and one for viewing lazygit's logs (which can be done via `go run main.go --logs` or just `lazygit --logs`).
From most places in the codebase you have access to a logger e.g. `gui.Log.Warn("blah")` or `self.c.Log.Warn("blah")`.
If you find that the existing logs are too noisy, you can set the log level with e.g. `LOG_LEVEL=warn go run main.go -debug` and then only use `Warn` logs yourself.
If you need to log from code in the vendor directory (e.g. the `gocui` package), you won't have access to the logger, but you can easily add logging support by setting the `LAZYGIT_LOG_PATH` environment variable and using `logs.Global.Warn("blah")`. This is a global logger that's only intended for development purposes.
If you keep having to do some setup steps to reproduce an issue, read the Testing section below to see how to create an integration test by recording a lazygit session. It's pretty easy!
### VSCode debugger
If you want to trigger a debug session from VSCode, you can use the following snippet. Note that the `console` key is, at the time of writing, still an experimental feature.
```jsonc
// .vscode/launch.json
{
"version":"0.2.0",
"configurations":[
{
"name":"debug lazygit",
"type":"go",
"request":"launch",
"mode":"auto",
"program":"main.go",
"args":["--debug"],
"console":"externalTerminal"// <-- you need this to actually see the lazygit UI in a window while debugging
}
]
}
```
## Profiling
If you want to investigate what's contributing to CPU usage you can add the following to the top of the `main()` function in `main.go`
```go
import"runtime/pprof"
funcmain(){
f,err:=os.Create("cpu.prof")
iferr!=nil{
log.Fatal("could not create CPU profile: ",err)
}
deferf.Close()
iferr:=pprof.StartCPUProfile(f);err!=nil{
log.Fatal("could not start CPU profile: ",err)
}
deferpprof.StopCPUProfile()
...
```
Then run lazygit, and afterwards, from your terminal, run:
```sh
go tool pprof --web cpu.prof
```
That should open an application which allows you to view the breakdown of CPU usage.
## Testing
Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go in files that end in `_test.go`, and are written in Go. For integration tests, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
## Updating Gocui
Sometimes you will need to make a change in the gocui fork (https://github.com/jesseduffield/gocui). Gocui is the package responsible for rendering windows and handling user input. Here's the typical process to follow:
1. Make the changes in gocui inside lazygit's vendor directory so it's easy to test against lazygit
2. Copy the changes over to the actual gocui repo (clone it if you haven't already, and use the `awesome` branch, not `master`)
3. Raise a PR on the gocui repo with your changes
4. After that PR is merged, make a PR in lazygit bumping the gocui version. You can bump the version by running the following at the lazygit repo root:
```sh
./scripts/bump_gocui.sh
```
5. Raise a PR in lazygit with those changes
## Updating Lazycore
[Lazycore](https://github.com/jesseduffield/lazycore) is a repo containing shared functionality between lazygit and lazydocker. Sometimes you will need to make a change to that repo and import the changes into lazygit. Similar to updating Gocui, here's what you do:
1. Make the changes in lazycore inside lazygit's vendor directory so it's easy to test against lazygit
2. Copy the changes over to the actual lazycore repo (clone it if you haven't already, and use the `master` branch)
3. Raise a PR on the lazycore repo with your changes
4. After that PR is merged, make a PR in lazygit bumping the lazycore version. You can bump the version by running the following at the lazygit repo root:
```sh
./scripts/bump_lazycore.sh
```
Or if you're using VSCode, there is a bump lazycore task you can find by going `cmd+shift+p` and typing 'Run task'
5. Raise a PR in lazygit with those changes
## Improvements
If you can think of any way to improve these docs let us know.
For old installations (slightly embarrassing: I didn't realise at the time that you didn't need to supply a vendor name to the path so I just used my name):
Specify an external command to invoke when copying to clipboard is requested. `{{text}` will be replaced by text to be copied. Default is to copy to system clipboard.
for users of VSCode
If you are working on a terminal that supports OSC52, the following command will let you take advantage of it:
To override the default config directory, use `CONFIG_DIR="$HOME/.config/lazygit"`. This directory contains the config file in addition to some other files lazygit uses to keep track of state across sessions.
To override the individual config file used, use the `--use-config-file` arg or the `LG_CONFIG_FILE` env var.
If you want to merge a specific config file into a more general config file, perhaps for the sake of setting some theme-specific options, you can supply a list of comma-separated config file paths, like so:
For color attributes you can choose an array of attributes (with max one color attribute)
The available attributes are:
- default
**Colors**
- black
- red
- green
@@ -78,25 +361,204 @@ The available attributes are:
- magenta
- cyan
- white
- '#ff00ff'
**Modifiers**
- bold
- default
- reverse # useful for high-contrast
- underline
- strikethrough
## Light terminal theme:
## Highlighting the selected line
If you have issues with a light terminal theme where you can't read / see the text add these settings
If you don't like the default behaviour of highlighting the selected line with a blue background, you can use the `selectedLineBgColor` and `selectedRangeBgColor` keys to customise the behaviour. If you just want to embolden theselected line (this was the original default), you can do the following:
Lazygit will assign a random color for every commit author in the commits pane by default.
You can customize the color in case you're not happy with the randomly assigned one:
```yaml
gui:
authorColors:
'John Smith':'red'# use red for John Smith
'Alan Smithee':'#00ff00'# use green for Alan Smithee
```
You can use wildcard to set a unified color in case your are lazy to customize the color for every author or you just want a single color for all/other authors:
```yaml
gui:
authorColors:
# use red for John Smith
'John Smith':'red'
# use blue for other authors
'*':'#0000ff'
```
## Custom Branch Color
You can customize the color of branches based on the branch prefix:
```yaml
gui:
branchColors:
'docs':'#11aaff'# use a light blue for branches beginning with 'docs/'
If you are using [Nerd Fonts](https://www.nerdfonts.com), you can display icons.
```yaml
gui:
nerdFontsVersion:"3"
```
Supported versions are "2" and "3". The deprecated config `showIcons` sets the
version to "2" for backwards compatibility.
## Keybindings
For all possible keybinding options, check [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md)
You can disable certain key bindings by specifying `null`.
```yaml
keybinding:
universal:
edit:null# disable 'edit file'
```
### Example Keybindings For Colemak Users
```yaml
keybinding:
universal:
prevItem-alt:'u'
nextItem-alt:'e'
prevBlock-alt:'n'
nextBlock-alt:'i'
nextMatch:'='
prevMatch:'-'
new:'k'
edit:'o'
openFile:'O'
scrollUpMain-alt1:'U'
scrollDownMain-alt1:'E'
scrollUpMain-alt2:'<c-u>'
scrollDownMain-alt2:'<c-e>'
undo:'l'
redo:'<c-r>'
diffingMenu:'M'
filteringMenu:'<c-f>'
files:
ignoreFile:'I'
commits:
moveDownCommit:'<c-e>'
moveUpCommit:'<c-u>'
branches:
viewGitFlowOptions:'I'
setUpstream:'U'
```
## Custom pull request URLs
Some git provider setups (e.g. on-premises GitLab) can have distinct URLs for git-related calls and
the web interface/API itself. To work with those, Lazygit needs to know where it needs to create
the pull request. You can do so on your `config.yml` file using the following syntax:
```yaml
services:
'<gitDomain>':'<provider>:<webDomain>'
```
Where:
-`gitDomain` stands for the domain used by git itself (i.e. the one present on clone URLs), e.g. `git.work.com`
-`provider` is one of `github`, `bitbucket`, `bitbucketServer`, `azuredevops`, `gitlab` or `gitea`
-`webDomain` is the URL where your git service exposes a web interface and APIs, e.g. `gitservice.work.com`
## Predefined commit message prefix
In situations where certain naming pattern is used for branches and commits, pattern can be used to populate
commit message with prefix that is parsed from the branch name.
Example:
- Branch name: feature/AB-123
- Commit message: [AB-123] Adding feature
```yaml
git:
commitPrefixes:
my_project:# This is repository folder name
pattern:"^\\w+\\/(\\w+-\\w+).*"
replace:'[$1] '
```
## Custom git log command
You can override the `git log` command that's used to render the log of the selected branch like so:
Looking at the command assigned to the 'n' key, here's what the result looks like:

Custom command keybindings will appear alongside inbuilt keybindings when you view the keybindings menu by pressing '?':

For a given custom command, here are the allowed fields:
| _field_ | _description_ | required |
|-----------------|----------------------|-|
| key | The key to trigger the command. Use a single letter or one of the values from [here](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md) | yes |
| command | The command to run (using Go template syntax for placeholder values) | yes |
| context | The context in which to listen for the key (see [below](#contexts)) | yes |
| subprocess | Whether you want the command to run in a subprocess (e.g. if the command requires user input) | no |
| prompts | A list of prompts that will request user input before running the final command | no |
| loadingText | Text to display while waiting for command to finish | no |
| description | Label for the custom command when displayed in the keybindings menu | no |
| stream | Whether you want to stream the command's output to the Command Log panel | no |
| showOutput | Whether you want to show the command's output in a popup within Lazygit | no |
| after | Actions to take after the command has completed | no |
Here are the options for the `after` key:
| _field_ | _description_ | required |
|-----------------|----------------------|-|
| checkForConflicts | true/false. If true, check for merge conflicts | no |
| type | One of 'input', 'confirm', 'menu', 'menuFromCommand' | yes |
| title | The title to display in the popup panel | no |
| key | Used to reference the entered value from within the custom command. E.g. a prompt with `key: 'Branch'` can be referred to as `{{.Form.Branch}}` in the command | yes |
| command | The command to run to generate menu options | yes |
| filter | The regexp to run specifying groups which are going to be kept from the command's output | no |
| valueFormat | How to format matched groups from the filter to construct a menu item's value | no |
| labelFormat | Like valueFormat but for the labels. If `labelFormat` is not specified, `valueFormat` is shown instead. | no |
Here's an example using named groups in the regex. Notice how we can pipe the label to a colour function for coloured output (available colours [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md))
```yml
- key :'a'
description:'Checkout a remote branch as FETCH_HEAD'
Here's an example using a command but not specifying anything else: so each line from the command becomes the value and label of the menu items
```yml
- key :'a'
description:'Checkout a remote branch as FETCH_HEAD'
command:"open {{.Form.File | quote}}"
context:'global'
prompts:
- type:'menuFromCommand'
title:'File:'
key:'File'
command:'ls'
```
## Placeholder values
Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/golang/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects:
```
SelectedLocalCommit
SelectedReflogCommit
SelectedSubCommit
SelectedFile
SelectedPath
SelectedLocalBranch
SelectedRemoteBranch
SelectedRemote
SelectedTag
SelectedStashEntry
SelectedCommitFile
SelectedWorktree
CheckedOutBranch
```
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/models/file.go) (all the modelling lives in the same directory). Note that the custom commands feature does not guarantee backwards compatibility (until we hit Lazygit version 1.0 of course) which means a field you're accessing on an object may no longer be available from one release to the next. Typically however, all you'll need is `{{.SelectedFile.Name}}`, `{{.SelectedLocalCommit.Sha}}` and `{{.SelectedLocalBranch.Name}}`. In the future we will likely introduce a tighter interface that exposes a limited set of fields for each model.
## Keybinding collisions
If your custom keybinding collides with an inbuilt keybinding that is defined for the same context, only the custom keybinding will be executed. This also applies to the global context. However, one caveat is that if you have a custom keybinding defined on the global context for some key, and there is an in-built keybinding defined for the same key and for a specific context (say the 'files' context), then the in-built keybinding will take precedence. See how to change in-built keybindings [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#keybindings)
## Debugging
If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `showOutput: true` so that it doesn't actually execute the command but you can see how the placeholders were resolved.
## More Examples
See the [wiki](https://github.com/jesseduffield/lazygit/wiki/Custom-Commands-Compendium) page for more examples, and feel free to add your own custom commands to this page so others can benefit!
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `o` in the Status panel).
Support does not extend to Windows users, because we're making use of a package which doesn't have Windows support.
## Default:
```yaml
git:
paging:
colorArg:always
useConfig:false
```
the `colorArg` key is for whether you want the `--color=always` arg in your `git diff` command. Some pagers want it set to `always`, others want it set to `never`.
## Delta:
```yaml
git:
paging:
colorArg:always
pager:delta --dark --paging=never
```

## Diff-so-fancy
```yaml
git:
paging:
colorArg:always
pager:diff-so-fancy
```

## ydiff
```yaml
gui:
sidePanelWidth:0.2# gives you more space to show things side-by-side
Be careful with this one, I think the homebrew and pip versions are behind master. I needed to directly download the ydiff script to get the no-pager functionality working.
## Using git config
```yaml
git:
paging:
colorArg:always
useConfig:true
```
If you set `useConfig: true`, lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
Depending on the currently focused view, hitting '/' will bring up a filter or search prompt. When filtering, the contents of the view will be filtered down to only those lines which match the query string. When searching, the contents of the view are not filtered, but matching lines are highlighted and you can iterate through matches with `n`/`N`.
We intend to support filtering for the files view soon, but at the moment it uses searching. We intend to continue using search for the commits view because you typically care about the commits that come before/after a matching commit.
If you would like both filtering and searching to be enabled on a given view, please raise an issue for this.
## Filtering files by status
You can filter the files view to only show staged/unstaged files by pressing `<c-b>` in the files view.
## Filtering commits by file path
You can filter the commits view to only show commits which contain changes to a given file path.
You can do this in a couple of ways:
1) Start lazygit with the -f flag e.g. `lazygit -f my/path`
2) From within lazygit, press `<c-s>` and then enter the path of the file you want to filter by
If you're as clumsy as me you'll probably have felt the pain of botching an interactive rebase or doing a hard reset onto the wrong commit. Luckily, the reflog allows you to trace your steps and make things right again, but I personally can't stand trying to make sense of the reflog.
Lazygit can read through your reflog for you and walk back action by action so that you don't even need to read the reflog. If lazygit finds a reflog entry where you checked out a branch, we'll checkout the original branch. If the entry is from a commit being applied, we'll go back to the commit before that. If we hit an interactive rebase, we'll go back to the commit you were on just before you started it.
## You can even undo things you did outside of lazygit!
Because lazygit just uses the reflog to keep track of things, it doesn't matter whether you're trying to undo something you did in lazygit or directly on the command line. You can open lazygit for the first time and start undoing thing in your repo! Likewise, lazygit marks its undos/redos in the reflog so if you quit the application and come back, lazygit still knows where you're up to.
## Limitations
There are limitations: firstly, lazygit can only undo things that are recorded in the reflog. That means changes to your working tree or stash aren't covered. Secondly, anything permanent you do like pushing to a remote can't be undone. Thirdly, actions like creating a branch won't be undone, because they're not stored in the reflog.
If you are mid-rebase, undo/redo is not supported, because the reflog doesn't contain enough information about what specific things have happened inside that rebase. If you want to undo out of a rebase, it's best to abort the rebase (the default keybinding for bringing up rebase options is 'm').
Undo/Redo is a new feature so if you find a bug let us know. The worst case scenario is that you'll just need to look at your reflog and manually put yourself back on track.
This topic deserves its own doc because there there are a few touch points for it. We have a use-case for knowing when Lazygit is idle or busy because integration tests follow the following process:
1) press a key
2) wait until Lazygit is idle
3) run assertion / press another key
4) repeat
In the past the process was:
1) press a key
2) run assertion
3) if assertion fails, wait a bit and retry
4) repeat
The old process was problematic because an assertion may give a false positive due to the contents of some view not yet having changed since the last key was pressed.
## The solution
First, it's important to distinguish three different types of goroutines:
* The UI goroutine, of which there is only one, which infinitely processes a queue of events
* Worker goroutines, which do some work and then typically enqueue an event in the UI goroutine to display the results
* Background goroutines, which periodically spawn worker goroutines (e.g. doing a git fetch every minute)
The point of distinguishing worker goroutines from background goroutines is that when any worker goroutine is running, we consider Lazygit to be 'busy', whereas this is not the case with background goroutines. It would be pointless to have background goroutines be considered 'busy' because then Lazygit would be considered busy for the entire duration of the program!
In gocui, the underlying package we use for managing the UI and events, we keep track of how many busy goroutines there are using the `Task` type. A task represents some work being done by lazygit. The gocui Gui struct holds a map of tasks and allows creating a new task (which adds it to the map), pausing/continuing a task, and marking a task as done (which removes it from the map). Lazygit is considered to be busy so long as there is at least one busy task in the map; otherwise it's considered idle. When Lazygit goes from busy to idle, it notifies the integration test.
It's important that we play by the rules below to ensure that after the user does anything, all the processing that follows happens in a contiguous block of busy-ness with no gaps.
### Spawning a worker goroutine
Here's the basic implementation of `OnWorker` (using the same flow as `WaitGroup`s):
```go
func(g*Gui)OnWorker(ffunc(*Task)){
task:=g.NewTask()
gofunc(){
f(task)
task.Done()
}()
}
```
The crucial thing here is that we create the task _before_ spawning the goroutine, because it means that we'll have at least one busy task in the map until the completion of the goroutine. If we created the task within the goroutine, the current function could exit and Lazygit would be considered idle before the goroutine starts, leading to our integration test prematurely progressing.
You typically invoke this with `self.c.OnWorker(f)`. Note that the callback function receives the task. This allows the callback to pause/continue the task (see below).
### Spawning a background goroutine
Spawning a background goroutine is as simple as:
```go
goutils.Safe(f)
```
Where `utils.Safe` is a helper function that ensures we clean up the gui if the goroutine panics.
### Programmatically enqueing a UI event
This is invoked with `self.c.OnUIThread(f)`. Internally, it creates a task before enqueuing the function as an event (including the task in the event struct) and once that event is processed by the event queue (and any other pending events are processed) the task is removed from the map by calling `task.Done()`.
### Pressing a key
If the user presses a key, an event will be enqueued automatically and a task will be created before (and `Done`'d after) the event is processed.
## Special cases
There are a couple of special cases where we manually pause/continue the task directly in the client code. These are subject to change but for the sake of completeness:
### Writing to the main view(s)
If the user focuses a file in the files panel, we run a `git diff` command for that file and write the output to the main view. But we only read enough of the command's output to fill the view's viewport: further loading only happens if the user scrolls. Given that we have a background goroutine for running the command and writing more output upon scrolling, we create our own task and call `Done` on it as soon as the viewport is filled.
### Requesting credentials from a git command
Some git commands (e.g. git push) may request credentials. This is the same deal as above; we use a worker goroutine and manually pause continue its task as we go from waiting on the git command to waiting on user input. This requires passing the task through to the `Push` method so that it can be paused/continued.
We want our demo recordings to be consistent and easy to update if we make changes to Lazygit's UI. Luckily for us, we have an existing recording system for the sake of our integration tests, so we can piggyback on that.
You'll want to familiarise yourself with how integration tests are written: see [here](../../pkg/integration/README.md).
## Prerequisites
Ideally we'd run this whole thing through docker but we haven't got that working. So you will need:
tar -xf DejaVuSansMono.tar.xz -C /usr/local/share/fonts && \
rm DejaVuSansMono.tar.xz
```
## Creating a demo
Demos are found in `pkg/integration/tests/demo/`. They are like regular integration tests but have `IsDemo: true` which has a few effects:
* The bottom row of the UI is quieter so that we can render captions
* Fetch/Push/Pull have artificial latency to mimic a network request
* The loader at the bottom-right does not appear
In demos, we don't need to be as strict in our assertions as we are in tests. But it's still good to have some basic assertions so that if we automate the process of updating demos we'll know if one of them has broken.
You can use the same flow as we use with integration tests when you're writing a demo:
* Setup the repo
* Run the demo in sandbox mode to get a feel of what needs to happen
* Come back and write the code to make it happen
### Adding captions
It's good to add captions explaining what task if being performed. Use the existing demos as a guide.
### Setting up the assets worktree
We store assets (which includes demo recordings) in the `assets` branch, which is a branch that shares no history with the main branch and exists purely for storing assets. Storing them separately means we don't clog up the code branches with large binaries.
The scripts and demo definitions live in the code branches but the output lives in the assets branch so to be able to create a video from a demo you'll need to create a linked worktree for the assets branch which you can do with:
```sh
git worktree add .worktrees/assets assets
```
Outputs will be stored in `.worktrees/assets/demos/`. We'll store three separate things:
* the yaml of the recording
* the original gif
* either the compressed gif or the mp4 depending on the output you chose (see below)
### Recording the demo
Once you're happy with your demo you can record it using:
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
## Global
# Lazygit Keybindings
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## Global keybindings
<pre>
<kbd>m</kbd>: view merge/rebase options
<kbd>P</kbd>: push
<kbd>p</kbd>: pull
<kbd>R</kbd>: refresh
<kbd><c-r></kbd>: Switch to a recent repo
<kbd><pgup></kbd>: Scroll up main panel (fn+up/shift+k)
<kbd><pgdown></kbd>: Scroll down main panel (fn+down/shift+j)
<kbd>@</kbd>: Open command log menu
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
<kbd>:</kbd>: Execute custom command
<kbd><c-p></kbd>: View custom patch options
<kbd>m</kbd>: View merge/rebase options
<kbd>R</kbd>: Refresh
<kbd>+</kbd>: Next screen mode (normal/half/fullscreen)
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit キーバインド
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 키 바인딩
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## 글로벌 키 바인딩
<pre>
<kbd><c-r></kbd>: 최근에 사용한 저장소로 전환
<kbd><pgup></kbd>: 메인 패널을 위로 스크롤 (fn+up/shift+k)
<kbd><pgdown></kbd>: 메인 패널을 아래로로 스크롤 (fn+down/shift+j)
<kbd>@</kbd>: 명령어 로그 메뉴 열기
<kbd>}</kbd>: Diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
## Global
# Lazygit Sneltoetsen
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## Globale sneltoetsen
<pre>
<kbd>m</kbd>: bekijk merge/rebase opties
<kbd>P</kbd>: push
<kbd>p</kbd>: pull
<kbd>R</kbd>: verversen
<kbd><c-r></kbd>: Wissel naar een recente repo
<kbd><pgup></kbd>: Scroll naar beneden vanaf hoofdpaneel (fn+up/shift+k)
<kbd><pgdown></kbd>: Scroll naar beneden vanaf hoofdpaneel (fn+down/shift+j)
<kbd>@</kbd>: Open command log menu
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Keybindings
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## Globalne
<pre>
<kbd>m</kbd>: view merge/rebase options
<kbd>P</kbd>: push
<kbd>p</kbd>: pull
<kbd>R</kbd>: odśwież
<kbd><c-r></kbd>: Switch to a recent repo
<kbd><pgup></kbd>: Scroll up main panel (fn+up/shift+k)
<kbd><pgdown></kbd>: Scroll down main panel (fn+down/shift+j)
<kbd>@</kbd>: Open command log menu
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
<kbd>:</kbd>: Wykonaj własną komendę
<kbd><c-p></kbd>: View custom patch options
<kbd>m</kbd>: Widok scalenia/opcje zmiany bazy
<kbd>R</kbd>: Odśwież
<kbd>+</kbd>: Next screen mode (normal/half/fullscreen)
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Связки клавиш
_Связки клавиш_
## Глобальные сочетания клавиш
<pre>
<kbd><c-r></kbd>: Переключиться на последний репозиторий
<kbd><pgup></kbd>: Прокрутить вверх главную панель (fn+up/shift+k)
<kbd><pgdown></kbd>: Прокрутить вниз главную панель (fn+down/shift+j)
<kbd>@</kbd>: Открыть меню журнала команд
<kbd>}</kbd>: Увеличить размер контекста, отображаемого вокруг изменений в просмотрщике сравнении
<kbd>{</kbd>: Уменьшите размер контекста, отображаемого вокруг изменений в просмотрщике сравнении
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 按键绑定
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
// but in this case we don't need to edit it, so we'll just return
}else{
app.Log.Info("Lazygit demon did not match on any use cases")
}
returnnil
}
// Close closes any resources
func(app*App)Close()error{
for_,closer:=rangeapp.closers{
err:=closer.Close()
iferr!=nil{
iferr:=closer.Close();err!=nil{
returnerr
}
}
returnnil
}
// KnownError takes an error and tells us whether it's an error that we know about where we can print a nicely formatted version of it rather than panicking with a stack trace
func(app*App)KnownError(errerror)(string,bool){
errorMessage:=err.Error()
mappings:=[]errorMapping{
{
originalError:"fatal: not a git repository (or any of the parent directories): .git",
flaggy.String(&repoPath,"p","path","Path of git repo. (equivalent to --work-tree=<path> --git-dir=<path>/.git/)")
filterPath:=""
flaggy.String(&filterPath,"f","filter","Path to filter on in `git log -- <path>`. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted")
gitArg:=""
flaggy.AddPositionalValue(&gitArg,"git-arg",1,false,"Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed.")
printVersionInfo:=false
flaggy.Bool(&printVersionInfo,"v","version","Print the current version")
debug:=false
flaggy.Bool(&debug,"d","debug","Run in debug mode with logging (see --logs flag below). Use the LOG_LEVEL env var to set the log level (debug/info/warn/error)")
tailLogs:=false
flaggy.Bool(&tailLogs,"l","logs","Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)")
printDefaultConfig:=false
flaggy.Bool(&printDefaultConfig,"c","config","Print the default config")
printConfigDir:=false
flaggy.Bool(&printConfigDir,"cd","print-config-dir","Print the config directory")
useConfigDir:=""
flaggy.String(&useConfigDir,"ucd","use-config-dir","override default config directory with provided directory")
workTree:=""
flaggy.String(&workTree,"w","work-tree","equivalent of the --work-tree git argument")
gitDir:=""
flaggy.String(&gitDir,"g","git-dir","equivalent of the --git-dir git argument")
customConfigFile:=""
flaggy.String(&customConfigFile,"ucf","use-config-file","Comma separated list to custom config file(s)")
flaggy.Parse()
ifos.Getenv("DEBUG")=="TRUE"{
debug=true
}
return&cliArgs{
RepoPath:repoPath,
FilterPath:filterPath,
GitArg:gitArg,
PrintVersionInfo:printVersionInfo,
Debug:debug,
TailLogs:tailLogs,
PrintDefaultConfig:printDefaultConfig,
PrintConfigDir:printConfigDir,
UseConfigDir:useConfigDir,
WorkTree:workTree,
GitDir:gitDir,
CustomConfigFile:customConfigFile,
}
}
funcparseGitArg(gitArgstring)appTypes.GitArg{
typedArg:=appTypes.GitArg(gitArg)
// using switch so that linter catches when a new git arg value is defined but not handled here
// knownError takes an error and tells us whether it's an error that we know about where we can print a nicely formatted version of it rather than panicking with a stack trace
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v",err)
}
fmt.Printf("\nCheatsheets are out of date. Please run `%s` at the project root and commit the changes. If you run the script and no keybindings files are updated as a result, try rebasing onto master and trying again.\n",CommandToRun())
// the price of having a convenient interface where we can say .New(...).Run() is that our builder now depends on our runner, so when we want to wrap the default builder/runner in new functionality we need to jump through some hoops. We could avoid the use of a decorator function here by just exporting the runner field on the default builder but that would be misleading because we don't want anybody using that to run commands (i.e. we want there to be a single API used across the codebase)
// here we're wrapping the default command runner in some git-specific stuff e.g. retry logic if we get an error due to the presence of .git/index.lock
varcommitsOutput=strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|better typing for rebase mode
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|refactoring the config struct`,"|","\x00",-1)
varsingleCommitOutput=strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|better typing for rebase mode`,"|","\x00",-1)
funcTestGetCommits(t*testing.T){
typescenariostruct{
testNamestring
runner*oscommands.FakeCmdObjRunner
expectedCommits[]*models.Commit
expectedErrorerror
logOrderstring
rebaseModeenums.RebaseMode
optsGetCommitsOptions
mainBranches[]string
}
scenarios:=[]scenario{
{
testName:"should return no commits if there are none",
ExpectGitArgs([]string{"rev-parse","--symbolic-full-name","develop@{u}"},"",errors.New("error")).// this one doesn't, so it checks origin instead
ExpectGitArgs([]string{"rev-parse","--verify","--quiet","refs/remotes/origin/develop"},"",errors.New("error")).// doesn't exist there, either, so it checks for a local branch
ExpectGitArgs([]string{"rev-parse","--verify","--quiet","refs/heads/develop"},"",errors.New("error")).// no local branch either
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.