Compare commits

..
Author SHA1 Message Date
Jesse Duffield 017367f940 Show commit merged/push statuses against branches
When viewing branches in an enlargened window, we now colour the commit shas based on
whether the branch has been merged to a main branch and whether it's been pushed to
the upstream.

This isn't using the exact same code as we use for commits, so it's possible we'll get
a colour mismatch when the user checks out a branch and views the commits in the commits
view, but if we come across that situation we'll just need to fix it.
2023-10-11 15:51:58 +11:00
Jesse Duffield 16369dc3c2 Introduce ConcurrentMap util 2023-10-11 15:50:17 +11:00
Jesse Duffield 86bb73cff1 Better Explain the red/yellow/green commit colouring 2023-10-11 15:50:17 +11:00
Jesse Duffield bac12231c3 Don't bother checking for origin/main when loading commits
The real reason I'm doing this is to that it's easier to explain the logic behind setting
commit merged statuses. Now it's just 'try the upstream, fallback to the local branch'.

Other justifications:
* we should avoid assuming that 'origin' is the main upstream remote
* it's one less git call
* surely the main branch _will_ have an upsream. I doubt many people have benefitted
  from the extra fallback to origin
2023-10-11 15:50:17 +11:00
Stefan Haller 3691856021 Re-apply filter when model changes (#3058) 2023-10-10 08:43:48 +02:00
Stefan Haller b5ca6a3add When refreshing models, re-apply active filter for the corresponding view 2023-10-10 08:37:30 +02:00
Stefan Haller ecaa4846f1 Fix crash when trying to filter the list of remotes (#3059) 2023-10-10 08:36:47 +02:00
Stefan Haller 787f9966ec Fix crash when trying to filter the list of remotes
This seems to be a left-over from an earlier iteration of the code. Removing it
fixes the crash.
2023-10-10 08:33:18 +02:00
Stefan Haller 013cfc77a1 Show sync status in branches list (#3021) 2023-10-10 08:32:42 +02:00
Jesse Duffield 16f7b01fec Add disabled compat for user config (#2833) (#3060) 2023-10-10 16:49:31 +11:00
Karim KhaleelandJesse Duffield 421c6565f9 Update wording in disable keybindings test
Co-authored-by: Jesse Duffield <jessedduffield@gmail.com>
2023-10-10 08:38:15 +03:00
Karim Khaleel d02deeefd8 Add disabled compat for user config (#2833)
Treat <disabled> setting as equivalent to "null"
in keybindings user configs.
2023-10-09 22:34:50 +03:00
Stefan Haller 235f5bb221 Avoid rendering branches view twice when refreshing
refreshWorktrees re-renders the branches view, because the branches view shows
worktrees against branches. This means that when both BRANCHES and WORKTREES are
requested to be refreshed, the branches view would be rendered twice in short
succession. This causes an ugly visual glitch when force-pushing a branch,
because when pushing is done, we would see the ↑4↓9 status come back from under
the Pushing status for a brief moment, to be replaced with a green checkmark a
moment later.

Fix this by including the worktree refresh in the branches refresh when both are
requested. This means that the two are no longer running in parallel for an
async refresh, but hopefully that's not so bad.
2023-10-08 18:45:36 +02:00
Stefan Haller be3b4bd791 Remove sync mutex
I'm pretty convinced we don't need it. Git itself does a good job of making sure
that concurrent operations don't corrupt anything.
2023-10-08 18:45:36 +02:00
Stefan Haller 67d6447e12 Disallow pulling/pushing a branch while the branch is pushed or pulled 2023-10-08 18:45:36 +02:00
Stefan Haller fd9d7cb7bb Disallow checking out another branch while the current one is being pulled 2023-10-08 18:45:36 +02:00
Stefan Haller 3d6965ccbb Add inline status for pushing tags and deleting remote tags 2023-10-08 18:45:36 +02:00
Stefan Haller 707fa37160 Add inline status for pushing/pulling/fast-forwarding branches
When pulling/pushing/fast-forwarding a branch, show this state in the branches
list for that branch for as long as the operation takes, to make it easier to
see when it's done (without having to stare at the status bar in the lower
left).

This will hopefully help with making these operations feel more predictable, now
that we no longer show a loader panel for them.
2023-10-08 18:45:36 +02:00
Stefan Haller 7075b66bc6 Add WithInlineStatus helper function
Very similar to WithWaitingStatus, except that the status is shown in a view
next to the affected item, rather than in the status bar.

Not used by anything yet; again, committing separately to get smaller commits.
2023-10-08 18:45:36 +02:00
Stefan Haller 9d55d71fdd Add GetItemOperation/SetItemOperation/ClearItemOperation to IStateAccessor
Not used by anything yet; committing this separately in the interest of having
smaller independent commits.
2023-10-08 18:30:57 +02:00
Stefan Haller cc9a20c4ab Don't report errors from within a WithWaitingStatus
We can just return our error to WithWaitingStatus, it will take care of
reporting it.
2023-10-08 18:30:34 +02:00
README-bot c39fafe6ec Updated README.md 2023-10-04 23:33:09 +00:00
Jesse Duffield 2a11725749 Remove redundant len check (#3051) 2023-10-05 10:32:53 +11:00
Eng Zer Jun deed9eb18e Remove redundant len check
From the Go specification [1]:

  "3. If the map is nil, the number of iterations is 0."

`len` returns 0 if the map is nil [2]. Therefore, checking `len(v) > 0`
before a loop is unnecessary.

[1]: https://go.dev/ref/spec#For_range
[2]: https://pkg.go.dev/builtin#len

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2023-10-04 21:03:26 +08:00
README-bot a3759c65e4 Updated README.md 2023-10-03 19:20:39 +00:00
Stefan Haller 519760077b Add Micro editor preset (#3049) 2023-10-03 21:20:21 +02:00
Nikita Karamov 8390622f70 Add Micro editor preset 2023-10-03 19:42:28 +02:00
README-bot a04fbafcfd Updated README.md 2023-10-03 09:35:18 +00:00
Stefan Haller 4fe8dee40f Band-aid fix for submodule/reset.go test failure (#3047) 2023-10-03 11:34:59 +02:00
Stefan Haller 6fd80565c7 Band-aid fix for submodule/reset.go test failure
This is not a complete fix, but it's good enough to fix the spurious test
failures of submodule/reset.go. We have some vague hope to fix this in a more
sustainable way by somehow improving our concurrency model fundamentally, but
that's a more long-term undertaking, and it's annoying that this test fails so
often, so let's fix it in this way for now.
2023-10-03 09:36:46 +02:00
README-bot 3cda1d03d9 Updated README.md 2023-10-01 06:05:28 +00:00
Stefan Haller d4eb02fac0 Add ability to force portrait mode (#3037) 2023-10-01 08:05:13 +02:00
Louis DeLosSantos 9c72d8a2b0 Add ability to force portrait mode
A new gui config flag 'portraitMode':<string> is added to influence when
LazyGit stacks its UI components on top of one another.

The accepted values are 'auto', 'always', 'never'.

'auto': enter portrait mode when terminal becomes narrow enough

'always': always use portrait mode unconditional of the terminal
dimensions

'never': never use portraid mode

Signed-off-by: Louis DeLosSantos <louis.delos@gmail.com>
2023-09-30 20:57:38 +02:00
Jesse Duffield 4f3127ccb8 Update PR template to use go generate command (#3041) 2023-09-30 20:06:58 +10:00
Jesse Duffield 877c27722f add gofumpt to workspace settings 2023-09-30 19:46:03 +10:00
Jesse Duffield 0dda8b58d1 Update PR template to use go generate command 2023-09-30 19:45:55 +10:00
Jesse Duffield e997d1ae5d Add comments in user config struct (#3040) 2023-09-30 19:39:42 +10:00
Jesse Duffield 3e2ef84d56 Ignore deprecation checks in linter
This is pretty funny: the staticcheck linter gets mad if we use a field which is marked
in a comment as being deprecated. But it tripped on my own comment saying that a field
is deprecated in terms of the user config!

Obviously we have to make use of this field, otherwise we would just remove it entirely
rather than mark it as deprecated, so I'm silencing this lint.

I doubt this lint would actually come in handy in other cases (like when using a third
party package) and worst case scenario we just end up fixing the problem when we
try to upgrade the package and the deprecated field is now gone).
2023-09-30 19:24:59 +10:00
Jesse Duffield b2eccd6fe8 Add comments in user config struct
We're going to make this user config struct a more authoritative source of truth.

Some of these fields weren't actually explained anywhere so I've added explanations.

In places where a lot of explanation is required I've linked to existing explanations in other docs.
2023-09-30 19:23:52 +10:00
Stefan Haller cc25a3b8c5 Change Makefile to build non-optimized (#3028) 2023-09-30 10:59:22 +02:00
Stefan Haller e93d945dba Change Makefile to build non-optimized 2023-09-30 10:50:32 +02:00
README-bot 246e08096f Updated README.md 2023-09-30 08:46:30 +00:00
Stefan Haller 860a52a736 Use go:generate for cheatsheet (#3035) 2023-09-30 10:46:16 +02:00
Stefan Haller 18d8b29461 Make test_list_generator.go print what it's doing
It's confusing if the cheatsheet generator prints output but this one doesn't.
2023-09-29 20:40:15 +02:00
Stefan Haller 7af371701d Use go:generate for generating cheatsheets
This has several benefits:
- it's less code
- we're using the same mechanism to generate all our auto-generated files, so if
  someone wants to add a new one, it's clear which pattern to follow
- we can re-generate all generated files with a single command
  ("go generate ./...", or "make generate")
- we only need a single check on CI to check that all files are up to date (see
  previous commit)
2023-09-29 20:38:29 +02:00
Stefan Haller 5ccc95b76f Generalize the CI check for the test list to all auto-generated files
At the moment, test_list.go is the only file that we generate using go:generate.
We will add another one in the next commit though, and we might add even more in
the future; it's useful to have a single check on CI that checks them all.
2023-09-29 20:20:09 +02:00
README-bot cdb1d76484 Updated README.md 2023-09-28 08:04:37 +00:00
Stefan Haller 9e423e949c Improve debugging of integration tests (#3029)
Several improvements to make debugging integration tests work better.
2023-09-28 10:04:23 +02:00
Stefan Haller e1ceb6892a Disable deadlock reporting when debugging an integration test 2023-09-28 10:03:53 +02:00
Stefan Haller 40b8557608 Disable the 40-second timeout for integration tests when debugging
Pausing at breakpoints and stepping through code can often take longer than 40s,
so the timeout is annoying when debugging.
2023-09-28 10:03:53 +02:00
Stefan Haller 92e107f52d Use constant for WAIT_FOR_DEBUGGER env var 2023-09-28 10:03:53 +02:00
Stefan Haller 2f6a87df98 Build lazygit without optimizations and inlining when debugging
This makes the debugging experience better.
2023-09-28 10:03:53 +02:00
Jesse Duffield 2c29577e3c Respect $GIT_WORK_TREE and $GIT_DIR env vars (fix #3010). (#3024) 2023-09-26 23:52:19 +10:00
BZ e72559bb27 respect and env vars 2023-09-25 12:20:12 +02:00
Stefan Haller 6da1cf87b1 Support passing -race flag to integration tests (#3019) 2023-09-25 09:14:38 +02:00
Stefan Haller 508b869773 Pass MAKECMDGOALS to make integration-test-tui
We need this to be able to pass the "-race" argument, i.e.

  make integration-test-tui -- -race
2023-09-25 09:09:41 +02:00
Stefan Haller 59cc6843e6 Print race detector logs after running a test with -race 2023-09-25 09:09:41 +02:00
Stefan Haller f108fd2236 Support -race arg when running integration tests to turn on go's race detector
For the "cli" and "tui" modes of the test runner there's a "-race" parameter to
turn it on; for running tests on CI with go test, you turn it on by setting the
environment variable LAZYGIT_RACE_DETECTOR to a non-empty value.
2023-09-25 09:09:41 +02:00
Stefan Haller 8081b59a02 Allow passing multiple flags to the cli runner
This is useful for example to pass both -slow and -debug. Since we're about to
add yet another flag in the next commit, it becomes even more important. Plus,
it makes the code a little nicer too.
2023-09-25 09:09:41 +02:00
Stefan Haller 26d180a50a Fix minor resource leak in runCmdHeadless
We still want to close the pty if the command failed.
2023-09-25 09:09:41 +02:00
Jesse Duffield 10fe872c71 Fix issue where active search inappropriately changed selected line (#3022) 2023-09-25 16:43:47 +10:00
Jesse Duffield c74448f00d Don't select current search result when showing search status
Previously there was no way to render a view's search status without also moving the cursor
to the current search match. This caused issues where we wanted to display the status
after leaving the view and coming back, or when beginning a new search from within the
view.

This commit separates the two use cases so we only move the cursor when we're actually
selecting the next search match
2023-09-25 16:37:59 +10:00
Jesse Duffield 41ab7c44a0 Use upstream branch when opening pull requests (#2693)
- **PR Description**

Should probably solve #2691

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go run scripts/cheatsheet/main.go
generate`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [x] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [x] Docs (specifically `docs/Config.md`) have been updated if
necessary
* [x] You've read through your own file changes for silly mistakes etc
2023-09-25 11:03:15 +10:00
README-bot 3589a43682 Updated README.md 2023-09-25 00:24:22 +00:00
Jesse Duffield ad70341139 Add menu to rebase onto selected branch remote upstream (#3020) 2023-09-25 10:24:06 +10:00
AzraelSec ea93df571d feat: add a menu to rebase current branch to a target branch upstream 2023-09-24 20:13:59 +02:00
AzraelSec bc21921001 chore: rename branch upstream view options method 2023-09-22 12:19:38 +02:00
Stefan Haller 07cbb62510 Replace loader panels with waiting status (pull/push/fetch) (#2973) 2023-09-20 14:07:25 +02:00
Stefan Haller fc868cdda5 Shorten the waiting status for fast-forwarding a branch
Now that we no longer show it in a loader panel, but in the app status view,
it's awkwardly long (the loading animation is much further to the right than for
other waiting status texts). Hopefully seeing just "Fast-forwarding <branch>" is
enough to be able to tell what's happening.
2023-09-20 13:56:06 +02:00
Stefan Haller 864a9ada57 Remove unused WithLoaderPanel code 2023-09-20 13:30:49 +02:00
Stefan Haller f3e9d50d94 Use WithWaitingStatus instead of WithLoaderPanel for pull/push/fetch 2023-09-20 13:30:49 +02:00
Stefan Haller cdad0b998e Add constant for LoaderAnimationInterval
Since Loader and renderAppStatus need to agree on it, it helps for it to be a
constant in case we want to change it.
2023-09-20 13:30:49 +02:00
Stefan Haller 64012d67a9 Remove unused class FakePopupHandler 2023-09-20 13:30:49 +02:00
Stefan Haller 33d9d4dbf1 Hide waiting status during credentials prompt (#3016) 2023-09-20 13:30:15 +02:00
Stefan Haller 4c5e250ed8 Add integration test for deleting a remote branch with credentials prompt
This test is interesting because it shows a credentials prompt inside a
WithWaitingStatus. Prior to this branch this test would have hung forever.
2023-09-20 13:29:30 +02:00
Stefan Haller 1359fa14c1 When pausing a task during a waiting status, hide the status while paused
We do this for two reasons:
- when popping up a credentials prompt, it looks distracting if the waiting
  status keeps spinning while the user is typing the password
- the task that updates the waiting status periodically would keep the program
  busy, so integration tests would wait forever for the program to become idle
  again
2023-09-20 13:29:30 +02:00
Stefan Haller c222a618a8 Extract WaitingStatusHandle 2023-09-20 13:29:11 +02:00
Stefan Haller 05c32e292e Extract StatusManager.addStatus method
Avoids a bit of code duplication.
2023-09-20 11:27:50 +02:00
README-bot fb4f742416 Updated README.md 2023-09-20 06:10:06 +00:00
Jesse Duffield 276438b601 Add history for search view (#2877) 2023-09-20 16:09:53 +10:00
Karim Khaleel edec116ceb Add search history
Add search history for filterable and searchable views.
2023-09-20 08:35:41 +03:00
Stefan Haller 1aae1772ce Allow cherry-picking commits during a rebase (#3013) 2023-09-19 07:15:10 +02:00
Stefan Haller a642395e9f Allow cherry-picking commits during a rebase
This can be useful when you know that a cherry-picked commit would conflict at
the tip of your branch, but doesn't at the beginning of the branch (or
somewhere in the middle). In that case you want to be able to edit the commit
before where you want to insert the cherry-picked commits, and then paste to
insert them into the todo list at that point.
2023-09-18 10:50:19 +02:00
Stefan Haller 70bfeddc90 Add StatusCommands.IsInNormalRebase and IsInInteractiveRebase
... and implement RebaseMode in terms of these.
2023-09-18 10:50:19 +02:00
Stefan Haller e2a966443b Add a DisabledReason mechanism for menu items and keybindings (#2992) 2023-09-18 10:26:11 +02:00
Stefan Haller 0b13c3ca87 Disabled paste when there are no copied commits 2023-09-18 10:20:23 +02:00
Stefan Haller 8b6766de79 Use DisabledReason for commits panel commands 2023-09-18 10:20:23 +02:00
Stefan Haller c9371f812f Use DisabledReason for rebasing a branch onto itself 2023-09-18 10:20:23 +02:00
Stefan Haller e592d81b60 Add Enabled func to Binding 2023-09-18 10:20:23 +02:00
Stefan Haller f2f50ccf75 Use DisabledReason for upstream options items 2023-09-18 10:15:11 +02:00
Stefan Haller 75aed98c35 Use DisabledReason when deleting branches is not possible
Two cases: trying to delete the currently checked-out branch, or deleting the
upstream of a branch that doesn't have one.
2023-09-18 10:15:11 +02:00
Stefan Haller 7f9818cfa2 Add DisabledReason field to MenuItem
This is useful to disable items that are not applicable right now because of
some condition (e.g. the "delete branch" menu item when the currently
checked-out branch is selected).

When a DisabledReason is set on a menu item, we
- show it in a tooltip (below the regular tooltip of the item, if it has one)
- strike through the item's key, if it has one
- show an error message with the DisabledReason if the user tries to invoke the
  command
2023-09-18 10:15:11 +02:00
README-bot 679148449a Updated README.md 2023-09-18 08:14:59 +00:00
Stefan Haller bc00aeb10d Rename test/results to test/_results (#3012) 2023-09-18 10:14:43 +02:00
Luka Markušić 4d258bd981 Use UpstreamBranch for opening pull requests 2023-09-18 13:40:52 +10:00
Stefan Haller c465b0f2ff Rename test/results to test/_results
This prevents commands like "go test ./..." from looking into it, and it
prevents VS Code's Problems panel from showing errors about the go files in that
folder.
2023-09-15 18:04:20 +02:00
README-bot 25160b671e Updated README.md 2023-09-13 07:17:12 +00:00
Jesse Duffield 20bfa42792 Use Error method to handle commits url copy from unknown service (#3007) 2023-09-13 17:16:53 +10:00
AzraelSec 5a740e34c7 fix: use Error method to handle the commit url copy from unknown service 2023-09-12 19:06:08 +02:00
Stefan Haller 0aad599a9b Various debugging improvements (#3000) 2023-09-11 08:27:03 +02:00
Stefan Haller b6c892a08a Provide a simple way to debug an integration test 2023-09-11 08:17:58 +02:00
Stefan Haller 28d12e4e5d Add debug configuration to attach to a running lazygit process
I often find it more convenient to start a lazygit process in a terminal window
and then attach to it, rather than have VS Code launch one for me.

Note that this doesn't work with "go run main.go". It does work with "make run",
however.

Make sure there's only one lazygit process running, otherwise VS Code will open
a chooser with all the running processes to pick one from, but it's pretty much
impossible to tell which is which.
2023-09-10 11:47:25 +02:00
Stefan Haller 5d5e24a48e Change "make run" to do a build and then launch lazygit
As far as I can tell, there's not much of a difference in behavior between the
two. The advantage of doing it this way is that you can attach a debugger to the
running lazygit process; see next commit.
2023-09-10 11:47:25 +02:00
Stefan Haller de598e55a6 Hide system goroutines in callstack
I have never found a reason to see them, and they just pollute the stack window
unnecessarily.
2023-09-10 11:47:25 +02:00
Stefan Haller 0fd8392067 Format launch.json with Prettier 2023-09-10 11:47:25 +02:00
Jesse Duffield 67ac2c5d9b Change the default of the "gui.borders" config to "rounded" (#2998) 2023-09-10 12:23:25 +10:00
Jesse Duffield 7626fd7df3 Add co-author to commits (#2912) 2023-09-10 11:38:16 +10:00
Orlando Maussa db409fa69f Add coauthor (#2)
Add co-author to commits

Add addCoAuthor command for commits

- Implement the `addCoAuthor` command to add co-authors to commits.
- Utilize suggestions helpers to populate author names from the suggestions list.
- Added command to gui at `LocalCommitsController`.

This commit introduces the `addCoAuthor` command, which allows users to easily add co-authors to their commits. The co-author names are populated from the suggestions list, minimizing the chances of user input errors. The co-authors are added using the Co-authored-by metadata format recognized by GitHub and GitLab.
2023-09-09 07:18:47 -05:00
Stefan Haller 5b8a8d356c Change the default of the "gui.borders" config to "rounded"
Most people seem to agree that it looks better than the sharp edges of "single".
2023-09-09 10:42:24 +02:00
Stefan Haller d7e2ca3f10 Add jump-to-panel label config setting (#2993) 2023-09-09 09:55:52 +02:00
Maria José Solano 387fbf6ab6 feat: add jump-to-panel label setting 2023-09-09 09:45:08 +02:00
Stefan Haller 917eb88617 Bump gocui 2023-09-09 09:44:50 +02:00
README-bot 005827395d Updated README.md 2023-09-09 07:35:14 +00:00
Jesse Duffield 7f9fa64074 Replace whitespace with '-' when renaming a branch (#2990) 2023-09-09 17:34:57 +10:00
README-bot ab06a1c85c Updated README.md 2023-09-06 06:45:15 +00:00
Stefan Haller 37048911ca Support to reset the current branch to a selected branch upstream (#2940) 2023-09-06 08:45:00 +02:00
AzraelSec 2b7b6f71ee feat: add a menu to reset current branch to a target branch upstream 2023-09-06 08:40:07 +02:00
AzraelSec 47d422bb8a chore: rename "Set/Unset upstream" menu to "Upstream Options"
This should already have been done when adding the "View divergence from
upstream" command, but now we're going to add yet another item to the menu that
is unrelated to setting or unsetting the upstream.
2023-09-06 00:23:35 +02:00
Cal Courtney c3ca77d6bf Replace whitespace with '-' when renaming a branch 2023-09-05 14:57:18 +01:00
README-bot a3cd1e93be Updated README.md 2023-09-05 12:01:12 +00:00
Stefan Haller 4cf8d81155 Save diff context size in state.yml instead of config.yml (#2969) 2023-09-05 14:00:55 +02:00
Stefan Haller 7774fe0ab3 Move diff context size from UserConfig to AppState 2023-09-05 13:55:30 +02:00
Stefan Haller f7db17f7d0 Load defaults for AppState before reading from yaml
So far this hasn't been necessary because all defaults were zero values. We're
about to add the first non-zero value though, and it's important that it is
initialized correctly for users who have a state.yml that doesn't have it yet.
2023-09-04 17:50:49 +02:00
Stefan Haller 1106981827 Extract a SaveAppStateAndLogError function
It seems that most actions that change a state option and resave the state want
to just log the error.
2023-09-04 17:50:49 +02:00
Stefan Haller 1dac4158e9 Don't pass ignoreWhitespace to git commands
Now that AppState is available via common.Common, they can take it from there.
2023-09-04 17:50:49 +02:00
Stefan Haller 18c5780485 Add AppState to common.Common 2023-09-04 17:48:39 +02:00
README-bot e895052437 Updated README.md 2023-09-04 14:58:07 +00:00
Stefan Haller ade8d78c63 Add support for external diff commands (e.g. difftastic) (#2868) 2023-09-04 16:57:49 +02:00
Stefan Haller 2e74b7177e Fix keybinding for editing config file 2023-09-04 16:52:30 +02:00
Stefan Haller 6266e19623 Add support for external diff commands (e.g. difftastic) 2023-09-04 16:52:30 +02:00
Stefan Haller c67aa49856 Add explicit --no-ext-diff arg to CommitCommands.ShowCmdObj
We do this for ShowFileDiffCmdObj and WorktreeFileDiffCmdObj too, so why not
here.
2023-09-04 13:16:00 +02:00
README-bot 3a54089859 Updated README.md 2023-09-04 07:45:52 +00:00
Stefan Haller e2c447b8d0 Improve prompts when amending commits (#2980) 2023-09-04 09:45:34 +02:00
Stefan Haller 843e12286f Improve prompts when amending commits
This fixes two minor problems with the prompts:

1. When pressing shift-A in the local commits view, it would first prompt
   whether to stage all files, and then it would prompt whether to amend the
   commit at all. This doesn't make sense, it needs to be the other way round.

2. When pressing shift-A on the head commit in an interactive rebase, we would
   ask whether they want to amend the last commit, like when pressing shift-A in
   the files view. While this is technically correct, the fact that we're
   amending the head commit in this case is just an implementation detail, and
   from the user's point of view it's better to use the same prompt as we do for
   any other commit.

To fix these, we remove the confirmation panel from AmendHelper.AmendHead() and
instead add it at the two call sites, so that we have more control over this.
2023-09-01 18:55:16 +02:00
README-bot e60936e964 Updated README.md 2023-08-31 14:13:26 +00:00
Stefan Haller b9cfd89b80 Fix escape not cancelling filter mode, but closing the menu instead (#2977) 2023-08-31 16:13:06 +02:00
Stefan Haller de4224bbe4 Fix escape not cancelling filter mode, but closing the menu instead
When filtering is on in a menu, pressing esc should only cancel the filter, but
not close the menu.
2023-08-30 22:37:13 +02:00
README-bot ce91de76e4 Updated README.md 2023-08-30 09:22:09 +00:00
Stefan Haller acfce07aa0 Don't show toasts when running integration tests (#2975) 2023-08-30 11:21:52 +02:00
Stefan Haller 81216189e4 Don't show toasts when running integration tests
It's pointless because you can't check for them in tests anyway, so they
unnecessarily slow down the test run by two seconds for no reason.
2023-08-30 10:54:32 +02:00
Stefan Haller 2b26b380b6 Check for staged files for "Amend commit" and "Create fixup commit" (#2970) 2023-08-29 09:23:45 +02:00
Stefan Haller 1ba8d3060b Ensure committable files for "amend to" and "create fixup commit"
These would previously fail with confusing error messages when no files were
staged.

The diff is best viewed with "ignore whitespace" turned on.
2023-08-29 09:10:59 +02:00
Stefan Haller f561007fb7 Extract a WithEnsureCommitableFiles function
This encapsulates the logic to make sure we have something to commit; which is
to
- auto-stage all files if no files are staged and the SkipNoStagedFilesWarning
config is on
- otherwise, prompt the user whether they want to stage all files
- error out if we don't have any files at all

Of these, the first one was only done when committing with the built-in commit
message panel; there's no reason why it shouldn't also be done when committing
with the editor, or when amending, and now it is.
2023-08-29 09:10:59 +02:00
Stefan Haller a63d5891e2 Add command to show divergence from upstream (#2871) 2023-08-29 08:22:44 +02:00
Stefan Haller df38e954f4 Add integration test for the new divergence log 2023-08-29 08:16:40 +02:00
Stefan Haller ea532b4729 Add DoesNotContainAnyOf matcher 2023-08-29 08:16:40 +02:00
Stefan Haller 572d1b5920 Add "Show divergence from upstream" entry to Upstream menu in branches panel 2023-08-29 08:16:40 +02:00
Stefan Haller e8fac6ca73 Extract a SubCommitsHelper from SwitchToSubCommitsController
We want to use it from BranchesController too.
2023-08-29 08:16:40 +02:00
Stefan Haller 4de3fadb00 Remove sub_commits_context's Title method
It implemented this because it wants to do custom truncation of the ref name;
however, we can achieve the same thing by passing the truncated ref name to our
DynamicTitleBuilder, which was previously unused.
2023-08-29 08:16:40 +02:00
Stefan Haller 12f24aa8b4 Add option RefToShowDivergenceFrom to GetCommitsOptions
Not used yet.
2023-08-29 08:16:40 +02:00
Stefan Haller 911aa7774b Don't return commits from setCommitMergedStatuses
Since the slice stores pointers to objects, and we're only modifying the objects
but not the slice itself, there's no need to return it and assign it back. This
will allow us to call the function for subslices of commits.

Also, move the condition that checks for an empty string inside the function;
we're going to call it from more than one place, so this makes it easier.
2023-08-29 08:16:40 +02:00
Stefan Haller 1fb0e1e151 Section headers in keybindings menu (#2911) 2023-08-29 08:14:30 +02:00
Stefan Haller 94bf279b5a Remove the magenta color on menu items that open a menu
It's distracting. But keep the ellipsis.
2023-08-29 08:04:47 +02:00
Stefan Haller 37381b610d Add sections (local, global) to the keybindings menu 2023-08-29 08:04:47 +02:00
Stefan Haller 6d4df57393 Add option to add sections to menus 2023-08-29 08:04:47 +02:00
Stefan Haller 3df01aaff0 Add a mechanism to insert non-model items into list contexts
Not used by anything yet.
2023-08-29 08:04:47 +02:00
Stefan Haller 4ee4f6f34b Make columnPositions include entries for removed columns
We will pass these positions back to clients for rendering non-model items, and
it's important that clients can consistently rely on them no matter which
columns were removed.
2023-08-28 14:21:06 +02:00
Stefan Haller 7953f7fa86 Make RenderDisplayStrings return the column positions
Not used by anything yet, but we'll need it later in this branch.
2023-08-28 14:21:06 +02:00
Stefan Haller 7a8df7795c Take removed columns into account when applying column alignments 2023-08-28 14:21:06 +02:00
Stefan Haller aa493d3a9e Add failing test demonstrating bug with column alignments and removed columns
When columns to the left of a column with an alignment are removed, the
alignment applies to the wrong column. We'll fix this in the next commit.
2023-08-28 14:21:06 +02:00
Stefan Haller 72731f2c16 Change RenderDisplayStrings to return a slice of strings
We'll join them with newlines afterwards. This will make it easier to insert
other (non-model) items.
2023-08-28 14:21:06 +02:00
Stefan Haller f680b6e82e Cleanup: use slices.Delete to delete elements from a slice
I find this much easier to read.
2023-08-28 14:21:06 +02:00
Stefan Haller ec22570eac Add tests for renderLines
These are very simple yet, but we'll extend them in the next commits.
2023-08-28 14:21:06 +02:00
Stefan Haller 198ead7c14 Extract a ListRenderer struct
I'm doing this not so much because it's a great abstraction, but just because it
will make it much easier to write tests for it.
2023-08-28 14:21:06 +02:00
Stefan Haller 297a020abf Call getDisplayStrings with a valid range of model indices
It's nicer if clients can rely on the indices being valid, and don't have to
clamp themselves.
2023-08-28 14:21:06 +02:00
Stefan Haller 473d989cde Extract a renderLines function
We'll make some changes to how the display strings are rendered, so it helps to
have this code only once. This also fixes the problem that contexts using
refreshViewportOnChange weren't able to use column alignments so far. We didn't
need this yet, but it's just nice if everything works. :)
2023-08-28 14:21:06 +02:00
Stefan Haller 061bfce835 Change length parameter of getDisplayStrings to endIdx
It's more natural to work with this way, as we will see later in this branch.
2023-08-28 14:21:06 +02:00
Jesse Duffield 996e30e5d9 Add icons for files with .mdx and .svelte file extensions (#2889) 2023-08-28 20:25:03 +10:00
Jesse Duffield ecd2a14a15 Add install instructions for openSUSE (#2727) 2023-08-28 20:10:49 +10:00
README-bot 8dfa8dc48c Updated README.md 2023-08-28 09:52:42 +00:00
Jesse Duffield f7c183a429 Add instruction in PR template to start PRs with an imperative (#2967) 2023-08-28 19:52:25 +10:00
Jesse Duffield 473f86fcaa Add instruction in PR template to start PRs with an imperative
This spares me effort when it comes to making release notes.

Yes, sometimes it may be easier to start a message without an imperative e.g. 'When X happens, do Y'
but I don't want to overwhelm the contributor with details.
2023-08-28 19:46:45 +10:00
Jesse Duffield 28353da61b Allow adding a port to webDomain part of services config (#2908) 2023-08-28 19:38:45 +10:00
Jesse Duffield efd49f07fd Support custom keybindings for confirm discard (#2960) 2023-08-28 19:36:39 +10:00
Jesse Duffield b8e183acc4 Add installation guide using winget CLI (#2963) 2023-08-28 14:58:56 +10:00
Erick Garcia Godoy 821c4b05fe Add Winget to installation guide 2023-08-27 05:50:12 -03:00
Erick Garcia Godoy 4272d84fa2 Add Winget to installation guide 2023-08-27 05:45:43 -03:00
Mark Skelton e95505453e Update docs 2023-08-25 08:52:52 -05:00
Mark Skelton 6dc34d19ea Support custom keybindings for confirm discard 2023-08-25 08:50:05 -05:00
Stefan Haller a670fbb33c Select same commit again after pressing "e" to edit a commit (#2954) 2023-08-24 10:48:07 +02:00
Stefan Haller 98e6c119f5 Select same commit again after pressing "e" to edit a commit
When editing a commit, the index of the current commit can change; for example,
when merge commits are involved, or when working with stacked branches where
"update-ref" commands may be added above the selected commit.

Reselect the current commit after pressing "e"; this requires doing the refresh
blocking on the main thread. (Another option that I considered was to use a
SYNC refresh, and then select the new line with an OnUIThread inside the Then
function. This also works, but results in a very visible lag.)
2023-08-22 14:08:12 +02:00
Stefan Haller c718a73d0a Call Then function only after everything is done
I'm actually surprised how this could even have worked before.
2023-08-22 14:06:31 +02:00
Stefan Haller d74c817fd8 Panic when trying to use RefreshOptions.Then with mode ASYNC
This doesn't work, and since it took me a while of debugging to figure this out,
alert other developers earlier when they try to do it.
2023-08-22 14:06:31 +02:00
Stefan Haller 93d19db158 Add assertion to show the problem 2023-08-22 14:06:29 +02:00
Raido Oras d7b611aa05 Allow port in webDomain for services config values 2023-08-21 14:33:58 +03:00
Jesse Duffield fc6008fdff fix GitHub Actions warnings (#2950) 2023-08-21 19:27:26 +10:00
kyu08 a65d3119c0 upgrade golangci/golangci-lint-action to v3.7.0 2023-08-21 18:11:33 +09:00
kyu08 51ecf4089b upgrade actions/setup-go to v4 and remove actions/cache for go cache 2023-08-21 18:10:52 +09:00
kyu08 0c0fe8997b upgrade goreleaser/goreleaser-action to v4 2023-08-21 18:09:59 +09:00
kyu08 f86309dd03 upgrade JamesIves/github-sponsors-readme-action to v1.2.2 2023-08-21 18:09:33 +09:00
Stefan Haller c31fcb7134 Switch to editor from commit message panel (#2881) 2023-08-21 10:12:26 +02:00
Stefan Haller 91ec42f3f8 Add integration test 2023-08-21 10:03:34 +02:00
Stefan Haller 8f628296ad Mention ctrl+o binding in commit message sub title
Only do this when an onSwitchToEditor function is actually provided. For the
"Move patch into new commit" command we don't, because it isn't totally
straightforward in that case.
2023-08-21 10:03:34 +02:00
Stefan Haller 61bd3e8dd2 Add key binding for switching from the commit message panel to an editor
This is useful for when you begin to type the message in lazygit's commit panel,
and then realize that you'd rather use your editor's more powerful editing
capabilities. Pressing <c-o> will take you right there.
2023-08-21 10:03:34 +02:00
Stefan Haller 7263630967 Remove obsolete comment 2023-08-21 10:03:34 +02:00
Jesse Duffield 16711c6f1a Added termux installation [README.md] (#2892) 2023-08-21 18:02:59 +10:00
Jesse Duffield b2023f10b6 Fix: Update 'zh' to 'zh-CN' in lazygit Language Configuration (#2842) 2023-08-21 17:56:45 +10:00
Stefan Haller e915488a83 Add gui.scrollOffBehavior config for scrolling list views by half-pages (#2939) 2023-08-21 09:11:03 +02:00
Stefan Haller b2d629b50a Add scrollOffEnabled config 2023-08-21 09:03:45 +02:00
Stefan Haller 125d4fa9dc Pass UserConfig to checkScrollUp/Down instead of just the scrollOffMargin
This will allow us to add a scrollOffEnabled config and have the functions
respect it without changes to clients.
2023-08-21 08:10:28 +02:00
Stefan Haller 527a1596f3 Add tests for scroll-off margin of zero 2023-08-21 08:10:28 +02:00
Stefan Haller 51d9f70f9e Fix section levels
These appeared as subsections of "Platform Defaults", which doesn't make sense.
2023-08-21 08:10:28 +02:00
Jesse Duffield 83727d48b5 Feature add git flow instructions (#2785) 2023-08-21 15:51:51 +10:00
Bart 8efc175340 Add git flow description in readme 2023-08-21 15:50:54 +10:00
Jesse Duffield 6a6cb25d7e Handle trailing slash in worktree path (#2947) 2023-08-21 13:26:58 +10:00
README-bot fd782ff568 Updated README.md 2023-08-21 01:26:29 +00:00
Jesse Duffield dbfb469bad Add Makefile (#2937) 2023-08-21 11:26:15 +10:00
Stefan Haller 888a976fc0 Fix the commit graph display after selection jumps in commits view (#2943) 2023-08-20 08:51:29 +02:00
Stefan Haller 2073730186 Fix the commit graph display after selection jumps in commits view
When navigating in the commits view to a line that is out of view (e.g. by
pressing , or . to scroll by page, or < or > to scroll to the top or bottom),
the commit graph was not correctly highlighted. Fix this by rerendering the
viewport in this case.
2023-08-20 08:46:04 +02:00
Cristian Betivu 7a4a0c85c4 Fix test 2023-08-19 19:12:36 +03:00
Cristian Betivu 382ecb6bfe Add unit test 2023-08-19 19:10:35 +03:00
Cristian Betivu 03694f7502 Fix arg order to asserts 2023-08-19 19:10:25 +03:00
Cristian Betivu ee308a4994 Clean before convertion? 2023-08-19 18:36:57 +03:00
Cristian Betivu a2b2336173 Stylistic changes 2023-08-19 18:36:57 +03:00
Cristian Betivu d7b1deb465 Clean path 2023-08-19 18:36:57 +03:00
Cristian Betivu dd01639f57 Improve error message 2023-08-19 18:36:57 +03:00
Stefan Haller 525932fbf2 Fix sha colors when rebasing (#2946) 2023-08-19 12:38:00 +02:00
Stefan Haller b1314349d7 Fix yellow/red coloring while rebasing
It determines the yellow/red status by getting the merge-base between the
current branch and its upstream; while we're rebasing, the current branch is
HEAD, so it tried to get the merge-base between HEAD and HEAD{u}, which doesn't
work. Fix this by passing the name of the checked-out branch separately.
2023-08-19 09:26:27 +02:00
Stefan Haller 9671f549a1 Fix the blue sha color of todo commits while rebasing
This broke with 5d8a85f7e7.
2023-08-19 09:24:00 +02:00
kyu08 a1738a77ae Add Makefile 2023-08-19 00:13:03 +09:00
Jesse Duffield 689deb72bd Add emacs-keybinds for word navigation (#2935) 2023-08-17 09:41:14 +10:00
Ching Pei Yang 84372cfad9 Add emacs-keybinds for word navigation 2023-08-15 12:22:17 +02:00
README-bot e429415ed4 Updated README.md 2023-08-15 09:49:34 +00:00
Stefan Haller 7402be98b6 Jump to middle of the view when selection leaves the visible area (#2915) 2023-08-15 11:49:20 +02:00
Stefan Haller 341b9725d4 Add ScrollOffMargin user config
When set to a non-zero value, views will scroll when the selection gets this
close to the top or bottom of the view.
2023-08-15 11:40:40 +02:00
Stefan Haller 8f164f7bc5 Stop cycling hunks when reaching the end
Previously, when pressing right-arrow when the cursor is already in the last
hunk, it would jump back to the beginning of that hunk. This can be confusing if
the hunk is long, maybe the start of the hunk is already scrolled off the top of
the window, and then pressing right-arrow actually scrolls *backwards*, which is
counter-intuitive. It's better to do nothing in this case.

Same for left-arrow when the cursor is already in the first hunk, although here
the problem is not so severe (unless diff context was increased by a huge
amount, and the start of the first hunk is scrolled off the bottom of the
window).
2023-08-15 11:40:40 +02:00
Stefan Haller 79c11a0458 If selected line is outside, move it to the middle of the view
Previously, the current line was only moved as much as necessary so that it's in
view again. This had the problem that when jumping downwards from hunk to hunk
with the right-arrow key, only the first line of the new hunk was shown at the
bottom of the window. I prefer to put the selected line in the middle of the
view in this case, so that I can see more of the newly selected hunk.

This has the consequence that when scrolling through the view line by line using
down-arrow, the view jumps by half a screen whenever I reach the bottom. I can
see how some users might be opposed to this change, but I happen to like it too,
because it allows me to see more context of what's ahead.
2023-08-15 11:40:40 +02:00
Stefan Haller 4a4afc4639 Fix typo in comment 2023-08-15 11:40:40 +02:00
Stefan Haller ebdfd8046a Bump gocui 2023-08-15 11:40:40 +02:00
Jesse Duffield d99e983236 Add compare-commits demo (#2929) 2023-08-12 17:32:15 +10:00
Jesse Duffield 1c6cfafba7 Add features to table of contents 2023-08-12 17:27:25 +10:00
Jesse Duffield 83d642b74f Add demo for diffing two commits 2023-08-12 17:24:05 +10:00
Jesse Duffield b1bc437d1b Factor out common config setup functions in demo package 2023-08-12 16:52:40 +10:00
Jesse Duffield 26989ce0ee Show commit mark before showing extra info (#2928) 2023-08-12 16:44:27 +10:00
Jesse Duffield e6356ce10c Show commit mark before showing extra info
The 'YOU ARE HERE' marking should be shown before we show things like the current tag, otherwise it could be missed
2023-08-12 16:34:04 +10:00
README-bot b92da0dc54 Updated README.md 2023-08-12 06:19:15 +00:00
Jesse Duffield 70e668bfcf Add more demos (#2927) 2023-08-12 16:18:59 +10:00
Jesse Duffield 8dd517870d Add commit graph demo 2023-08-12 16:16:03 +10:00
Jesse Duffield f1753f36c8 Add rebase from marked base commit test
This also fixes a bug where after the rebase each commit in the commits view had a tick against it because we hadn't
refreshed the view since the base commit was no longer marked
2023-08-12 16:16:03 +10:00
Jesse Duffield 3ea81d4a6f Add undo demo 2023-08-12 16:15:50 +10:00
Federico 0df5cb1286 Allow deleting remote tags/branches from local tag/branch views (#2738) 2023-08-10 17:39:26 +10:00
Jesse Duffield c43830b027 Support editing files in existing neovim instance (#2916) 2023-08-10 17:23:58 +10:00
Stefan Haller 08624b8ef7 Fix jumping to the correct line from the staging view (#2919) 2023-08-10 07:27:11 +02:00
Stefan Haller 73b68927af Fix bug in LineNumberOfLine
This fixes a regression that was introduced in 73c7dc9c5d.
2023-08-10 07:22:42 +02:00
Stefan Haller bf699d3a79 Add test case for LineNumberOfLine()
There's a bug in LineNumberOfLine, but the existing test coverage doesn't catch
it, as the only test case for this was one where oldStart and newStart were the
same for all hunks. Add a test case where newStart is different for one of the
hunks; this demonstrates a bug, where all expected results from index 12 on are
off by one.
2023-08-10 07:22:42 +02:00
README-bot f2e8d549ba Updated README.md 2023-08-09 22:36:48 +00:00
Jesse Duffield 4ffb6c0fea Show dialogue when attempting to open info link fails (#2899) 2023-08-10 08:36:32 +10:00
Simon Whitaker 54776052a1 If OpenLink errors, show a dialog instead
If the command used by OSCommand.OpenLink fails, lazygit crashes. With this change, if the OpenLink command fails, lazygit just shows a dialog inviting the user to visit the relevant URL.

Fixes #2882
2023-08-09 13:12:40 +01:00
Jesse Duffield 9c5eedf748 use 'suspend' instead of 'editInTerminal' internally
'suspend' is a more appropriate name, especially now that you can choose not to suspend despite
still being in a terminal
2023-08-09 22:03:58 +10:00
Jesse Duffield ca08956f77 Use soft wrapping in config doc
Looking online I can't find any consensus about whether soft or hard wrap is better.
This post goes into the pros/cons: https://martin-ueding.de/posts/hard-vs-soft-line-wrap/

I find that editing hard-wrapped text is a pain in the ass, and it's hard to enforce
consistency. So I'm switching to soft-wrapping for this doc.
2023-08-09 21:33:12 +10:00
Jesse Duffield aa74239f05 Add nvim-remote editor preset
This allows us to jump back to the parent neovim process when we want to edit a file, rather than opening a new neovim
process within lazygit.

Arguably this should be the default, but I'm not familiar with the various ways people use lazygit with neovim.
2023-08-09 21:32:53 +10:00
Jesse Duffield 9e7018db8a Honour editInTerminal value when opening a worktree folder
There was no good reason not to do this in the first place.
2023-08-09 21:00:27 +10:00
Jesse Duffield cdea5b4873 Fix issue where explosion effect was out-of-view (#2909) 2023-08-08 23:07:46 +10:00
Jesse Duffield 4b0432423d Reset origin when clearing view 2023-08-08 22:01:43 +10:00
Jesse Duffield 2607580a09 Add Click() to GuiDriver (#2898) 2023-08-08 17:05:32 +10:00
Jesse Duffield ebd56bd8d5 Mention JSON schema (#2893) 2023-08-08 08:19:05 +10:00
Simon Whitaker ed1547e0cb Add a Click() primitive to the integration test library 2023-08-07 15:10:28 +01:00
README-bot 5e388e21c8 Updated README.md 2023-08-07 14:03:45 +00:00
Jesse Duffield 4c89f9fba5 Fix seg-fault when opening submodule in nested folder (#2903) 2023-08-08 00:03:28 +10:00
Jesse Duffield 8e7958f78b Fix seg-fault when opening submodule in nested folder 2023-08-07 23:57:28 +10:00
Jesse Duffield 579791e7bc Fix opening lazygit in a bare repo with specified worktree (#2902) 2023-08-07 22:48:14 +10:00
Jesse Duffield 595e28d335 Support bare worktrees where worktree does not have its own .git file
This was on oversight on my part: I assumed that the --work-tree arg was
always intended for use with linked worktrees which have a .git file
pointing back to the repo.

I'm honestly confused now: seems like there are three kinds of worktrees:
* the main worktree of a non-bare repo
* a linked worktree (with its own gitdir in the repo's worktrees/ dir)
* a random folder which you specify as a worktree with the --work-tree arg

I'm pretty sure the --work-tree arg is only intended to be used with this
third kind or workree
2023-08-07 22:40:53 +10:00
Jesse Duffield 0551f29de9 Test bare repos with dotfile setup 2023-08-07 22:21:23 +10:00
微笑andBill ZHANG 82b5c20050 Add zh-TW to docs/Config.md
Co-authored-by: Bill ZHANG <36790218+Lutra-Fs@users.noreply.github.com>
2023-08-07 19:52:28 +08:00
Jesse Duffield ecaf1e9002 Use nerdfont version 3 in demos (#2901) 2023-08-07 21:23:42 +10:00
Jesse Duffield 69ff621494 Use nerdfont version 3
Turns out I was on version 2 when recording these. I've switched
to v3 now.
2023-08-07 21:19:10 +10:00
EmilySeville7cfg f32fe84c9b feat(doc): better JSON schema usage explanation 2023-08-07 18:27:37 +10:00
Stefan Haller 2ded352f3a Don't run the check-required-label check on master (#2896) 2023-08-07 08:45:48 +02:00
Stefan Haller c877a8528f Don't run the check-required-label check on master
master doesn't have a label, so CI for master currently always fails.
2023-08-07 08:41:13 +02:00
Stefan Haller d1754a9490 update worktree icons for NerdFont V3 (#2895) 2023-08-07 08:16:42 +02:00
README-bot 33a96a3dd7 Updated README.md 2023-08-07 06:14:41 +00:00
Stefan Haller cd889adfa0 Import stefanhaller's tcell fork for real rather than just replacing it (#2888) 2023-08-07 08:14:28 +02:00
Saafo 77447ea6c3 👽️ update worktree icons for NerdFont V3 2023-08-07 05:47:57 +00:00
EmilySeville7cfg fb05ee369f feat(doc): mention JSON schema 2023-08-07 03:38:30 +10:00
Jothi Prasath 213da4fddd added termux installation 2023-08-06 20:12:11 +05:30
Harshit Tomar e78a09ebab added svelte and mdx 2023-08-06 20:26:07 +10:00
Stefan Haller 7ccb871a45 Bump gocui
... 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.
2023-08-06 12:03:23 +02:00
README-bot 9cc18bd7e6 Updated README.md 2023-08-06 06:29:45 +00:00
Stefan Haller 8e2ecd17a9 Fix README.md typo backkground -> background (#2884) 2023-08-06 08:29:32 +02:00
Mattias Johnson 78676dcff6 Fix README.md typo backkground -> background 2023-08-05 20:09:53 +02:00
README-bot 7847203d6e Updated README.md 2023-08-05 06:14:48 +00:00
Jesse Duffield af3101c7f2 Fix confirmation view sizing (#2879) 2023-08-05 16:14:34 +10:00
Jesse Duffield ec7e9f9228 Fix confirmation view sizing
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.
2023-08-05 16:09:02 +10:00
Jesse Duffield ab5875c78f Add more demos (#2874) 2023-08-04 09:18:25 +10:00
Jesse Duffield f6af4c29d4 Add custom patch demo 2023-08-04 09:15:07 +10:00
Jesse Duffield 996ad5bf26 Add worktree demo 2023-08-04 09:14:58 +10:00
Jesse Duffield 096628e366 Add custom command demo 2023-08-04 08:38:15 +10:00
Jesse Duffield b432fb5efe Add undo/redo demo 2023-08-03 21:02:12 +10:00
Jesse Duffield c390c9d58e Add filter demo to README 2023-08-03 18:22:30 +10:00
Jesse Duffield e25403c650 Move features to top of readme (#2867) 2023-08-03 17:25:48 +10:00
Jesse Duffield 5c9a9e777c Move features to top of readme 2023-08-03 17:23:47 +10:00
Jesse Duffield 39d24fdeb7 Add more demos to the README (#2866) 2023-08-03 17:02:40 +10:00
Jesse Duffield bbb2b32816 Point to new demos 2023-08-03 16:58:11 +10:00
Jesse Duffield 0152639ef0 Add demo for staging lines 2023-08-02 22:32:57 +10:00
Jesse Duffield c43195efb6 Add demo for amending old commit 2023-08-02 22:32:51 +10:00
Jesse Duffield 17db918cba Add demo for filtering branches 2023-08-02 22:32:51 +10:00
Jesse Duffield befd7fe63e Support mp4 videos for demos
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
2023-08-02 22:21:25 +10:00
Jesse Duffield de28391052 Remove file watcher code (#2865) 2023-08-02 22:20:39 +10:00
Jesse Duffield 92f0aa23cc Remove file watcher code
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.
2023-08-02 21:50:44 +10:00
Jesse Duffield c8520fbe78 Auto-refresh on window activation (#2854) 2023-08-02 20:42:47 +10:00
Stefan Haller 8ce60662f2 Refresh when the terminal window gets the focus 2023-08-02 11:35:36 +02:00
Stefan Haller c5acbb6c7c Bump gocui 2023-08-02 11:35:36 +02:00
Stefan Haller 4aca854b59 Point tcell at stefanhaller's fork
This is temporary as long as https://github.com/gdamore/tcell/pull/599 is not
merged. Once that PR is merged, we can revert this.
2023-08-02 11:35:36 +02:00
Jesse Duffield f7bf125037 Stop worktrees view from stealing the window (#2863) 2023-08-02 09:24:51 +10:00
Jesse Duffield c92ed07082 Appease linter 2023-08-02 08:32:57 +10:00
Jesse Duffield df0c3b3ea8 Increase timeout for linter
we've had a timeout issue in CI
2023-08-02 08:32:57 +10:00
Jesse Duffield 280b0429c3 Fix focus issue
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.
2023-08-02 07:37:40 +10:00
Jesse Duffield cb22db675a Update README.md 2023-08-02 00:05:04 +10:00
Jesse Duffield bb21034972 Update README.md 2023-08-01 22:37:53 +10:00
Jesse Duffield ec5bc93576 Show visual explosion effect when nuking worktree (#2861) 2023-08-01 22:31:29 +10:00
Jesse Duffield f43fd7af79 Wait in demo after setting caption
This looks nicer than waiting a second and then showing the caption as the action begins
2023-08-01 22:19:04 +10:00
Jesse Duffield a200fccba9 Add explosion animation when nuking working tree
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
2023-08-01 22:16:04 +10:00
Jesse Duffield 37dc31b815 Output correct path in demo record script 2023-08-01 22:10:59 +10:00
Jesse Duffield 1712ad08ae Start in fullscreen when passing a git arg
Often we just want to see the desired view in fullscreen so I'm making that the default
2023-08-01 22:10:59 +10:00
README-bot 53f0c1eb4f Updated README.md 2023-08-01 12:03:12 +00:00
Jesse Duffield c53adbbb84 Update README.md 2023-08-01 22:02:56 +10:00
Jesse Duffield e932aaaeaf Internationalise logging of commands (#2852) 2023-08-01 10:06:48 +10:00
Jesse Duffield b44d0c4bde Show loader when rebasing (#2851) 2023-08-01 09:58:11 +10:00
Karl Heitmann 917cfd7586 Adds EditRebaseFromBaseCommit log message to i18n 2023-07-31 19:57:14 -04:00
Karl Heitmann c7e6a73512 Wraps rebase func with WithWaitingStatus to show loader when rebasing 2023-07-31 19:46:59 -04:00
Karl Heitmann b7ba06fa5b Moves log related translations into its own Tr.Log. namespace 2023-07-31 19:42:41 -04:00
Karl Heitmann 51e205ce11 Moves hard coded strings for LogCommand to i18n. 2023-07-31 19:35:55 -04:00
README-bot 6984a4f4d4 Updated README.md 2023-07-31 23:12:55 +00:00
Jesse Duffield 4baecec7bf Create demo output dir if it doesn't already exist (#2857) 2023-08-01 09:12:39 +10:00
Jesse Duffield b262e7f9b8 Create demo output dir if it doesn't already exist 2023-08-01 09:12:23 +10:00
Jesse Duffield b92c294059 Add automated demo recordings (#2853) 2023-07-31 22:43:36 +10:00
Jesse Duffield 07d03df8df Update README.md 2023-07-31 22:36:40 +10:00
Jesse Duffield 9cc1d65280 Add demo test variant
We're piggybacking on our existing integration test framework to record  demos that we can include in our docs
2023-07-31 22:33:04 +10:00
Jesse Duffield 893c1cab75 Update README.md 2023-07-31 22:19:11 +10:00
Jesse Duffield 25d57350df Update README.md 2023-07-31 22:12:30 +10:00
Jesse Duffield eb68591396 Update README.md 2023-07-31 20:31:43 +10:00
Stefan Haller 6de9e230a0 Save IgnoreWhitespaceInDiffView in state.yml (#2830) 2023-07-31 11:48:51 +02:00
Stefan Haller 668d29736c Log error when saving app state fails after showing/hiding command log 2023-07-31 11:41:55 +02:00
Stefan Haller be667682f0 Move IgnoreWhitespaceInDiffView to AppState, and persist it when it changes 2023-07-31 11:41:55 +02:00
Stefan Haller 045bce5dfc Allow force-tagging if tag exists (#2827) 2023-07-31 11:40:40 +02:00
Stefan Haller 3827479643 Show correct keybindings in force-push prompt 2023-07-31 11:38:52 +02:00
Stefan Haller d41a195ee6 Allow force-tagging if tag exists 2023-07-31 11:38:52 +02:00
Stefan Haller 71d2fd37e2 Rebase onto branch from a marked base commit (#2835) 2023-07-31 08:52:58 +02:00
Stefan Haller 66de981e91 Add a "Mark commit as base commit for rebase" command
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.
2023-07-31 08:41:41 +02:00
Stefan Haller 375451785c Cleanup: remove extra OutsideFilterMode guard
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.
2023-07-31 08:40:47 +02:00
Stefan Haller a6af31a4cb Visualize local branch heads in commits panel, 2nd approach (#2775) 2023-07-31 08:40:09 +02:00
Stefan Haller 94daf7bddc Add documentation for working with stacked branches 2023-07-31 08:34:01 +02:00
Stefan Haller 4eb73393bb Don't show branch marker for head commit unless updateRefs config is on 2023-07-31 08:34:01 +02:00
Stefan Haller f5c9764dd2 Don't show branch heads in reflog subcommits
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.
2023-07-31 08:34:01 +02:00
Stefan Haller 6dc25d796b Visualize local branch heads in commits panel
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.
2023-07-31 08:34:01 +02:00
Stefan Haller 0c07963a2e Store full sha in branch model
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.
2023-07-31 08:34:01 +02:00
Stefan Haller 092d5dd608 Add CheckedOutBranch to Model struct 2023-07-31 08:34:01 +02:00
Stefan Haller e9bbd816de Make bisect/basic.go test more concrete
- 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.
2023-07-31 08:34:01 +02:00
Stefan Haller eb6f089a2a Add author short names to commits in test
This allow us to check not only whether a given commit has the branch head
marker, but also that other commits _don't_ have it, which is important.
2023-07-31 08:34:00 +02:00
Stefan Haller cb240081a8 Improve updateRef test
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.
2023-07-31 08:34:00 +02:00
Stefan Haller 9c57444adc Remove the old experimentalShowBranchHeads mechanism and config
We are going to replace it with a better one later in this branch.
2023-07-31 08:34:00 +02:00
Stefan Haller 7b8ebfa27e Fix merge status for update-ref command (#2845) 2023-07-31 08:32:48 +02:00
Stefan Haller 8ab05d6834 Fix merge status of commits when update-ref command is present
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.
2023-07-31 08:28:03 +02:00
Stefan Haller 774df817fd Add tests for setCommitMergedStatuses
The test for update-ref shows demonstrates a problem. See next commit for the
fix.
2023-07-31 08:28:03 +02:00
Stefan Haller 18903a7dde Make setCommitMergedStatuses a non-member function
It doesn't depend on anything in CommitLoader, so it can be free-standing, and
that makes it easier to test (see next commit).
2023-07-31 08:28:03 +02:00
Jesse Duffield 7cfbfb7183 Remove redundant secureexec package (#2847) 2023-07-30 20:04:06 +10:00
Jesse Duffield 975d2bedb6 Remove secureexec package
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.
2023-07-30 19:59:51 +10:00
Jesse Duffield 5c78394299 Standardise on using lo for slice functions (#2846) 2023-07-30 18:55:12 +10:00
Jesse Duffield e33fe37a99 Standardise on using lo for slice functions
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.
2023-07-30 18:51:23 +10:00
README-bot ea54cb6e9c Updated README.md 2023-07-30 08:42:37 +00:00
Jesse Duffield 08f0e28e55 Add worktrees view (#2147) 2023-07-30 18:42:25 +10:00
Jesse Duffield 2d4706e96e fix go mod 2023-07-30 18:36:04 +10:00
Jesse Duffield d30eef69b1 Use double dash for disambuating path in editor templates 2023-07-30 18:35:36 +10:00
Jesse Duffield 91909331b5 Fix flakey worktree tests
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
2023-07-30 18:35:36 +10:00
Jesse Duffield f31e213edc rename files 2023-07-30 18:35:36 +10:00
Jesse Duffield 7b302d8c29 Write unit tests with the help of afero
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
2023-07-30 18:35:36 +10:00
Jesse Duffield a1fae41051 Fix bug where worktree view would take over window upon switching branches
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.
2023-07-30 18:35:24 +10:00
Jesse Duffield b16bb409fc Use forward-slashes on windows
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
2023-07-30 18:35:24 +10:00
Jesse Duffield a77d24fdc6 Add section in integration readme about testing against old git versions 2023-07-30 18:35:24 +10:00
Jesse Duffield eecf07cd12 Add more i18n for worktrees 2023-07-30 18:35:24 +10:00
Jesse Duffield 06be88aef7 Use fields rather than methods on worktrees
I would prefer to use methods to keep things immutable but I'd rather be consistent with the other
models and update them all at once
2023-07-30 18:35:24 +10:00
Jesse Duffield 4c5b1574f1 Centralise logic for obtaining repo paths
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.
2023-07-30 18:35:24 +10:00
Jesse Duffield de57cfd6ff Remove IO logic from presentation code for worktrees
We're doing all the IO in our workers loader method so that we don't need to do any
in our presentation code
2023-07-30 18:35:24 +10:00
Jesse Duffield 2b24c15938 Add test for opening lazygit in the worktree of a bare repo 2023-07-30 18:35:23 +10:00
Jesse Duffield e874f94cf8 Remove dead function 2023-07-30 18:35:23 +10:00
Jesse Duffield 0a410141ae Fix test by making branches appear deterministically
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
2023-07-30 18:35:23 +10:00
Jesse Duffield e356b29b4a Allow entering a submodule by pressing space 2023-07-30 18:35:23 +10:00
Jesse Duffield bc4ce26cef Allow entering a worktree by pressing enter 2023-07-30 18:35:23 +10:00
Jesse Duffield ae66f720f5 Update repo switch logic
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
2023-07-30 18:35:23 +10:00
Jesse Duffield 9c15ba0c0b Add test for retained context focus when switching worktrees 2023-07-30 18:35:23 +10:00
Jesse Duffield b3060065d9 Support fastforwarding worktree 2023-07-30 18:35:23 +10:00
Jesse Duffield a313b16704 Add more worktree tests 2023-07-30 18:35:23 +10:00
Jesse Duffield b93b9dae88 Add worktree tests for removing/detaching 2023-07-30 18:35:23 +10:00
Jesse Duffield 277142fc4b Add worktree integration tests 2023-07-30 18:35:23 +10:00
Jesse Duffield 18a508b29c Update cheatsheets 2023-07-30 18:35:23 +10:00
Jesse Duffield cdfad864ae Remove worktree version guards
Our min required git version is 2.20 so there's no need to add guards
for worktrees because they were added in 2.5
2023-07-30 18:35:23 +10:00
Jesse Duffield fb4453c18a Fix unit tests 2023-07-30 18:35:23 +10:00
Jesse Duffield b5ff55e538 Show loader when switching worktrees 2023-07-30 18:35:23 +10:00
Jesse Duffield 81a9133261 Support older versions of git when fetching worktrees
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
2023-07-30 18:35:23 +10:00
Jesse Duffield ca6f9c4155 Fix tests
Going and fixing up some submodule tests which were broken by bad assumptions with worktree code
2023-07-30 18:35:23 +10:00
Jesse Duffield 7569180cac Fix tests
We now change directories to the repo on startup so we don't need to determine the test path in some special way
2023-07-30 18:35:23 +10:00
Jesse Duffield eeec373728 Safer fetching of linked worktree paths 2023-07-30 18:35:23 +10:00
Jesse Duffield 0604e43813 Move worktrees tab to files window 2023-07-30 18:35:23 +10:00
Jesse Duffield 27ade502ee Change directory to worktree if given as an argument
Previously we used an env var for this and it's not clear to me how that worked but
with this PR current directory = worktree directory
2023-07-30 18:35:23 +10:00
Jesse Duffield 3a7468ecac Support opening worktree in editor 2023-07-30 18:35:23 +10:00
Jesse Duffield 7b05dacb98 Properly render worktrees in files panel 2023-07-30 18:35:22 +10:00
Jesse Duffield b73efb2c22 Better logic for knowing which repo we're in 2023-07-30 18:35:22 +10:00
Jesse Duffield a06a5cadee Only show worktree in status panel if not the main worktree and worktrees are supported 2023-07-30 18:35:22 +10:00
Jesse Duffield 3cd2d6fa5c Hide worktree functionality on old git versions 2023-07-30 18:35:22 +10:00
Jesse Duffield ec839e9e96 Associate branches with worktrees even when mid-rebase 2023-07-30 18:35:22 +10:00
Jesse Duffield 6f2f9f6677 Assume that the base of a worktree can be checked out 2023-07-30 18:35:22 +10:00
Jesse Duffield 2082fdf84a i18n for worktrees 2023-07-30 18:35:22 +10:00
Jesse Duffield 894485190b Don't quit on error 2023-07-30 18:35:22 +10:00
Jesse Duffield 87b2455dbb Allow opening worktree in editor
This does the job but I think we need yet another editor command for opening a directory in a new window.
2023-07-30 18:35:22 +10:00
Jesse Duffield 2e68967e02 Show base ref suggestions when creating worktree 2023-07-30 18:35:22 +10:00
Jesse Duffield 71422a8549 Refresh work trees when discarding file changes
We do this because we may be deleting a worktree folder so we'll need to show that in the worktrees view
2023-07-30 18:35:22 +10:00
Jesse Duffield 9c69a5df69 Checkout worktree when creating from worktree view 2023-07-30 18:35:22 +10:00
Jesse Duffield 9b24995990 Use 'M' for months in branches panel 2023-07-30 18:35:22 +10:00
Jesse Duffield 142f06357f Fix filtering logic in worktrees view 2023-07-30 18:35:22 +10:00
Jesse Duffield 18ea68c23a Support creating worktrees from refs 2023-07-30 18:35:22 +10:00
Jesse Duffield 4b2622d93b Fix wording 2023-07-30 18:35:22 +10:00
Jesse Duffield ae0193698e Log when directory is changed 2023-07-30 18:35:22 +10:00
Jesse Duffield 6b4a638415 Handle deleting branch attached to worktree 2023-07-30 18:35:22 +10:00
Jesse Duffield 4b3c9f5b35 Update wording 2023-07-30 18:35:22 +10:00
Jesse Duffield 6c69549ced Don't touch repo stack when switching worktrees
We shouldn't touch this cos we're doing a lateral move
2023-07-30 18:35:22 +10:00
Jesse Duffield 5868750aba Move status panel presentation logic into presentation package 2023-07-30 18:35:22 +10:00
Jesse Duffield ab3052f642 Land in the same panel when switching to a worktree 2023-07-30 18:35:22 +10:00
Jesse Duffield 53f4ccb809 Move current worktree to top of list 2023-07-30 18:35:22 +10:00
Jesse Duffield 077ae99438 Prompt to switch to worktree when branch is checked out by other worktree 2023-07-30 18:35:22 +10:00
Jesse Duffield fe8adf9eb8 Use git lingo 2023-07-30 18:35:22 +10:00
Jesse Duffield c713d550c0 Improve name handling 2023-07-30 18:35:22 +10:00
Jesse Duffield d19d89ea9d Use sentence case 2023-07-30 18:35:22 +10:00
Jesse Duffield e8ec41fb0f Refactor 2023-07-30 18:35:22 +10:00
Jesse Duffield 7682ec029b Update worktree model 2023-07-30 18:35:22 +10:00
Jesse Duffield 03f726038e Alert when attempting to enter the current worktree 2023-07-30 18:35:21 +10:00
Jesse Duffield 796945aad0 Remove comment 2023-07-30 18:35:21 +10:00
Joel Baranick db02c13bf6 Address PR comments 2023-07-30 18:35:21 +10:00
Joel Baranick 1ce9a87544 Basic support for adding a worktree 2023-07-30 18:35:21 +10:00
Joel Baranick 35e6e6347a Put all worktree i18n strings together
Use tabwriter to align worktree panel contents
2023-07-30 18:35:21 +10:00
Joel Baranick 54708233ac Improve worktree panel 2023-07-30 18:35:21 +10:00
Joel Baranick c679fd1924 Style missing worktree as red and display better error when trying to switch to them
Use a broken link icon for missing worktrees
2023-07-30 18:35:21 +10:00
Joel Baranick 9a79154d05 Hide worktrees in the worktree panel if they point at a non-existing filesystem location.
Remove unneeded check when filtering out branches from non-current worktrees from the branch panel.
Add link icon for linked worktrees
2023-07-30 18:35:21 +10:00
Joel Baranick 60872c91e6 Update status to differentiate the main vs linked worktrees 2023-07-30 18:35:21 +10:00
Joel Baranick afc4aedd4f Support for deleting a worktree 2023-07-30 18:35:21 +10:00
Joel Baranick f8ba899b87 Initial addition of support for worktrees 2023-07-30 18:35:21 +10:00
Stefan Haller 52447e5d46 When bisecting, always mark the current commit as good/bad, not the selected (#2837) 2023-07-29 14:56:14 +02:00
Stefan Haller 6794149ec8 When bisecting, always mark the current commit as good/bad, not the selected
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.
2023-07-29 14:44:00 +02:00
Stefan Haller a4772ae606 Add bisect menu entry that lets you choose bisect terms (#2838) 2023-07-29 12:02:22 +02:00
Stefan Haller f30e09856c Add bisect menu entry that lets you choose bisect terms
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.
2023-07-29 11:59:58 +02:00
Stefan Haller 9b7f978e3e Fix the x/exp dependency (#2844) 2023-07-29 11:59:04 +02:00
Jesse Duffield f17417219a feat: add os.copyToClipboardCmd to allow for a custom command #1055 (#2784) 2023-07-29 19:35:52 +10:00
Red S d7f84aed8a feat: add os.copyToClipboardCmd to allow for a custom command
Issue #1055

test: CopyPatchToClipboard (temporary commit for review)
2023-07-29 19:09:59 +10:00
Stefan Haller deecbadda9 Fix the x/exp dependency
We started to use it directly in 429225da80.
2023-07-29 10:01:41 +02:00
微笑 1431ffcebf Corrected 'zh' to 'zh-CN' in lazygit docs for proper localization 2023-07-29 12:07:40 +08:00
Pavel Dostál 1a2a301aa2 Add instructions for openSUSE 2023-06-12 08:21:34 +02:00
512 changed files with 11804 additions and 6057 deletions
+6 -1
View File
@@ -2,9 +2,14 @@
- **Please check if the PR fulfills these requirements**
* [ ] Cheatsheets are up-to-date (run `go run scripts/cheatsheet/main.go generate`)
* [ ] Cheatsheets are up-to-date (run `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
<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for examples
-->
+3 -3
View File
@@ -14,11 +14,11 @@ jobs:
- name: Unshallow repo
run: git fetch --prune --unshallow
- name: Setup Go
uses: actions/setup-go@v1
uses: actions/setup-go@v4
with:
go-version: 1.18.x
go-version: 1.20.x
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v1
uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
version: v1.17.2
+16 -63
View File
@@ -1,7 +1,7 @@
name: Continuous Integration
env:
GO_VERSION: 1.18
GO_VERSION: 1.20
on:
push:
@@ -30,18 +30,9 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v1
uses: actions/setup-go@v4
with:
go-version: 1.18.x
- name: Cache build
uses: actions/cache@v3
with:
path: |
${{matrix.cache_path}}
~/go/pkg/mod
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
restore-keys: |
${{runner.os}}-go-
go-version: 1.20.x
- name: Test code
# we're passing -short so that we skip the integration tests, which will be run in parallel below
run: |
@@ -89,18 +80,9 @@ jobs:
path: ~/git-${{matrix.git-version}}
key: ${{runner.os}}-git-${{matrix.git-version}}
- name: Setup Go
uses: actions/setup-go@v1
uses: actions/setup-go@v4
with:
go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
restore-keys: |
${{runner.os}}-go-
go-version: 1.20.x
- name: Print git version
run: git --version
- name: Test code
@@ -115,18 +97,9 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v1
uses: actions/setup-go@v4
with:
go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-build
restore-keys: |
${{runner.os}}-go-
go-version: 1.20.x
- name: Build linux binary
run: |
GOOS=linux go build
@@ -151,29 +124,17 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v1
uses: actions/setup-go@v4
with:
go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-build
restore-keys: |
${{runner.os}}-go-
- name: Check Cheatsheet
run: |
go run scripts/cheatsheet/main.go check
go-version: 1.20.x
- name: Check Vendor Directory
# 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
- name: Check All Auto-Generated Files
# ensure all our auto-generated files are 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)
go generate ./... && git diff --quiet || (git status -s; echo "Auto-generated files not up to date. Run 'go generate ./...' locally and commit the changes" && exit 1)
shell: bash # needed so that we get "-o pipefail"
- name: Check Filenames
run: scripts/check_filenames.sh
@@ -185,20 +146,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v1
uses: actions/setup-go@v4
with:
go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
restore-keys: |
${{runner.os}}-go-
go-version: 1.20.x
- name: Lint
uses: golangci/golangci-lint-action@v3.1.0
uses: golangci/golangci-lint-action@v3.7.0
with:
version: latest
- name: errors
@@ -206,6 +158,7 @@ jobs:
if: ${{ failure() }}
check-required-label:
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/master'
steps:
- uses: mheap/github-action-required-labels@v5
with:
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
uses: actions/checkout@v3
- name: Generate Sponsors 💖
uses: JamesIves/github-sponsors-readme-action@v1.0.8
uses: JamesIves/github-sponsors-readme-action@v1.2.2
with:
token: ${{ secrets.SPONSORS_TOKEN }}
file: 'README.md'
+3 -2
View File
@@ -35,9 +35,10 @@ lazygit.exe
test/git_server/data
test/results/**
test/_results/**
oryxBuildBinary
__debug_bin
.worktrees
.worktrees
demo/output/*
+7 -2
View File
@@ -20,10 +20,15 @@ linters:
linters-settings:
exhaustive:
default-signifies-exhaustive: true
staticcheck:
# SA1019 is for checking that we're not using fields marked as deprecated
# in a comment. It decides this in a loose way so I'm silencing it. Also because
# it's tripping on our own structs.
checks: ["all", "-SA1019"]
nakedret:
# the gods will judge me but I just don't like naked returns at all
max-func-lines: 0
run:
go: 1.18
go: '1.20'
timeout: 10m
+35 -4
View File
@@ -7,7 +7,11 @@
"request": "launch",
"mode": "auto",
"program": "main.go",
"args": ["--debug", "--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"],
"args": [
"--debug",
"--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"
],
"hideSystemGoroutines": true,
"console": "integratedTerminal",
"presentation": {
"hidden": true
@@ -19,17 +23,44 @@
"request": "launch",
"mode": "auto",
"program": "main.go",
"args": ["--logs", "--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"],
"args": [
"--logs",
"--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"
],
"console": "integratedTerminal",
"presentation": {
"hidden": true
}
}
},
{
"name": "Attach to a running Lazygit",
"type": "go",
"request": "attach",
"mode": "local",
"processId": "lazygit",
"hideSystemGoroutines": true,
"console": "integratedTerminal",
},
{
// To use this, first start an integration test with the "cli" runner and
// use the -debug option; e.g.
// $ make integration-test-cli -- -debug tag/reset.go
"name": "Attach to integration test runner",
"type": "go",
"request": "attach",
"mode": "local",
"processId": "test_lazygit",
"hideSystemGoroutines": true,
"console": "integratedTerminal",
},
],
"compounds": [
{
"name": "Run with logs",
"configurations": ["Tail Lazygit logs", "Debug Lazygit"],
"configurations": [
"Tail Lazygit logs",
"Debug Lazygit"
],
"stopAll": true
}
]
+5
View File
@@ -0,0 +1,5 @@
{
"gopls": {
"formatting.gofumpt": true,
},
}
+1 -1
View File
@@ -2,7 +2,7 @@
# docker build -t lazygit .
# docker run -it lazygit:latest /bin/sh
FROM golang:1.18 as build
FROM golang:1.20 as build
WORKDIR /go/src/github.com/jesseduffield/lazygit/
COPY go.mod go.sum ./
RUN go mod download
+68
View File
@@ -0,0 +1,68 @@
.PHONY: all
all: build
.PHONY: build
build:
go build -gcflags='all=-N -l'
.PHONY: install
install:
go install
.PHONY: run
run: build
./lazygit
# Run `make run-debug` in one terminal tab and `make print-log` in another to view the program and its log output side by side
.PHONY: run-debug
run-debug:
go run main.go -debug
.PHONY: print-log
print-log:
go run main.go --logs
.PHONY: unit-test
unit-test:
go test ./... -short
.PHONY: test
test: unit-test integration-test-all
# Generate all our auto-generated files (test list, cheatsheets, maybe other things in the future)
.PHONY: generate
generate:
go generate ./...
.PHONY: format
format:
gofumpt -l -w .
# For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md.
.PHONY: integration-test-tui
integration-test-tui:
go run cmd/integration_test/main.go tui $(filter-out $@,$(MAKECMDGOALS))
.PHONY: integration-test-cli
integration-test-cli:
go run cmd/integration_test/main.go cli $(filter-out $@,$(MAKECMDGOALS))
.PHONY: integration-test-all
integration-test-all:
go test pkg/integration/clients/*.go
.PHONY: bump-gocui
bump-gocui:
scripts/bump_gocui.sh
.PHONY: bump-lazycore
bump-lazycore:
scripts/bump_lazycore.sh
.PHONY: record-demo
record-demo:
demo/record_demo.sh $(filter-out $@,$(MAKECMDGOALS))
.PHONY: vendor
vendor:
go mod vendor && go mod tidy
+176 -38
View File
File diff suppressed because one or more lines are too long
+41 -15
View File
@@ -16,7 +16,7 @@ Usage:
> go run cmd/integration_test/main.go cli [--slow] [--sandbox] <test1> <test2> ...
If you pass no test names, it runs all tests
Accepted environment variables:
KEY_PRESS_DELAY (e.g. 200): the number of milliseconds to wait between keypresses
INPUT_DELAY (e.g. 200): the number of milliseconds to wait between keypresses or mouse clicks
TUI mode:
> go run cmd/integration_test/main.go tui
@@ -26,6 +26,29 @@ Usage:
> go run cmd/integration_test/main.go help
`
type flagInfo struct {
name string // name of the flag; can be used with "-" or "--"
flag *bool // a pointer to the variable that should be set to true when this flag is passed
}
// Takes the args that you want to parse (excluding the program name and any
// subcommands), and returns the remaining args with the flags removed
func parseFlags(args []string, flags []flagInfo) []string {
outer:
for len(args) > 0 {
for _, f := range flags {
if args[0] == "-"+f.name || args[0] == "--"+f.name {
*f.flag = true
args = args[1:]
continue outer
}
}
break
}
return args
}
func main() {
if len(os.Args) < 2 {
log.Fatal(usage)
@@ -35,23 +58,26 @@ func main() {
case "help":
fmt.Println(usage)
case "cli":
testNames := os.Args[2:]
slow := false
sandbox := false
// get the next arg if it's --slow
if len(os.Args) > 2 {
if os.Args[2] == "--slow" || os.Args[2] == "-slow" {
testNames = os.Args[3:]
slow = true
} else if os.Args[2] == "--sandbox" || os.Args[2] == "-sandbox" {
testNames = os.Args[3:]
sandbox = true
}
}
clients.RunCLI(testNames, slow, sandbox)
waitForDebugger := false
raceDetector := false
testNames := parseFlags(os.Args[2:], []flagInfo{
{"slow", &slow},
{"sandbox", &sandbox},
{"debug", &waitForDebugger},
{"race", &raceDetector},
})
clients.RunCLI(testNames, slow, sandbox, waitForDebugger, raceDetector)
case "tui":
clients.RunTUI()
raceDetector := false
remainingArgs := parseFlags(os.Args[2:], []flagInfo{
{"race", &raceDetector},
})
if len(remainingArgs) > 0 {
log.Fatal("tui only supports the -race argument.")
}
clients.RunTUI(raceDetector)
default:
log.Fatal(usage)
}
+2
View File
@@ -0,0 +1,2 @@
This directory contains stuff for recording lazygit demos.
+112
View File
@@ -0,0 +1,112 @@
# Specify a command to be executed
# like `/bin/bash -l`, `ls`, or any other commands
# the default is bash for Linux
# or powershell.exe for Windows
command: echo "YOU NEED TO SPECIFY YOUR OWN COMMAND WITH THE -d ARG"
# Specify the current working directory path
# the default is the current working directory path
cwd: null
# Export additional ENV variables
env:
recording: true
# Explicitly set the number of columns
# or use `auto` to take the current
# number of columns of your shell
cols: 120 # 100
# Explicitly set the number of rows
# or use `auto` to take the current
# number of rows of your shell
rows: 35 # 30
# Amount of times to repeat GIF
# If value is -1, play once
# If value is 0, loop indefinitely
# If value is a positive number, loop n times
repeat: 0
# Quality
# 1 - 100
# Higher quality seems to make no difference, but running it through
# gifsicle ends up with a much better compressed version.
quality: 100
# Delay between frames in ms
# If the value is `auto` use the actual recording delays
frameDelay: auto
# Maximum delay between frames in ms
# Ignored if the `frameDelay` isn't set to `auto`
# Set to `auto` to prevent limiting the max idle time
maxIdleTime: 2000
# The surrounding frame box
# The `type` can be null, window, floating, or solid`
# To hide the title use the value null
# Don't forget to add a backgroundColor style with a null as type
frameBox:
type: floating
title: Lazygit
style:
border: 0px black solid
backgroundColor: "#1d1d1d"
margin: -5px
# Add a watermark image to the rendered gif
# You need to specify an absolute path for
# the image on your machine or a URL, and you can also
# add your own CSS styles
watermark:
imagePath: null
style:
position: absolute
right: 15px
bottom: 15px
width: 100px
opacity: 0.9
# Cursor style can be one of
# `block`, `underline`, or `bar`
cursorStyle: block
# Font family
# You can use any font that is installed on your machine
# in CSS-like syntax
# Download from:
# https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.zip
# Not using the mono font because it makes icons too small.
fontFamily: "DejaVuSansM Nerd Font"
# The size of the font
fontSize: 8
# The height of lines
lineHeight: 1
# The spacing between letters
letterSpacing: 0
# Theme
theme:
background: "transparent"
foreground: "#dddad6"
cursor: "#c7c7c7"
black: "#7a7a7a"
red: "#fc4384"
green: "#b3e33b"
yellow: "#ffa727"
blue: "#102895"
magenta: "#c930c7"
cyan: "#00c5c7"
white: "#c7c7c7"
brightBlack: "#676767"
brightRed: "#ff7fac"
brightGreen: "#c8ed71"
brightYellow: "#ebdf86"
brightBlue: "#6871ff"
brightMagenta: "#ff76ff"
brightCyan: "#5ffdff"
brightWhite: "#fffefe"
+81
View File
@@ -0,0 +1,81 @@
#!/bin/sh
set -e
TYPE=$1
TEST=$2
usage() {
echo "Usage: $0 [gif|mp4] <test path>"
echo "e.g. using full path: $0 gif pkg/integration/tests/demo/nuke_working_tree.go"
exit 1
}
if [ "$#" -ne 2 ]
then
usage
fi
if [ "$TYPE" != "gif" ] && [ "$TYPE" != "mp4" ]
then
usage
exit 1
fi
if [ -z "$TEST" ]
then
usage
fi
WORKTREE_PATH=$(git worktree list | grep assets | awk '{print $1}')
if [ -z "$WORKTREE_PATH" ]
then
echo "Could not find assets worktree. You'll need to create a worktree for the assets branch using the following command:"
echo "git worktree add .worktrees/assets assets"
echo "The assets branch has no shared history with the main branch: it exists to store assets which are too large to store in the main branch."
exit 1
fi
OUTPUT_DIR="$WORKTREE_PATH/demo"
if ! command -v terminalizer &> /dev/null
then
echo "terminalizer could not be found"
echo "Install it with: npm install -g terminalizer"
exit 1
fi
if ! command -v "gifsicle" &> /dev/null
then
echo "gifsicle could not be found"
echo "Install it with: npm install -g gifsicle"
exit 1
fi
# Get last part of the test path and set that as the output name
# example test path: pkg/integration/tests/01_basic_test.go
# For that we want: NAME=01_basic_test
NAME=$(echo "$TEST" | sed -e 's/.*\///' | sed -e 's/\..*//')
# Add the demo to the tests list (if missing) so that it can be run
go generate pkg/integration/tests/tests.go
mkdir -p "$OUTPUT_DIR"
# First we record the demo into a yaml representation
terminalizer -c demo/config.yml record --skip-sharing -d "go run cmd/integration_test/main.go cli --slow $TEST" "$OUTPUT_DIR/$NAME"
# Then we render it into a gif
terminalizer render "$OUTPUT_DIR/$NAME" -o "$OUTPUT_DIR/$NAME.gif"
# Then we convert it to either an mp4 or gif based on the command line argument
if [ "$TYPE" = "mp4" ]
then
COMPRESSED_PATH="$OUTPUT_DIR/$NAME.mp4"
ffmpeg -y -i "$OUTPUT_DIR/$NAME.gif" -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" "$COMPRESSED_PATH"
else
COMPRESSED_PATH="$OUTPUT_DIR/$NAME-compressed.gif"
gifsicle --colors 256 --use-col=web -O3 < "$OUTPUT_DIR/$NAME.gif" > "$COMPRESSED_PATH"
fi
echo "Demo recorded to $COMPRESSED_PATH"
+59 -36
View File
@@ -16,6 +16,17 @@ If you want to change the config directory:
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
JSON schema is available for `config.yml` so that IntelliSense in Visual Studio Code (completion and error checking) is automatically enabled when the [YAML Red Hat][yaml] extension is installed. However, note that automatic schema detection only works if your config file is in one of the standard paths mentioned above. If you override the path to the file, you can still make IntelliSense work by adding
```yaml
# yaml-language-server: $schema=https://json.schemastore.org/lazygit.json
```
to the top of your config file or via [Visual Studio Code settings.json config][settings].
[yaml]: https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml
[settings]: https://github.com/redhat-developer/vscode-yaml#associating-a-schema-to-a-glob-pattern-via-yamlschemas
## Default
```yaml
@@ -24,10 +35,12 @@ gui:
windowSize: 'normal' # one of 'normal' | 'half' | 'full' default is 'normal'
scrollHeight: 2 # how many lines you scroll by
scrollPastBottom: true # enable scrolling past the bottom
scrollOffMargin: 2 # how many lines to keep before/after the cursor when it reaches the top/bottom of the view; see 'Scroll-off Margin' section below
scrollOffBehavior: 'margin' # one of 'margin' | 'jump'; see 'Scroll-off Margin' section below
sidePanelWidth: 0.3333 # number from 0 to 1
expandFocusedSidePanel: false
mainPanelSplitMode: 'flexible' # one of 'horizontal' | 'flexible' | 'vertical'
language: 'auto' # one of 'auto' | 'en' | 'zh' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
language: 'auto' # one of 'auto' | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
timeFormat: '02 Jan 06' # https://pkg.go.dev/time#Time.Format
shortTimeFormat: '3:04PM'
theme:
@@ -62,15 +75,17 @@ gui:
showListFooter: true # for seeing the '5 of 20' message in list panels
showRandomTip: true
showBranchCommitHash: false # show commit hashes alongside branch names
experimentalShowBranchHeads: false # visualize branch heads with (*) in commits list
showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you)
showPanelJumps: true # for showing the jump-to-panel keybindings as panel subtitles
showCommandLog: true
showIcons: false # deprecated: use nerdFontsVersion instead
nerdFontsVersion: "" # nerd fonts version to use ("2" or "3"); empty means don't show nerd font icons
commandLogSize: 8
splitDiff: 'auto' # one of 'auto' | 'always'
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
border: 'single' # one of 'single' | 'double' | 'rounded' | 'hidden'
border: 'rounded' # one of 'single' | 'double' | 'rounded' | 'hidden'
animateExplosion: true # shows an explosion animation when nuking the working tree
portraitMode: 'auto' # one of 'auto' | 'never' | 'always'
git:
paging:
colorArg: always
@@ -104,8 +119,8 @@ git:
overrideGpg: false # prevents lazygit from spawning a separate process when using GPG
disableForcePushing: false
parseEmoji: false
diffContextSize: 3 # how many lines of context are shown around a change in diffs
os:
copyToClipboardCmd: '' # See 'Custom Command for Copying to Clipboard' section
editPreset: '' # see 'Configuring File Editing' section
edit: ''
editAtLine: ''
@@ -148,7 +163,7 @@ keybinding:
jumpToBlock: ['1', '2', '3', '4', '5'] # goto the Nth block / panel
nextMatch: 'n'
prevMatch: 'N'
optionMenu: null # show help menu
optionMenu: <disabled> # show help menu
optionMenu-alt1: '?' # show help menu
select: '<space>'
goInto: '<enter>'
@@ -193,6 +208,7 @@ keybinding:
commitChangesWithoutHook: 'w' # commit changes without pre-commit hook
amendLastCommit: 'A'
commitChangesWithEditor: 'C'
confirmDiscard: 'x'
ignoreFile: 'i'
refreshFiles: 'r'
stashAllChanges: 's'
@@ -278,29 +294,36 @@ os:
open: 'open {{filename}}'
```
### Configuring File Editing
## Custom Command for Copying to Clipboard
```yaml
os:
copyToClipboardCmd: ''
```
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.
There are two commands for opening files, `o` for "open" and `e` for "edit". `o`
acts as if the file was double-clicked in the Finder/Explorer, so it also works
for non-text files, whereas `e` opens the file in an editor. `e` can also jump
to the right line in the file if you invoke it from the staging panel, for
example.
If you are working on a terminal that supports OSC52, the following command will let you take advantage of it:
```
os:
copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64)\a" > /dev/tty
```
To tell lazygit which editor to use for the `e` command, the easiest way to do
that is to provide an editPreset config, e.g.
## Configuring File Editing
There are two commands for opening files, `o` for "open" and `e` for "edit". `o` acts as if the file was double-clicked in the Finder/Explorer, so it also works for non-text files, whereas `e` opens the file in an editor. `e` can also jump to the right line in the file if you invoke it from the staging panel, for example.
To tell lazygit which editor to use for the `e` command, the easiest way to do that is to provide an editPreset config, e.g.
```yaml
os:
editPreset: 'vscode'
```
Supported presets are `vim`, `nvim`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`,
`kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset
from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
Supported presets are `vim`, `nvim`, `nvim-remote`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
If for some reason you are not happy with the default commands from a preset, or
there simply is no preset for your editor, you can customize the commands by
setting the `edit`, `editAtLine`, and `editAtLineAndWait` options, e.g.:
`nvim-remote` is an experimental preset for when you have invoked lazygit from within a neovim process, allowing lazygit to open the file from within the parent process rather than spawning a new one.
If for some reason you are not happy with the default commands from a preset, or there simply is no preset for your editor, you can customize the commands by setting the `edit`, `editAtLine`, and `editAtLineAndWait` options, e.g.:
```yaml
os:
@@ -311,13 +334,11 @@ os:
openDirInEditor: 'myeditor {{dir}}'
```
The `editInTerminal` option is used to decide whether lazygit needs to suspend
itself to the background before calling the editor.
The `editInTerminal` option is used to decide whether lazygit needs to suspend itself to the background before calling the editor. It should really be named `suspend` because for some cases like when lazygit is opened from within a neovim session and you're using the `nvim-remote` preset, you're technically still in a terminal. Nonetheless we're sticking with the name `editInTerminal` for backwards compatibility.
Contributions of new editor presets are welcome; see the `getPreset` function in
[`editor_presets.go`](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/editor_presets.go).
Contributions of new editor presets are welcome; see the `getPreset` function in [`editor_presets.go`](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/editor_presets.go).
### Overriding default config file location
## Overriding default config file location
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.
@@ -331,6 +352,14 @@ or
LG_CONFIG_FILE="$HOME/.base_lg_conf,$HOME/.light_theme_lg_conf" lazygit
```
## Scroll-off Margin
When the selected line gets close to the bottom of the window and you hit down-arrow, there's a feature called "scroll-off margin" that lets the view scroll a little earlier so that you can see a bit of what's coming in the direction that you are moving. This is controlled by the `gui.scrollOffMargin` setting (default: 2), so it keeps 2 lines below the selection visible as you scroll down. It can be set to 0 to scroll only when the selection reaches the bottom of the window.
That's the behavior when `gui.scrollOffBehavior` is set to "margin" (the default). If you set `gui.scrollOffBehavior` to "jump", then upon reaching the last line of a view and hitting down-arrow the view will scroll by half a page so that the selection ends up in the middle of the view. This may feel a little jarring because the cursor jumps around when continuously moving down, but it has the advantage that the view doesn't scroll as often.
This setting applies both to all list views (e.g. commits and branches etc), and to the staging view.
## Color Attributes
For color attributes you can choose an array of attributes (with max one color attribute)
@@ -427,19 +456,18 @@ gui:
nerdFontsVersion: "3"
```
Supported versions are "2" and "3". The deprecated config `showIcons` sets the
version to "2" for backwards compatibility.
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`.
You can disable certain key bindings by specifying `<disabled>`.
```yaml
keybinding:
universal:
edit: null # disable 'edit file'
edit: <disabled> # disable 'edit file'
```
### Example Keybindings For Colemak Users
@@ -476,9 +504,7 @@ keybinding:
## 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:
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:
@@ -493,8 +519,7 @@ Where:
## 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.
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:
@@ -524,9 +549,7 @@ Result:
## Launching not in a repository behaviour
By default, when launching lazygit from a directory that is not a repository,
you will be prompted to choose if you would like to initialize a repo. You can
override this behaviour in the config with one of the following:
By default, when launching lazygit from a directory that is not a repository, you will be prompted to choose if you would like to initialize a repo. You can override this behaviour in the config with one of the following:
```yaml
# for default prompting behaviour
+23 -1
View File
@@ -1,6 +1,6 @@
# Custom Pagers
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `o` in the Status panel).
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `e` 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.
@@ -62,3 +62,25 @@ git:
```
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).
## Using external diff commands
Some diff tools can't work as a simple pager like the ones above do, because they need access to the entire diff, so just post-processing git's diff is not enough for them. The most notable example is probably [difftastic](https://difftastic.wilfred.me.uk).
These can be used in lazygit by using the `externalDiffCommand` config; in the case of difftastic, that could be
```yaml
git:
paging:
externalDiffCommand: difft --color=always
```
The `colorArg`, `pager`, and `useConfig` options are not used in this case.
You can add whatever extra arguments you prefer for your difftool; for instance
```yaml
git:
paging:
externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off
```
+1
View File
@@ -6,4 +6,5 @@
* [Keybindings](./keybindings)
* [Undo/Redo](./Undoing.md)
* [Searching/Filtering](./Searching.md)
* [Stacked Branches](./Stacked_Branches.md)
* [Dev docs](./dev)
+18
View File
@@ -0,0 +1,18 @@
# Working with stacked branches
When working on a large branch it can often be useful to break it down into
smaller pieces, and it can help to create separate branches for each independent
chunk of changes. For example, you could have one branch for preparatory
refactorings, one for backend changes, and one for frontend changes. Those
branches would then all be stacked onto each other.
Git has support for rebasing such a stack as a whole; you can enable it by
setting the git config `rebase.updateRfs` to true. If you then rebase the
topmost branch of the stack, the other ones in the stack will follow. This
includes interactive rebases, so for example amending a commit in the first
branch of the stack will "just work" in the sense that it keeps the other
branches properly stacked onto it.
Lazygit visualizes the invidual branch heads in the stack by marking them with a
cyan asterisk (or a cyan branch symbol if you are using [nerd
fonts](Config.md#display-nerd-fonts-icons)).
+3 -3
View File
@@ -1,9 +1,9 @@
# Undo/Redo in lazygit
![Gif](../../assets/undo2.gif)
You can undo the last action by pressing 'z' and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions.
Undo uses the reflog which is specific to commits and branches so we can't undo changes to the working tree or stash.
## Keybindings:
'z' to undo, 'ctrl+z' to redo
![undo](../../assets/demo/undo-compressed.gif)
## How it works
+82
View File
@@ -0,0 +1,82 @@
# Demo Recordings
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:
```
# for recording
npm i -g terminalizer
# for gif compression
npm i -g gifsicle
# for mp4 conversion
brew install ffmpeg
# font with icons
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.tar.xz && \
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:
```sh
scripts/record_demo.sh [gif|mp4] <path>
# e.g.
scripts/record_demo.sh gif pkg/integration/tests/demo/interactive_rebase.go
```
~~The gif format is for use in the first video of the readme (it has a larger size but has auto-play and looping)~~
~~The mp4 format is for everything else (no looping, requires clicking, but smaller size).~~
Turns out that you can't store mp4s in a repo and link them from a README so we're gonna just use gifs across the board for now.
### Including demos in README/docs
If you've followed the above steps you'll end up with your output in your assets worktree.
Within that worktree, stage all three output files and raise a PR against the assets branch.
Then back in the code branch, in the doc, you can embed the recording like so:
```md
![Nuke working tree](../assets/demo/interactive_rebase-compressed.gif)
```
This means we can update assets without needing to update the docs that embed them.
+1
View File
@@ -2,3 +2,4 @@
* [Busy/Idle tracking](./Busy.md).
* [Integration Tests](../../pkg/integration/README.md)
* [Demo Recordings](./Demo_Recordings.md)
+6 -5
View File
@@ -1,4 +1,4 @@
_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._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
# Lazygit Keybindings
@@ -84,6 +84,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: Move commit down one
<kbd>&lt;c-k&gt;</kbd>: Move commit up one
<kbd>v</kbd>: Paste commits (cherry-pick)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: Amend commit with staged changes
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: Revert commit
@@ -147,14 +148,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-y&gt;</kbd>: Copy pull request URL to clipboard
<kbd>c</kbd>: Checkout by name
<kbd>F</kbd>: Force checkout
<kbd>d</kbd>: Delete branch
<kbd>d</kbd>: View delete options
<kbd>r</kbd>: Rebase checked-out branch onto this branch
<kbd>M</kbd>: Merge into currently checked out branch
<kbd>f</kbd>: Fast-forward this branch from its upstream
<kbd>T</kbd>: Create tag
<kbd>g</kbd>: View reset options
<kbd>R</kbd>: Rename branch
<kbd>u</kbd>: Set/Unset upstream
<kbd>u</kbd>: View upstream options
<kbd>w</kbd>: View worktree options
<kbd>&lt;enter&gt;</kbd>: View commits
<kbd>/</kbd>: Filter the current view by text
@@ -254,7 +255,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>n</kbd>: New branch
<kbd>M</kbd>: Merge into currently checked out branch
<kbd>r</kbd>: Rebase checked-out branch onto this branch
<kbd>d</kbd>: Delete branch
<kbd>d</kbd>: Delete remote tag
<kbd>u</kbd>: Set as upstream of checked-out branch
<kbd>g</kbd>: View reset options
<kbd>w</kbd>: View worktree options
@@ -331,7 +332,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<pre>
<kbd>&lt;space&gt;</kbd>: Checkout
<kbd>d</kbd>: Delete tag
<kbd>d</kbd>: View delete options
<kbd>P</kbd>: Push tag
<kbd>n</kbd>: Create tag
<kbd>g</kbd>: View reset options
+6 -5
View File
@@ -1,4 +1,4 @@
_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._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
# Lazygit キーバインド
@@ -103,6 +103,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: コミットを1つ下に移動
<kbd>&lt;c-k&gt;</kbd>: コミットを1つ上に移動
<kbd>v</kbd>: コミットを貼り付け (cherry-pick)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: ステージされた変更でamendコミット
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: コミットをrevert
@@ -171,7 +172,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<pre>
<kbd>&lt;space&gt;</kbd>: チェックアウト
<kbd>d</kbd>: タグを削除
<kbd>d</kbd>: View delete options
<kbd>P</kbd>: タグをpush
<kbd>n</kbd>: タグを作成
<kbd>g</kbd>: View reset options
@@ -219,14 +220,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-y&gt;</kbd>: Pull RequestのURLをクリップボードにコピー
<kbd>c</kbd>: Checkout by name
<kbd>F</kbd>: Force checkout
<kbd>d</kbd>: ブランチを削除
<kbd>d</kbd>: View delete options
<kbd>r</kbd>: Rebase checked-out branch onto this branch
<kbd>M</kbd>: 現在のブランチにマージ
<kbd>f</kbd>: Fast-forward this branch from its upstream
<kbd>T</kbd>: タグを作成
<kbd>g</kbd>: View reset options
<kbd>R</kbd>: ブランチ名を変更
<kbd>u</kbd>: Set/Unset upstream
<kbd>u</kbd>: View upstream options
<kbd>w</kbd>: View worktree options
<kbd>&lt;enter&gt;</kbd>: コミットを閲覧
<kbd>/</kbd>: Filter the current view by text
@@ -319,7 +320,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>n</kbd>: 新しいブランチを作成
<kbd>M</kbd>: 現在のブランチにマージ
<kbd>r</kbd>: Rebase checked-out branch onto this branch
<kbd>d</kbd>: ブランチを削除
<kbd>d</kbd>: Delete remote tag
<kbd>u</kbd>: Set as upstream of checked-out branch
<kbd>g</kbd>: View reset options
<kbd>w</kbd>: View worktree options
+6 -5
View File
@@ -1,4 +1,4 @@
_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._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
# Lazygit 키 바인딩
@@ -183,14 +183,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-y&gt;</kbd>: 풀 리퀘스트 URL을 클립보드에 복사
<kbd>c</kbd>: 이름으로 체크아웃
<kbd>F</kbd>: 강제 체크아웃
<kbd>d</kbd>: 브랜치 삭제
<kbd>d</kbd>: View delete options
<kbd>r</kbd>: 체크아웃된 브랜치를 이 브랜치에 리베이스
<kbd>M</kbd>: 현재 브랜치에 병합
<kbd>f</kbd>: Fast-forward this branch from its upstream
<kbd>T</kbd>: 태그를 생성
<kbd>g</kbd>: View reset options
<kbd>R</kbd>: 브랜치 이름 변경
<kbd>u</kbd>: Set/Unset upstream
<kbd>u</kbd>: View upstream options
<kbd>w</kbd>: View worktree options
<kbd>&lt;enter&gt;</kbd>: 커밋 보기
<kbd>/</kbd>: Filter the current view by text
@@ -239,7 +239,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>n</kbd>: 새 브랜치 생성
<kbd>M</kbd>: 현재 브랜치에 병합
<kbd>r</kbd>: 체크아웃된 브랜치를 이 브랜치에 리베이스
<kbd>d</kbd>: 브랜치 삭제
<kbd>d</kbd>: Delete remote tag
<kbd>u</kbd>: Set as upstream of checked-out branch
<kbd>g</kbd>: View reset options
<kbd>w</kbd>: View worktree options
@@ -265,6 +265,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: 커밋을 1개 아래로 이동
<kbd>&lt;c-k&gt;</kbd>: 커밋을 1개 위로 이동
<kbd>v</kbd>: 커밋을 붙여넣기 (cherry-pick)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: Amend commit with staged changes
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: 커밋 되돌리기
@@ -308,7 +309,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<pre>
<kbd>&lt;space&gt;</kbd>: 체크아웃
<kbd>d</kbd>: 태그 삭제
<kbd>d</kbd>: View delete options
<kbd>P</kbd>: 태그를 push
<kbd>n</kbd>: 태그를 생성
<kbd>g</kbd>: View reset options
+6 -5
View File
@@ -1,4 +1,4 @@
_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._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
# Lazygit Sneltoetsen
@@ -90,14 +90,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-y&gt;</kbd>: Kopieer de URL van het pull-verzoek naar het klembord
<kbd>c</kbd>: Uitchecken bij naam
<kbd>F</kbd>: Forceer checkout
<kbd>d</kbd>: Verwijder branch
<kbd>d</kbd>: View delete options
<kbd>r</kbd>: Rebase branch
<kbd>M</kbd>: Merge in met huidige checked out branch
<kbd>f</kbd>: Fast-forward deze branch vanaf zijn upstream
<kbd>T</kbd>: Creëer tag
<kbd>g</kbd>: Bekijk reset opties
<kbd>R</kbd>: Hernoem branch
<kbd>u</kbd>: Set/Unset upstream
<kbd>u</kbd>: View upstream options
<kbd>w</kbd>: View worktree options
<kbd>&lt;enter&gt;</kbd>: Bekijk commits
<kbd>/</kbd>: Filter the current view by text
@@ -143,6 +143,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: Verplaats commit 1 naar beneden
<kbd>&lt;c-k&gt;</kbd>: Verplaats commit 1 naar boven
<kbd>v</kbd>: Plak commits (cherry-pick)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: Wijzig commit met staged veranderingen
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: Commit ongedaan maken
@@ -232,7 +233,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>n</kbd>: Nieuwe branch
<kbd>M</kbd>: Merge in met huidige checked out branch
<kbd>r</kbd>: Rebase branch
<kbd>d</kbd>: Verwijder branch
<kbd>d</kbd>: Delete remote tag
<kbd>u</kbd>: Stel in als upstream van uitgecheckte branch
<kbd>g</kbd>: Bekijk reset opties
<kbd>w</kbd>: View worktree options
@@ -331,7 +332,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<pre>
<kbd>&lt;space&gt;</kbd>: Uitchecken
<kbd>d</kbd>: Verwijder tag
<kbd>d</kbd>: View delete options
<kbd>P</kbd>: Push tag
<kbd>n</kbd>: Creëer tag
<kbd>g</kbd>: Bekijk reset opties
+6 -5
View File
@@ -1,4 +1,4 @@
_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._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
# Lazygit Keybindings
@@ -69,6 +69,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: Przenieś commit 1 w dół
<kbd>&lt;c-k&gt;</kbd>: Przenieś commit 1 w górę
<kbd>v</kbd>: Wklej commity (przebieranie)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: Popraw commit zmianami z poczekalni
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: Odwróć commit
@@ -105,14 +106,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-y&gt;</kbd>: Skopiuj adres URL żądania pobrania do schowka
<kbd>c</kbd>: Przełącz używając nazwy
<kbd>F</kbd>: Wymuś przełączenie
<kbd>d</kbd>: Usuń gałąź
<kbd>d</kbd>: View delete options
<kbd>r</kbd>: Zmiana bazy gałęzi
<kbd>M</kbd>: Scal do obecnej gałęzi
<kbd>f</kbd>: Fast-forward this branch from its upstream
<kbd>T</kbd>: Create tag
<kbd>g</kbd>: Wyświetl opcje resetu
<kbd>R</kbd>: Rename branch
<kbd>u</kbd>: Set/Unset upstream
<kbd>u</kbd>: View upstream options
<kbd>w</kbd>: View worktree options
<kbd>&lt;enter&gt;</kbd>: View commits
<kbd>/</kbd>: Filter the current view by text
@@ -231,7 +232,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>n</kbd>: Nowa gałąź
<kbd>M</kbd>: Scal do obecnej gałęzi
<kbd>r</kbd>: Zmiana bazy gałęzi
<kbd>d</kbd>: Usuń gałąź
<kbd>d</kbd>: Delete remote tag
<kbd>u</kbd>: Set as upstream of checked-out branch
<kbd>g</kbd>: Wyświetl opcje resetu
<kbd>w</kbd>: View worktree options
@@ -324,7 +325,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<pre>
<kbd>&lt;space&gt;</kbd>: Przełącz
<kbd>d</kbd>: Delete tag
<kbd>d</kbd>: View delete options
<kbd>P</kbd>: Push tag
<kbd>n</kbd>: Create tag
<kbd>g</kbd>: Wyświetl opcje resetu
+6 -5
View File
@@ -1,4 +1,4 @@
_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._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
# Lazygit Связки клавиш
@@ -151,6 +151,7 @@ _Связки клавиш_
<kbd>&lt;c-j&gt;</kbd>: Переместить коммит вниз на один
<kbd>&lt;c-k&gt;</kbd>: Переместить коммит вверх на один
<kbd>v</kbd>: Вставить отобранные коммиты (cherry-pick)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: Править последний коммит с проиндексированными изменениями
<kbd>a</kbd>: Установить/убрать автора коммита
<kbd>t</kbd>: Отменить коммит
@@ -180,14 +181,14 @@ _Связки клавиш_
<kbd>&lt;c-y&gt;</kbd>: Скопировать URL запроса на принятие изменений в буфер обмена
<kbd>c</kbd>: Переключить по названию
<kbd>F</kbd>: Принудительное переключение
<kbd>d</kbd>: Удалить ветку
<kbd>d</kbd>: View delete options
<kbd>r</kbd>: Перебазировать переключённую ветку на эту ветку
<kbd>M</kbd>: Слияние с текущей переключённой веткой
<kbd>f</kbd>: Перемотать эту ветку вперёд из её upstream-ветки
<kbd>T</kbd>: Создать тег
<kbd>g</kbd>: Просмотреть параметры сброса
<kbd>R</kbd>: Переименовать ветку
<kbd>u</kbd>: Установить/убрать upstream-ветку
<kbd>u</kbd>: View upstream options
<kbd>w</kbd>: View worktree options
<kbd>&lt;enter&gt;</kbd>: Просмотреть коммиты
<kbd>/</kbd>: Filter the current view by text
@@ -276,7 +277,7 @@ _Связки клавиш_
<pre>
<kbd>&lt;space&gt;</kbd>: Переключить
<kbd>d</kbd>: Удалить тег
<kbd>d</kbd>: View delete options
<kbd>P</kbd>: Отправить тег
<kbd>n</kbd>: Создать тег
<kbd>g</kbd>: Просмотреть параметры сброса
@@ -293,7 +294,7 @@ _Связки клавиш_
<kbd>n</kbd>: Новая ветка
<kbd>M</kbd>: Слияние с текущей переключённой веткой
<kbd>r</kbd>: Перебазировать переключённую ветку на эту ветку
<kbd>d</kbd>: Удалить ветку
<kbd>d</kbd>: Delete remote tag
<kbd>u</kbd>: Установить как upstream-ветку переключённую ветку
<kbd>g</kbd>: Просмотреть параметры сброса
<kbd>w</kbd>: View worktree options
+6 -5
View File
@@ -1,4 +1,4 @@
_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._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
# Lazygit 按键绑定
@@ -84,14 +84,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-y&gt;</kbd>: 将抓取请求 URL 复制到剪贴板
<kbd>c</kbd>: 按名称检出
<kbd>F</kbd>: 强制检出
<kbd>d</kbd>: 删除分支
<kbd>d</kbd>: View delete options
<kbd>r</kbd>: 将已检出的分支变基到该分支
<kbd>M</kbd>: 合并到当前检出的分支
<kbd>f</kbd>: 从上游快进此分支
<kbd>T</kbd>: 创建标签
<kbd>g</kbd>: 查看重置选项
<kbd>R</kbd>: 重命名分支
<kbd>u</kbd>: Set/Unset upstream
<kbd>u</kbd>: View upstream options
<kbd>w</kbd>: View worktree options
<kbd>&lt;enter&gt;</kbd>: 查看提交
<kbd>/</kbd>: Filter the current view by text
@@ -147,6 +147,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>&lt;c-j&gt;</kbd>: 下移提交
<kbd>&lt;c-k&gt;</kbd>: 上移提交
<kbd>v</kbd>: 粘贴提交(拣选)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: 用已暂存的更改来修补提交
<kbd>a</kbd>: Set/Reset commit author
<kbd>t</kbd>: 还原提交
@@ -233,7 +234,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<pre>
<kbd>&lt;space&gt;</kbd>: 检出
<kbd>d</kbd>: 删除标签
<kbd>d</kbd>: View delete options
<kbd>P</kbd>: 推送标签
<kbd>n</kbd>: 创建标签
<kbd>g</kbd>: 查看重置选项
@@ -333,7 +334,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>n</kbd>: 新分支
<kbd>M</kbd>: 合并到当前检出的分支
<kbd>r</kbd>: 将已检出的分支变基到该分支
<kbd>d</kbd>: 删除分支
<kbd>d</kbd>: Delete remote tag
<kbd>u</kbd>: 设置为检出分支的上游
<kbd>g</kbd>: 查看重置选项
<kbd>w</kbd>: View worktree options
+6 -5
View File
@@ -1,4 +1,4 @@
_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._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
# Lazygit 鍵盤快捷鍵
@@ -191,6 +191,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
<kbd>&lt;c-j&gt;</kbd>: 向下移動提交
<kbd>&lt;c-k&gt;</kbd>: 向上移動提交
<kbd>v</kbd>: 貼上提交 (揀選)
<kbd>B</kbd>: Mark commit as base commit for rebase
<kbd>A</kbd>: 使用已預存的更改修正提交
<kbd>a</kbd>: 設置/重設提交作者
<kbd>t</kbd>: 還原提交
@@ -255,14 +256,14 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
<kbd>&lt;c-y&gt;</kbd>: 複製拉取請求的 URL 到剪貼板
<kbd>c</kbd>: 根據名稱檢出
<kbd>F</kbd>: 強制檢出
<kbd>d</kbd>: 刪除分支
<kbd>d</kbd>: View delete options
<kbd>r</kbd>: 將已檢出的分支變基至此分支
<kbd>M</kbd>: 合併到當前檢出的分支
<kbd>f</kbd>: 從上游快進此分支
<kbd>T</kbd>: 建立標籤
<kbd>g</kbd>: 檢視重設選項
<kbd>R</kbd>: 重新命名分支
<kbd>u</kbd>: 設定/取消設定上游
<kbd>u</kbd>: View upstream options
<kbd>w</kbd>: View worktree options
<kbd>&lt;enter&gt;</kbd>: 檢視提交
<kbd>/</kbd>: Filter the current view by text
@@ -272,7 +273,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
<pre>
<kbd>&lt;space&gt;</kbd>: 檢出
<kbd>d</kbd>: 刪除標籤
<kbd>d</kbd>: View delete options
<kbd>P</kbd>: 推送標籤
<kbd>n</kbd>: 建立標籤
<kbd>g</kbd>: 檢視重設選項
@@ -343,7 +344,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
<kbd>n</kbd>: 新分支
<kbd>M</kbd>: 合併到當前檢出的分支
<kbd>r</kbd>: 將已檢出的分支變基至此分支
<kbd>d</kbd>: 刪除分支
<kbd>d</kbd>: Delete remote tag
<kbd>u</kbd>: 將此分支設為當前分支之上游
<kbd>g</kbd>: 檢視重設選項
<kbd>w</kbd>: View worktree options
+11 -12
View File
@@ -1,24 +1,21 @@
module github.com/jesseduffield/lazygit
go 1.18
go 1.20
require (
github.com/OpenPeeDeeP/xdg v1.0.0
github.com/atotto/clipboard v0.1.4
github.com/aybabtme/humanlog v0.4.1
github.com/cli/safeexec v1.0.0
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.11
github.com/fsmiamoto/git-todo-parser v0.0.5
github.com/fsnotify/fsnotify v1.4.7
github.com/gdamore/tcell/v2 v2.6.0
github.com/go-errors/errors v1.4.2
github.com/go-errors/errors v1.5.1
github.com/gookit/color v1.4.2
github.com/imdario/mergo v0.3.11
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144
github.com/jesseduffield/gocui v0.3.1-0.20230925062444-7cd0d7e2a70a
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
@@ -26,17 +23,21 @@ require (
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/kyokomi/emoji/v2 v2.2.8
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-runewidth v0.0.14
github.com/mattn/go-runewidth v0.0.15
github.com/mgutz/str v1.2.0
github.com/mitchellh/go-ps v1.0.0
github.com/pmezard/go-difflib v1.0.0
github.com/sahilm/fuzzy v0.1.0
github.com/samber/lo v1.31.0
github.com/sanity-io/litter v1.5.2
github.com/sasha-s/go-deadlock v0.3.1
github.com/sirupsen/logrus v1.4.2
github.com/spf13/afero v1.9.5
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68
github.com/stretchr/testify v1.8.0
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -64,13 +65,11 @@ require (
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/term v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
+17 -23
View File
@@ -55,8 +55,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
@@ -84,18 +82,14 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsmiamoto/git-todo-parser v0.0.5 h1:Bhzd/vz/6Qm3udfkd6NO9fWfD3TpwR9ucp3N75/J5I8=
github.com/fsmiamoto/git-todo-parser v0.0.5/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
@@ -185,8 +179,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144 h1:gwy5JzP6+PhcPFG1obkUSLGcTkUY88sLKlCPOFjwtak=
github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
github.com/jesseduffield/gocui v0.3.1-0.20230925062444-7cd0d7e2a70a h1:/Ifbvq4BWY5VKoyPnHIW5oRLiy00S3ebFtVIY2MlJm0=
github.com/jesseduffield/gocui v0.3.1-0.20230925062444-7cd0d7e2a70a/go.mod h1:trXE7RRGL2hTsv+Ntk+SHLtRobg9JE138n3Ug/X2Cf4=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
@@ -218,7 +212,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE=
github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -230,13 +223,15 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -246,15 +241,14 @@ github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
@@ -276,6 +270,8 @@ github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68 h1:NSTj9xAKUu85d6pAdNFyblL84BfiOB1rVnzxQO/cYUk=
github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68/go.mod h1:PuJ7T6QKbsU7iVOHlhRoV3D/ipIAJsyiV4dbwcVaYj8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
@@ -314,7 +310,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -458,14 +453,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -475,8 +469,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+9 -4
View File
@@ -13,7 +13,6 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/afero"
"github.com/jesseduffield/generics/slices"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -63,6 +62,7 @@ func Run(
func NewCommon(config config.AppConfigurer) (*common.Common, error) {
userConfig := config.GetUserConfig()
appState := config.GetAppState()
var err error
log := newLogger(config)
@@ -75,6 +75,7 @@ func NewCommon(config config.AppConfigurer) (*common.Common, error) {
Log: log,
Tr: tr,
UserConfig: userConfig,
AppState: appState,
Debug: config.GetDebug(),
Fs: afero.NewOsFs(),
}, nil
@@ -267,7 +268,11 @@ func (app *App) Run(startArgs appTypes.StartArgs) error {
// Close closes any resources
func (app *App) Close() error {
return slices.TryForEach(app.closers, func(closer io.Closer) error {
return closer.Close()
})
for _, closer := range app.closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
+2 -2
View File
@@ -5,12 +5,12 @@ import (
"fmt"
"log"
"os"
"os/exec"
"strconv"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -92,7 +92,7 @@ func getDaemonKind() DaemonKind {
}
func getCommentChar() byte {
cmd := secureexec.Command("git", "config", "--get", "--null", "core.commentChar")
cmd := exec.Command("git", "config", "--get", "--null", "core.commentChar")
if output, err := cmd.Output(); err == nil && len(output) == 2 {
return output[0]
}
+3 -3
View File
@@ -6,10 +6,10 @@ import (
"strings"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/samber/lo"
)
type TodoLine struct {
@@ -26,11 +26,11 @@ func (self *TodoLine) ToString() string {
}
func TodoLinesToString(todoLines []TodoLine) string {
lines := slices.Map(todoLines, func(todoLine TodoLine) string {
lines := lo.Map(todoLines, func(todoLine TodoLine, _ int) string {
return todoLine.ToString()
})
return strings.Join(slices.Reverse(lines), "")
return strings.Join(lo.Reverse(lines), "")
}
type ChangeTodoAction struct {
+14 -18
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"runtime/debug"
@@ -17,7 +18,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/env"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
"github.com/jesseduffield/lazygit/pkg/logs/tail"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"gopkg.in/yaml.v3"
@@ -63,8 +63,17 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
log.Fatal(absRepoPath + " is not a valid git repository.")
}
cliArgs.WorkTree = absRepoPath
cliArgs.GitDir = filepath.Join(absRepoPath, ".git")
err = os.Chdir(absRepoPath)
if err != nil {
log.Fatalf("Failed to change directory to %s: %v", absRepoPath, err)
}
} else if cliArgs.WorkTree != "" {
env.SetWorkTreeEnv(cliArgs.WorkTree)
if err := os.Chdir(cliArgs.WorkTree); err != nil {
log.Fatalf("Failed to change directory to %s: %v", cliArgs.WorkTree, err)
}
}
if cliArgs.CustomConfigFile != "" {
@@ -75,13 +84,6 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
os.Setenv("CONFIG_DIR", cliArgs.UseConfigDir)
}
if cliArgs.WorkTree != "" {
err := os.Chdir(cliArgs.WorkTree)
if err != nil {
log.Fatalf("Failed to change directory to %s: %v", cliArgs.WorkTree, err)
}
}
if cliArgs.GitDir != "" {
env.SetGitDirEnv(cliArgs.GitDir)
}
@@ -118,12 +120,6 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
os.Exit(0)
}
if cliArgs.WorkTree != "" {
if err := os.Chdir(cliArgs.WorkTree); err != nil {
log.Fatal(err.Error())
}
}
tempDir, err := os.MkdirTemp("", "lazygit-*")
if err != nil {
log.Fatal(err.Error())
@@ -184,10 +180,10 @@ func parseCliArgsAndEnvVars() *cliArgs {
useConfigDir := ""
flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory")
workTree := ""
workTree := os.Getenv("GIT_WORK_TREE")
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
gitDir := ""
gitDir := os.Getenv("GIT_DIR")
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
customConfigFile := ""
@@ -280,7 +276,7 @@ func mergeBuildInfo(buildInfo *BuildInfo) {
}
func getGitVersionInfo() string {
cmd := secureexec.Command("git", "--version")
cmd := exec.Command("git", "--version")
stdout, _ := cmd.Output()
gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n")
return gitVersion
+3 -3
View File
@@ -3,8 +3,8 @@ package app
import (
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/samber/lo"
)
type errorMapping struct {
@@ -18,7 +18,7 @@ func knownError(tr *i18n.TranslationSet, err error) (string, bool) {
knownErrorMessages := []string{tr.MinGitVersionError}
if slices.Contains(knownErrorMessages, errorMessage) {
if lo.Contains(knownErrorMessages, errorMessage) {
return errorMessage, true
}
@@ -29,7 +29,7 @@ func knownError(tr *i18n.TranslationSet, err error) (string, bool) {
},
}
if mapping, ok := slices.Find(mappings, func(mapping errorMapping) bool {
if mapping, ok := lo.Find(mappings, func(mapping errorMapping) bool {
return strings.Contains(errorMessage, mapping.originalError)
}); ok {
return mapping.newError, true
-77
View File
@@ -1,77 +0,0 @@
package cheatsheet
import (
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"regexp"
"github.com/pmezard/go-difflib/difflib"
)
func Check() {
dir := GetKeybindingsDir()
tmpDir := filepath.Join(os.TempDir(), "lazygit_cheatsheet")
err := os.RemoveAll(tmpDir)
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
err = os.Mkdir(tmpDir, 0o700)
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
generateAtDir(tmpDir)
defer os.RemoveAll(tmpDir)
actualContent := obtainContent(dir)
expectedContent := obtainContent(tmpDir)
if expectedContent == "" {
log.Fatal("empty expected content")
}
if actualContent != expectedContent {
err := difflib.WriteUnifiedDiff(os.Stdout, difflib.UnifiedDiff{
A: difflib.SplitLines(expectedContent),
B: difflib.SplitLines(actualContent),
FromFile: "Expected",
FromDate: "",
ToFile: "Actual",
ToDate: "",
Context: 1,
})
if err != nil {
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())
os.Exit(1)
}
fmt.Println("\nCheatsheets are up to date")
}
func obtainContent(dir string) string {
re := regexp.MustCompile(`Keybindings_\w+\.md$`)
content := ""
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if re.MatchString(path) {
bytes, err := os.ReadFile(path)
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
content += fmt.Sprintf("\n%s\n\n", filepath.Base(path))
content += string(bytes)
}
return nil
})
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
return content
}
+11 -9
View File
@@ -1,10 +1,12 @@
// This "script" generates a file called Keybindings_{{.LANG}}.md
// in current working directory.
//go:generate go run generator.go
// This "script" generates files called Keybindings_{{.LANG}}.md
// in the docs/keybindings directory.
//
// The content of this generated file is a keybindings cheatsheet.
// The content of these generated files is a keybindings cheatsheet.
//
// To generate cheatsheet in english run:
// go run scripts/generate_cheatsheet.go
// To generate the cheatsheets, run:
// go generate pkg/cheatsheet/generate.go
package cheatsheet
@@ -15,7 +17,6 @@ import (
"strings"
"github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazygit/pkg/app"
"github.com/jesseduffield/lazygit/pkg/config"
@@ -23,6 +24,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/samber/lo"
"golang.org/x/exp/slices"
)
type bindingSection struct {
@@ -42,7 +44,7 @@ type headerWithBindings struct {
}
func CommandToRun() string {
return "go run scripts/cheatsheet/main.go generate"
return "go generate ./..."
}
func GetKeybindingsDir() string {
@@ -129,7 +131,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection {
excludedViews := []string{"stagingSecondary", "patchBuildingSecondary"}
bindingsToDisplay := slices.Filter(bindings, func(binding *types.Binding) bool {
bindingsToDisplay := lo.Filter(bindings, func(binding *types.Binding, _ int) bool {
if lo.Contains(excludedViews, binding.ViewName) {
return false
}
@@ -162,7 +164,7 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
return a.header.title < b.header.title
})
return slices.Map(bindingGroups, func(hb headerWithBindings) *bindingSection {
return lo.Map(bindingGroups, func(hb headerWithBindings, _ int) *bindingSection {
return &bindingSection{
title: hb.header.title,
bindings: hb.bindings,
+14
View File
@@ -0,0 +1,14 @@
//go:build ignore
package main
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/cheatsheet"
)
func main() {
fmt.Printf("Generating cheatsheets in %s...\n", cheatsheet.GetKeybindingsDir())
cheatsheet.Generate()
}
+5 -8
View File
@@ -7,7 +7,6 @@ import (
"strings"
"github.com/go-errors/errors"
"github.com/sasha-s/go-deadlock"
"github.com/spf13/afero"
gogit "github.com/jesseduffield/go-git/v5"
@@ -48,6 +47,7 @@ type GitCommand struct {
type Loaders struct {
BranchLoader *git_commands.BranchLoader
MergedBranchLoader *git_commands.MergedBranchLoader
CommitFileLoader *git_commands.CommitFileLoader
CommitLoader *git_commands.CommitLoader
FileLoader *git_commands.FileLoader
@@ -63,7 +63,6 @@ func NewGitCommand(
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
syncMutex *deadlock.Mutex,
) (*GitCommand, error) {
currentPath, err := os.Getwd()
if err != nil {
@@ -118,7 +117,6 @@ func NewGitCommand(
gitConfig,
repoPaths,
repository,
syncMutex,
), nil
}
@@ -129,7 +127,6 @@ func NewGitCommandAux(
gitConfig git_config.IGitConfig,
repoPaths *git_commands.RepoPaths,
repo *gogit.Repository,
syncMutex *deadlock.Mutex,
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
@@ -140,7 +137,7 @@ func NewGitCommandAux(
// common ones are: cmn, osCommand, dotGitDir, configCommands
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands, syncMutex)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands)
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
statusCommands := git_commands.NewStatusCommands(gitCommon)
@@ -159,15 +156,14 @@ func NewGitCommandAux(
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
patchBuilder := patch.NewPatchBuilder(cmn.Log,
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
// TODO: make patch builder take Gui.IgnoreWhitespaceInDiffView into
// account. For now we just pass false.
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, plain, false)
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, plain)
})
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
bisectCommands := git_commands.NewBisectCommands(gitCommon)
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
mergedBranchLoader := git_commands.NewMergedBranchLoader(gitCommon)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
@@ -206,6 +202,7 @@ func NewGitCommandAux(
Worktrees: worktreeLoader,
StashLoader: stashLoader,
TagLoader: tagLoader,
MergedBranchLoader: mergedBranchLoader,
},
RepoPaths: repoPaths,
}
+9
View File
@@ -125,6 +125,15 @@ func (self *BisectCommands) Start() error {
return self.cmd.New(cmdArgs).StreamOutput().Run()
}
func (self *BisectCommands) StartWithTerms(oldTerm string, newTerm string) error {
cmdArgs := NewGitCmd("bisect").Arg("start").
Arg("--term-old=" + oldTerm).
Arg("--term-new=" + newTerm).
ToArgv()
return self.cmd.New(cmdArgs).StreamOutput().Run()
}
// tells us whether we've found our problem commit(s). We return a string slice of
// commit sha's if we're done, and that slice may have more that one item if
// skipped commits are involved.
+2 -2
View File
@@ -2,7 +2,7 @@ package git_commands
import (
"github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/generics/slices"
"github.com/samber/lo"
"github.com/sirupsen/logrus"
)
@@ -97,5 +97,5 @@ func (self *BisectInfo) Bisecting() bool {
return false
}
return slices.Contains(maps.Values(self.statusMap), BisectStatusOld)
return lo.Contains(maps.Values(self.statusMap), BisectStatusOld)
}
+17 -2
View File
@@ -70,8 +70,23 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
}, nil
}
// Delete delete branch
func (self *BranchCommands) Delete(branch string, force bool) error {
// CurrentBranchName get name of current branch
func (self *BranchCommands) CurrentBranchName() (string, error) {
cmdArgs := NewGitCmd("rev-parse").
Arg("--abbrev-ref").
Arg("--verify").
Arg("HEAD").
ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err == nil {
return strings.TrimSpace(output), nil
}
return "", err
}
// LocalDelete delete branch locally
func (self *BranchCommands) LocalDelete(branch string, force bool) error {
cmdArgs := NewGitCmd("branch").
ArgIfElse(force, "-D", "-d").
Arg(branch).
+7 -7
View File
@@ -6,13 +6,13 @@ import (
"strings"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/go-git/v5/config"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"golang.org/x/exp/slices"
)
// context:
@@ -75,7 +75,7 @@ outer:
if strings.EqualFold(reflogBranch.Name, branch.Name) {
branch.Recency = reflogBranch.Recency
branchesWithRecency = append(branchesWithRecency, branch)
branches = slices.Remove(branches, j)
branches = utils.Remove(branches, j)
continue outer
}
}
@@ -87,14 +87,14 @@ outer:
return a.Name < b.Name
})
branches = slices.Prepend(branches, branchesWithRecency...)
branches = utils.Prepend(branches, branchesWithRecency...)
foundHead := false
for i, branch := range branches {
if branch.Head {
foundHead = true
branch.Recency = " *"
branches = slices.Move(branches, i, 0)
branches = utils.Move(branches, i, 0)
break
}
}
@@ -103,7 +103,7 @@ outer:
if err != nil {
return nil, err
}
branches = slices.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
branches = utils.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
}
configBranches, err := self.config.Branches()
@@ -131,7 +131,7 @@ func (self *BranchLoader) obtainBranches() []*models.Branch {
trimmedOutput := strings.TrimSpace(output)
outputLines := strings.Split(trimmedOutput, "\n")
return slices.FilterMap(outputLines, func(line string) (*models.Branch, bool) {
return lo.FilterMap(outputLines, func(line string, _ int) (*models.Branch, bool) {
if line == "" {
return nil, false
}
@@ -171,7 +171,7 @@ var branchFields = []string{
"upstream:short",
"upstream:track",
"subject",
fmt.Sprintf("objectname:short=%d", utils.COMMIT_HASH_SHORT_SIZE),
"objectname",
}
// Obtain branch information from parsed line output of getRawBranches()
+1 -1
View File
@@ -93,7 +93,7 @@ func TestBranchDeleteBranch(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner})
s.test(instance.Delete("test", s.force))
s.test(instance.LocalDelete("test", s.force))
s.runner.CheckForMissingCalls()
})
}
+35 -3
View File
@@ -38,6 +38,22 @@ func (self *CommitCommands) SetAuthor(value string) error {
return self.cmd.New(cmdArgs).Run()
}
// Add a commit's coauthor using Github/Gitlab Co-authored-by metadata. Value is expected to be of the form 'Name <Email>'
func (self *CommitCommands) AddCoAuthor(sha string, value string) error {
message, err := self.GetCommitMessage(sha)
if err != nil {
return err
}
message = message + fmt.Sprintf("\nCo-authored-by: %s", value)
cmdArgs := NewGitCmd("commit").
Arg("--allow-empty", "--amend", "--only", "-m", message).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
// ResetToCommit reset to commit
func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error {
cmdArgs := NewGitCmd("reset").Arg("--"+strength, sha).ToArgv()
@@ -68,6 +84,19 @@ func (self *CommitCommands) RewordLastCommitInEditorCmdObj() oscommands.ICmdObj
return self.cmd.New(NewGitCmd("commit").Arg("--allow-empty", "--amend", "--only").ToArgv())
}
func (self *CommitCommands) RewordLastCommitInEditorWithMessageFileCmdObj(tmpMessageFile string) oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("commit").
Arg("--allow-empty", "--amend", "--only", "--edit", "--file="+tmpMessageFile).ToArgv())
}
func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile string) oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("commit").
Arg("--edit").
Arg("--file="+tmpMessageFile).
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
ToArgv())
}
// RewordLastCommit rewords the topmost commit with the given message
func (self *CommitCommands) RewordLastCommit(summary string, description string) error {
messageArgs := self.commitMessageArgs(summary, description)
@@ -183,10 +212,13 @@ func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
return self.cmd.New(cmdArgs)
}
func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhitespace bool) oscommands.ICmdObj {
contextSize := self.UserConfig.Git.DiffContextSize
func (self *CommitCommands) ShowCmdObj(sha string, filterPath string) oscommands.ICmdObj {
contextSize := self.AppState.DiffContextSize
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
cmdArgs := NewGitCmd("show").
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
Arg(fmt.Sprintf("--unified=%d", contextSize)).
@@ -194,7 +226,7 @@ func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhit
Arg("--decorate").
Arg("-p").
Arg(sha).
ArgIf(ignoreWhitespace, "--ignore-all-space").
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
ArgIf(filterPath != "", "--", filterPath).
ToArgv()
@@ -3,7 +3,6 @@ package git_commands
import (
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
@@ -52,7 +51,7 @@ func getCommitFilesFromFilenames(filenames string) []*models.CommitFile {
}
// typical result looks like 'A my_file' meaning my_file was added
return slices.Map(lo.Chunk(lines, 2), func(chunk []string) *models.CommitFile {
return lo.Map(lo.Chunk(lines, 2), func(chunk []string, _ int) *models.CommitFile {
return &models.CommitFile{
ChangeStatus: chunk[0],
Name: chunk[1],
+74 -59
View File
@@ -6,12 +6,12 @@ import (
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
@@ -66,8 +66,11 @@ type GetCommitsOptions struct {
FilterPath string
IncludeRebaseCommits bool
RefName string // e.g. "HEAD" or "my_branch"
RefForPushedStatus string // the ref to use for determining pushed/unpushed status
// determines if we show the whole git graph i.e. pass the '--all' flag
All bool
// If non-empty, show divergence from this ref (left-right log)
RefToShowDivergenceFrom string
}
// GetCommits obtains the commits of the current branch
@@ -93,22 +96,26 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
defer wg.Done()
logErr = self.getLogCmd(opts).RunAndProcessLines(func(line string) (bool, error) {
commit := self.extractCommitFromLine(line)
commit := self.extractCommitFromLine(line, opts.RefToShowDivergenceFrom != "")
commits = append(commits, commit)
return false, nil
})
})
var ancestor string
var remoteAncestor string
go utils.Safe(func() {
defer wg.Done()
ancestor = self.getMergeBase(opts.RefName)
if opts.RefToShowDivergenceFrom != "" {
remoteAncestor = self.getMergeBase(opts.RefToShowDivergenceFrom)
}
})
passedFirstPushedCommit := false
// I can get this before
firstPushedCommit, err := self.getFirstPushedCommit(opts.RefName)
firstPushedCommit, err := self.getFirstPushedCommit(opts.RefForPushedStatus)
if err != nil {
// must have no upstream branch so we'll consider everything as pushed
passedFirstPushedCommit = true
@@ -124,10 +131,12 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
if commit.Sha == firstPushedCommit {
passedFirstPushedCommit = true
}
if passedFirstPushedCommit {
commit.Status = models.StatusPushed
} else {
commit.Status = models.StatusUnpushed
if commit.Status != models.StatusRebasing {
if passedFirstPushedCommit {
commit.Status = models.StatusPushed
} else {
commit.Status = models.StatusUnpushed
}
}
}
@@ -135,8 +144,23 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
return commits, nil
}
if ancestor != "" {
commits = self.setCommitMergedStatuses(ancestor, commits)
if opts.RefToShowDivergenceFrom != "" {
sort.SliceStable(commits, func(i, j int) bool {
// In the divergence view we want incoming commits to come first
return commits[i].Divergence > commits[j].Divergence
})
_, localSectionStart, found := lo.FindIndexOf(commits, func(commit *models.Commit) bool {
return commit.Divergence == models.DivergenceLeft
})
if !found {
localSectionStart = len(commits)
}
setCommitMergedStatuses(remoteAncestor, commits[:localSectionStart])
setCommitMergedStatuses(ancestor, commits[localSectionStart:])
} else {
setCommitMergedStatuses(ancestor, commits)
}
return commits, nil
@@ -177,8 +201,8 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
// then puts them into a commit object
// example input:
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
split := strings.SplitN(line, "\x00", 7)
func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool) *models.Commit {
split := strings.SplitN(line, "\x00", 8)
sha := split[0]
unixTimestamp := split[1]
@@ -187,6 +211,10 @@ func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
extraInfo := strings.TrimSpace(split[4])
parentHashes := split[5]
message := split[6]
divergence := models.DivergenceNone
if showDivergence {
divergence = lo.Ternary(split[7] == "<", models.DivergenceLeft, models.DivergenceRight)
}
tags := []string{}
@@ -220,6 +248,7 @@ func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
AuthorName: authorName,
AuthorEmail: authorEmail,
Parents: parents,
Divergence: divergence,
}
}
@@ -233,7 +262,7 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
return nil, nil
}
commitShas := slices.FilterMap(commits, func(commit *models.Commit) (string, bool) {
commitShas := lo.FilterMap(commits, func(commit *models.Commit, _ int) (string, bool) {
return commit.Sha, commit.Sha != ""
})
@@ -249,7 +278,7 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
fullCommits := map[string]*models.Commit{}
err = cmdObj.RunAndProcessLines(func(line string) (bool, error) {
commit := self.extractCommitFromLine(line)
commit := self.extractCommitFromLine(line, false)
fullCommits[commit.Sha] = commit
return false, nil
})
@@ -379,7 +408,7 @@ func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, err
// Command does not have a commit associated, skip
continue
}
commits = slices.Prepend(commits, &models.Commit{
commits = utils.Prepend(commits, &models.Commit{
Sha: t.Commit,
Name: t.Msg,
Status: models.StatusRebasing,
@@ -493,10 +522,15 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
}
}
func (self *CommitLoader) setCommitMergedStatuses(ancestor string, commits []*models.Commit) []*models.Commit {
func setCommitMergedStatuses(ancestor string, commits []*models.Commit) {
if ancestor == "" {
return
}
passedAncestor := false
for i, commit := range commits {
if strings.HasPrefix(ancestor, commit.Sha) {
// some commits aren't really commits and don't have sha's, such as the update-ref todo
if commit.Sha != "" && strings.HasPrefix(ancestor, commit.Sha) {
passedAncestor = true
}
if commit.Status != models.StatusPushed && commit.Status != models.StatusUnpushed {
@@ -506,7 +540,6 @@ func (self *CommitLoader) setCommitMergedStatuses(ancestor string, commits []*mo
commits[i].Status = models.StatusMerged
}
}
return commits
}
func (self *CommitLoader) getMergeBase(refName string) string {
@@ -535,50 +568,26 @@ func (self *CommitLoader) getMergeBase(refName string) string {
}
func (self *CommitLoader) getExistingMainBranches() []string {
var existingBranches []string
var wg sync.WaitGroup
mainBranches := self.UserConfig.Git.MainBranches
existingBranches = make([]string, len(mainBranches))
for i, branchName := range mainBranches {
wg.Add(1)
i := i
branchName := branchName
go utils.Safe(func() {
defer wg.Done()
existingBranches := utils.ConcurrentMap(mainBranches, func(mainBranch string) string {
// Try to determine upstream of local main branch
if ref, err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--symbolic-full-name", mainBranch+"@{u}").ToArgv(),
).DontLog().RunWithOutput(); err == nil {
return strings.TrimSpace(ref)
}
// Try to determine upstream of local main branch
if ref, err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
).DontLog().RunWithOutput(); err == nil {
existingBranches[i] = strings.TrimSpace(ref)
return
}
// If this failed, fallback to the local branch
ref := "refs/heads/" + mainBranch
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
return ref
}
// If this failed, a local branch for this main branch doesn't exist or it
// has no upstream configured. Try looking for one in the "origin" remote.
ref := "refs/remotes/origin/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
return
}
// If this failed as well, try if we have the main branch as a local
// branch. This covers the case where somebody is using git locally
// for something, but never pushing anywhere.
ref = "refs/heads/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
}
})
}
wg.Wait()
return ""
})
existingBranches = lo.Filter(existingBranches, func(branch string, _ int) bool {
return branch != ""
@@ -619,8 +628,13 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
config := self.UserConfig.Git.Log
refSpec := opts.RefName
if opts.RefToShowDivergenceFrom != "" {
refSpec += "..." + opts.RefToShowDivergenceFrom
}
cmdArgs := NewGitCmd("log").
Arg(opts.RefName).
Arg(refSpec).
ArgIf(config.Order != "default", "--"+config.Order).
ArgIf(opts.All, "--all").
Arg("--oneline").
@@ -629,6 +643,7 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
ArgIf(opts.Limit, "-300").
ArgIf(opts.FilterPath != "", "--follow").
Arg("--no-show-signature").
ArgIf(opts.RefToShowDivergenceFrom != "", "--left-right").
Arg("--").
ArgIf(opts.FilterPath != "", opts.FilterPath).
ToArgv()
@@ -636,4 +651,4 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
return self.cmd.New(cmdArgs).DontLog()
}
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s`
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m`
+72 -29
View File
@@ -42,10 +42,10 @@ func TestGetCommits(t *testing.T) {
testName: "should return no commits if there are none",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{},
expectedError: nil,
@@ -54,10 +54,10 @@ func TestGetCommits(t *testing.T) {
testName: "should use proper upstream name for branch",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: "refs/heads/mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{},
expectedError: nil,
@@ -66,20 +66,18 @@ func TestGetCommits(t *testing.T) {
testName: "should return commits if they are present",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main", "develop"},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
// here it's testing which of the configured main branches have an upstream
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", nil). // yep, origin/main exists
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
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "refs/remotes/origin/main", nil). // this one does too
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so check 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'
ExpectGitArgs([]string{"merge-base", "HEAD", "refs/remotes/origin/master", "refs/remotes/origin/main"}, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
@@ -203,19 +201,17 @@ func TestGetCommits(t *testing.T) {
testName: "should not call merge-base for mainBranches if none exist",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main"},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist; neither does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")),
expectedCommits: []*models.Commit{
@@ -240,17 +236,16 @@ func TestGetCommits(t *testing.T) {
testName: "should call merge-base for all main branches that exist",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "refs/remotes/origin/develop", nil).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "1.0-hotfixes@{u}"}, "refs/remotes/origin/1.0-hotfixes", nil).
@@ -279,10 +274,10 @@ func TestGetCommits(t *testing.T) {
testName: "should not specify order if `log.order` is `default`",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{},
expectedError: nil,
@@ -291,10 +286,10 @@ func TestGetCommits(t *testing.T) {
testName: "should set filter path",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", FilterPath: "src"},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
expectedCommits: []*models.Commit{},
expectedError: nil,
@@ -506,3 +501,51 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
})
}
}
func TestCommitLoader_setCommitMergedStatuses(t *testing.T) {
type scenario struct {
testName string
commits []*models.Commit
ancestor string
expectedCommits []*models.Commit
}
scenarios := []scenario{
{
testName: "basic",
commits: []*models.Commit{
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Sha: "67890", Name: "2", Action: models.ActionNone, Status: models.StatusPushed},
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
},
ancestor: "67890",
expectedCommits: []*models.Commit{
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Sha: "67890", Name: "2", Action: models.ActionNone, Status: models.StatusMerged},
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusMerged},
},
},
{
testName: "with update-ref",
commits: []*models.Commit{
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Sha: "", Name: "", Action: todo.UpdateRef, Status: models.StatusNone},
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
},
ancestor: "deadbeef",
expectedCommits: []*models.Commit{
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Sha: "", Name: "", Action: todo.UpdateRef, Status: models.StatusNone},
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
},
},
}
for _, scenario := range scenarios {
t.Run(scenario.testName, func(t *testing.T) {
expectedCommits := scenario.commits
setCommitMergedStatuses(scenario.ancestor, expectedCommits)
assert.Equal(t, scenario.expectedCommits, expectedCommits)
})
}
}
+23 -7
View File
@@ -186,6 +186,7 @@ func TestCommitShowCmdObj(t *testing.T) {
filterPath string
contextSize int
ignoreWhitespace bool
extDiffCmd string
expected []string
}
@@ -195,28 +196,40 @@ func TestCommitShowCmdObj(t *testing.T) {
filterPath: "",
contextSize: 3,
ignoreWhitespace: false,
expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
extDiffCmd: "",
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
},
{
testName: "Default case with filter path",
filterPath: "file.txt",
contextSize: 3,
ignoreWhitespace: false,
expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
extDiffCmd: "",
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
},
{
testName: "Show diff with custom context size",
filterPath: "",
contextSize: 77,
ignoreWhitespace: false,
expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"},
extDiffCmd: "",
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"},
},
{
testName: "Show diff, ignoring whitespace",
filterPath: "",
contextSize: 77,
ignoreWhitespace: true,
expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"},
extDiffCmd: "",
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"},
},
{
testName: "Show diff with external diff command",
filterPath: "",
contextSize: 3,
ignoreWhitespace: false,
extDiffCmd: "difft --color=always",
expected: []string{"-c", "diff.external=difft --color=always", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
},
}
@@ -224,12 +237,15 @@ func TestCommitShowCmdObj(t *testing.T) {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
userConfig.Git.DiffContextSize = s.contextSize
userConfig.Git.Paging.ExternalDiffCommand = s.extDiffCmd
appState := &config.AppState{}
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil)
instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})
instance := buildCommitCommands(commonDeps{userConfig: userConfig, appState: appState, runner: runner})
assert.NoError(t, instance.ShowCmdObj("1234567890", s.filterPath, s.ignoreWhitespace).Run())
assert.NoError(t, instance.ShowCmdObj("1234567890", s.filterPath).Run())
runner.CheckForMissingCalls()
})
}
-5
View File
@@ -4,7 +4,6 @@ import (
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/sasha-s/go-deadlock"
)
type GitCommon struct {
@@ -15,8 +14,6 @@ type GitCommon struct {
repoPaths *RepoPaths
repo *gogit.Repository
config *ConfigCommands
// mutex for doing things like push/pull/fetch
syncMutex *deadlock.Mutex
}
func NewGitCommon(
@@ -27,7 +24,6 @@ func NewGitCommon(
repoPaths *RepoPaths,
repo *gogit.Repository,
config *ConfigCommands,
syncMutex *deadlock.Mutex,
) *GitCommon {
return &GitCommon{
Common: cmn,
@@ -37,6 +33,5 @@ func NewGitCommon(
repoPaths: repoPaths,
repo: repo,
config: config,
syncMutex: syncMutex,
}
}
+4
View File
@@ -107,3 +107,7 @@ func (self *ConfigCommands) GetCoreCommentChar() byte {
return '#'
}
func (self *ConfigCommands) GetRebaseUpdateRefs() bool {
return self.gitConfig.GetBool("rebase.updateRefs")
}
+2 -1
View File
@@ -17,6 +17,7 @@ import (
type commonDeps struct {
runner *oscommands.FakeCmdObjRunner
userConfig *config.UserConfig
appState *config.AppState
gitVersion *GitVersion
gitConfig *git_config.FakeGitConfig
getenv func(string) string
@@ -32,7 +33,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
gitCommon.Common = deps.common
if gitCommon.Common == nil {
gitCommon.Common = utils.NewDummyCommonWithUserConfig(deps.userConfig)
gitCommon.Common = utils.NewDummyCommonWithUserConfigAndAppState(deps.userConfig, deps.appState)
}
if deps.fs != nil {
+7 -7
View File
@@ -83,14 +83,14 @@ func (self *FileCommands) GetEditCmdStr(filename string) (string, bool) {
}
}
template, editInTerminal := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
template, suspend := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
templateValues := map[string]string{
"filename": self.cmd.Quote(filename),
}
cmdStr := utils.ResolvePlaceholderString(template, templateValues)
return cmdStr, editInTerminal
return cmdStr, suspend
}
func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) {
@@ -101,7 +101,7 @@ func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (
}
}
template, editInTerminal := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
templateValues := map[string]string{
"filename": self.cmd.Quote(filename),
@@ -109,7 +109,7 @@ func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (
}
cmdStr := utils.ResolvePlaceholderString(template, templateValues)
return cmdStr, editInTerminal
return cmdStr, suspend
}
func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string {
@@ -131,15 +131,15 @@ func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber
return cmdStr
}
func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) string {
template := config.GetOpenDirInEditorTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) (string, bool) {
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
templateValues := map[string]string{
"dir": self.cmd.Quote(path),
}
cmdStr := utils.ResolvePlaceholderString(template, templateValues)
return cmdStr
return cmdStr, suspend
}
func (self *FileCommands) guessDefaultEditor() string {
+30 -30
View File
@@ -179,34 +179,34 @@ func TestEditFileCmdStrLegacy(t *testing.T) {
func TestEditFileCmd(t *testing.T) {
type scenario struct {
filename string
osConfig config.OSConfig
expectedCmdStr string
expectedEditInTerminal bool
filename string
osConfig config.OSConfig
expectedCmdStr string
suspend bool
}
scenarios := []scenario{
{
filename: "test",
osConfig: config.OSConfig{},
expectedCmdStr: `vim -- "test"`,
expectedEditInTerminal: true,
filename: "test",
osConfig: config.OSConfig{},
expectedCmdStr: `vim -- "test"`,
suspend: true,
},
{
filename: "test",
osConfig: config.OSConfig{
Edit: "nano {{filename}}",
},
expectedCmdStr: `nano "test"`,
expectedEditInTerminal: true,
expectedCmdStr: `nano "test"`,
suspend: true,
},
{
filename: "file/with space",
osConfig: config.OSConfig{
EditPreset: "sublime",
},
expectedCmdStr: `subl -- "file/with space"`,
expectedEditInTerminal: false,
expectedCmdStr: `subl -- "file/with space"`,
suspend: false,
},
}
@@ -218,28 +218,28 @@ func TestEditFileCmd(t *testing.T) {
userConfig: userConfig,
})
cmdStr, editInTerminal := instance.GetEditCmdStr(s.filename)
cmdStr, suspend := instance.GetEditCmdStr(s.filename)
assert.Equal(t, s.expectedCmdStr, cmdStr)
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
assert.Equal(t, s.suspend, suspend)
}
}
func TestEditFileAtLineCmd(t *testing.T) {
type scenario struct {
filename string
lineNumber int
osConfig config.OSConfig
expectedCmdStr string
expectedEditInTerminal bool
filename string
lineNumber int
osConfig config.OSConfig
expectedCmdStr string
suspend bool
}
scenarios := []scenario{
{
filename: "test",
lineNumber: 42,
osConfig: config.OSConfig{},
expectedCmdStr: `vim +42 -- "test"`,
expectedEditInTerminal: true,
filename: "test",
lineNumber: 42,
osConfig: config.OSConfig{},
expectedCmdStr: `vim +42 -- "test"`,
suspend: true,
},
{
filename: "test",
@@ -247,8 +247,8 @@ func TestEditFileAtLineCmd(t *testing.T) {
osConfig: config.OSConfig{
EditAtLine: "nano +{{line}} {{filename}}",
},
expectedCmdStr: `nano +35 "test"`,
expectedEditInTerminal: true,
expectedCmdStr: `nano +35 "test"`,
suspend: true,
},
{
filename: "file/with space",
@@ -256,8 +256,8 @@ func TestEditFileAtLineCmd(t *testing.T) {
osConfig: config.OSConfig{
EditPreset: "sublime",
},
expectedCmdStr: `subl -- "file/with space":12`,
expectedEditInTerminal: false,
expectedCmdStr: `subl -- "file/with space":12`,
suspend: false,
},
}
@@ -269,9 +269,9 @@ func TestEditFileAtLineCmd(t *testing.T) {
userConfig: userConfig,
})
cmdStr, editInTerminal := instance.GetEditAtLineCmdStr(s.filename, s.lineNumber)
cmdStr, suspend := instance.GetEditAtLineCmdStr(s.filename, s.lineNumber)
assert.Equal(t, s.expectedCmdStr, cmdStr)
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
assert.Equal(t, s.suspend, suspend)
}
}
@@ -44,6 +44,14 @@ func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder {
return self
}
func (self *GitCommandBuilder) ConfigIf(condition bool, ifTrue string) *GitCommandBuilder {
if condition {
self.Config(ifTrue)
}
return self
}
// the -C arg will make git do a `cd` to the directory before doing anything else
func (self *GitCommandBuilder) Dir(path string) *GitCommandBuilder {
// repo path comes before the command
@@ -0,0 +1,59 @@
package git_commands
import (
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// Returns a set of branch names which are merged into a main branch.
// This
type MergedBranchLoader struct {
c *GitCommon
}
func NewMergedBranchLoader(c *GitCommon) *MergedBranchLoader {
return &MergedBranchLoader{c: c}
}
// TODO: check against upstreams, and share code with commit loader that determines main branches
func (self *MergedBranchLoader) Load() *set.Set[string] {
set := set.New[string]()
mainBranches := self.c.UserConfig.Git.MainBranches
results := utils.ConcurrentMap(mainBranches, func(mainBranch string) []string {
mergedBranches, err := self.GetMergedBranches(mainBranch)
if err != nil {
self.c.Log.Warnf("Failed to get merged branches for %s: %s", mainBranch, err)
return nil
}
return mergedBranches
})
for i := 0; i < len(mainBranches); i++ {
for _, mergedBranch := range results[i] {
set.Add(mergedBranch)
}
}
return set
}
func (self *MergedBranchLoader) GetMergedBranches(mainBranch string) ([]string, error) {
// git for-each-ref --merged master --format '%(refname)' refs/heads/
cmdArgs := NewGitCmd("for-each-ref").Arg(
"--merged",
mainBranch,
"--format",
"%(refname)",
"refs/heads/",
).ToArgv()
output, err := self.c.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return nil, err
}
branches := utils.SplitLines(output)
return branches, nil
}
+74 -6
View File
@@ -7,7 +7,6 @@ import (
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/go-errors/errors"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/app/daemon"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
@@ -80,6 +79,12 @@ func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, index int,
})
}
func (self *RebaseCommands) AddCommitCoAuthor(commits []*models.Commit, index int, value string) error {
return self.GenericAmend(commits, index, func() error {
return self.commit.AddCoAuthor(commits[index].Sha, value)
})
}
func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f func() error) error {
if models.IsHeadCommit(commits, index) {
// we've selected the top commit so no rebase is required
@@ -105,7 +110,13 @@ func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int)
sha := commits[index].Sha
self.os.LogCommand(fmt.Sprintf("Moving TODO down: %s", utils.ShortSha(sha)), false)
msg := utils.ResolvePlaceholderString(
self.Tr.Log.MoveCommitDown,
map[string]string{
"shortSha": utils.ShortSha(sha),
},
)
self.os.LogCommand(msg, false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseShaOrRoot,
@@ -119,7 +130,13 @@ func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) er
sha := commits[index].Sha
self.os.LogCommand(fmt.Sprintf("Moving TODO up: %s", utils.ShortSha(sha)), false)
msg := utils.ResolvePlaceholderString(
self.Tr.Log.MoveCommitUp,
map[string]string{
"shortSha": utils.ShortSha(sha),
},
)
self.os.LogCommand(msg, false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseShaOrRoot,
@@ -150,15 +167,37 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index in
}
func (self *RebaseCommands) EditRebase(branchRef string) error {
self.os.LogCommand(fmt.Sprintf("Beginning interactive rebase at '%s'", branchRef), false)
msg := utils.ResolvePlaceholderString(
self.Tr.Log.EditRebase,
map[string]string{
"ref": branchRef,
},
)
self.os.LogCommand(msg, false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: branchRef,
instruction: daemon.NewInsertBreakInstruction(),
}).Run()
}
func (self *RebaseCommands) EditRebaseFromBaseCommit(targetBranchName string, baseCommit string) error {
msg := utils.ResolvePlaceholderString(
self.Tr.Log.EditRebaseFromBaseCommit,
map[string]string{
"baseCommit": baseCommit,
"targetBranchName": targetBranchName,
},
)
self.os.LogCommand(msg, false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseCommit,
onto: targetBranchName,
instruction: daemon.NewInsertBreakInstruction(),
}).Run()
}
func logTodoChanges(changes []daemon.ChangeTodoAction) string {
changeTodoStr := strings.Join(slices.Map(changes, func(c daemon.ChangeTodoAction) string {
changeTodoStr := strings.Join(lo.Map(changes, func(c daemon.ChangeTodoAction, _ int) string {
return fmt.Sprintf("%s:%s", c.Sha, c.NewAction)
}), "\n")
return fmt.Sprintf("Changing TODO actions: %s", changeTodoStr)
@@ -166,6 +205,7 @@ func logTodoChanges(changes []daemon.ChangeTodoAction) string {
type PrepareInteractiveRebaseCommandOpts struct {
baseShaOrRoot string
onto string
instruction daemon.Instruction
overrideEditor bool
keepCommitsThatBecomeEmpty bool
@@ -184,6 +224,7 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
ArgIf(opts.keepCommitsThatBecomeEmpty && !self.version.IsOlderThan(2, 26, 0), "--empty=keep").
Arg("--no-autosquash").
ArgIf(!self.version.IsOlderThan(2, 22, 0), "--rebase-merges").
ArgIf(opts.onto != "", "--onto", opts.onto).
Arg(opts.baseShaOrRoot).
ToArgv()
@@ -307,6 +348,13 @@ func (self *RebaseCommands) RebaseBranch(branchName string) error {
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseShaOrRoot: branchName}).Run()
}
func (self *RebaseCommands) RebaseBranchFromBaseCommit(targetBranchName string, baseCommit string) error {
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseCommit,
onto: targetBranchName,
}).Run()
}
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
cmdArgs := NewGitCmd(commandType).Arg("--" + command).ToArgv()
@@ -395,7 +443,13 @@ func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
commitLines := lo.Map(commits, func(commit *models.Commit, _ int) string {
return fmt.Sprintf("%s %s", utils.ShortSha(commit.Sha), commit.Name)
})
self.os.LogCommand(fmt.Sprintf("Cherry-picking commits:\n%s", strings.Join(commitLines, "\n")), false)
msg := utils.ResolvePlaceholderString(
self.Tr.Log.CherryPickCommits,
map[string]string{
"commitLines": strings.Join(commitLines, "\n"),
},
)
self.os.LogCommand(msg, false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: "HEAD",
@@ -403,6 +457,20 @@ func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
}).Run()
}
// CherryPickCommitsDuringRebase simply prepends the given commits to the existing git-rebase-todo file
func (self *RebaseCommands) CherryPickCommitsDuringRebase(commits []*models.Commit) error {
todoLines := lo.Map(commits, func(commit *models.Commit, _ int) daemon.TodoLine {
return daemon.TodoLine{
Action: "pick",
Commit: commit,
}
})
todo := daemon.TodoLinesToString(todoLines)
filePath := filepath.Join(self.repoPaths.worktreeGitDirPath, "rebase-merge/git-rebase-todo")
return utils.PrependStrToTodoFile(filePath, []byte(todo))
}
// we can't start an interactive rebase from the first commit without passing the
// '--root' arg
func getBaseShaOrRoot(commits []*models.Commit, index int) string {
+9 -1
View File
@@ -53,7 +53,15 @@ func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName strin
Arg(remoteName, "--delete", branchName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string, tagName string) error {
cmdArgs := NewGitCmd("push").
Arg(remoteName, "--delete", tagName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
// CheckRemoteBranchExists Returns remote branch
+3 -2
View File
@@ -4,12 +4,13 @@ import (
"strings"
"sync"
"github.com/jesseduffield/generics/slices"
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"golang.org/x/exp/slices"
)
type RemoteLoader struct {
@@ -53,7 +54,7 @@ func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
return nil, remoteBranchesErr
}
remotes := slices.Map(goGitRemotes, func(goGitRemote *gogit.Remote) *models.Remote {
remotes := lo.Map(goGitRemotes, func(goGitRemote *gogit.Remote, _ int) *models.Remote {
remoteName := goGitRemote.Config().Name
branches := remoteBranchesByRemoteName[remoteName]
+44 -12
View File
@@ -91,10 +91,24 @@ func getRepoPathsAux(
if err != nil {
return nil, errors.Errorf("failed to get repo git dir path: %v", err)
}
worktreeGitDirPath, err := worktreeGitDirPath(fs, currentPath)
if err != nil {
return nil, errors.Errorf("failed to get worktree git dir path: %v", err)
var worktreeGitDirPath string
if env.GetWorkTreeEnv() != "" {
// This env is set when you pass --work-tree to lazygit. In that case,
// we're not dealing with a linked work-tree, we're dealing with a 'specified'
// worktree (for lack of a better term). In this case, the worktree has no
// .git file and it just contains a bunch of files: it has no idea it's
// pointed to by a bare repo. As such it does not have its own git dir within
// the bare repo's git dir. Instead, we just use the bare repo's git dir.
worktreeGitDirPath = repoGitDirPath
} else {
var err error
worktreeGitDirPath, err = getWorktreeGitDirPath(fs, currentPath)
if err != nil {
return nil, errors.Errorf("failed to get worktree git dir path: %v", err)
}
}
repoName := path.Base(repoPath)
return &RepoPaths{
@@ -110,7 +124,7 @@ func getRepoPathsAux(
// Returns the path of the git-dir for the worktree. For linked worktrees, the worktree has
// a .git file that points to the git-dir (which itself lives in the git-dir
// of the repo)
func worktreeGitDirPath(fs afero.Fs, worktreePath string) (string, error) {
func getWorktreeGitDirPath(fs afero.Fs, worktreePath string) (string, error) {
// if .git is a file, we're in a linked worktree, otherwise we're in
// the main worktree
dotGitPath := path.Join(worktreePath, ".git")
@@ -143,6 +157,11 @@ func linkedWorktreeGitDirPath(fs afero.Fs, worktreePath string) (string, error)
}
gitDir := strings.TrimPrefix(gitDirLine[0], "gitdir: ")
gitDir = filepath.Clean(gitDir)
// For windows support
gitDir = filepath.ToSlash(gitDir)
return gitDir, nil
}
@@ -180,19 +199,32 @@ func getCurrentRepoGitDirPath(
return "", "", errors.Errorf("could not find git dir for %s: %v", currentPath, err)
}
// confirm whether the next directory up is the worktrees/submodules directory
parent := path.Dir(worktreeGitPath)
if path.Base(parent) != "worktrees" && path.Base(parent) != "modules" {
return "", "", errors.Errorf("could not find git dir for %s", currentPath)
_, err = fs.Stat(worktreeGitPath)
if err != nil {
if os.IsNotExist(err) {
// hardcoding error to get around windows-specific error message
return "", "", errors.Errorf("could not find git dir for %s. %s does not exist", currentPath, worktreeGitPath)
}
return "", "", errors.Errorf("could not find git dir for %s: %v", currentPath, err)
}
// if it's a submodule, we treat it as its own repo
if path.Base(parent) == "modules" {
// confirm whether the next directory up is the worktrees directory
parent := path.Dir(worktreeGitPath)
if path.Base(parent) == "worktrees" {
gitDirPath := path.Dir(parent)
return gitDirPath, path.Dir(gitDirPath), nil
}
// Unlike worktrees, submodules can be nested arbitrarily deep, so we check
// if the `modules` directory is anywhere up the chain.
if strings.Contains(worktreeGitPath, "/modules/") {
// For submodules, we just return the path directly
return worktreeGitPath, currentPath, nil
}
gitDirPath := path.Dir(parent)
return gitDirPath, path.Dir(gitDirPath), nil
// If this error causes issues, we could relax the constraint and just always
// return the path
return "", "", errors.Errorf("could not find git dir for %s: the path '%s' is not under `worktrees` or `modules` directories", currentPath, worktreeGitPath)
}
// takes a path containing a symlink and returns the true path
+46 -1
View File
@@ -55,6 +55,24 @@ func TestGetRepoPathsAux(t *testing.T) {
},
Err: nil,
},
{
Name: "worktree with trailing separator in path",
BeforeFunc: func(fs afero.Fs) {
// setup for linked worktree
_ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree1", 0o755)
_ = afero.WriteFile(fs, "/path/to/repo/worktree1/.git", []byte("gitdir: /path/to/repo/.git/worktrees/worktree1/"), 0o644)
},
Path: "/path/to/repo/worktree1",
Expected: &RepoPaths{
currentPath: "/path/to/repo/worktree1",
worktreePath: "/path/to/repo/worktree1",
worktreeGitDirPath: "/path/to/repo/.git/worktrees/worktree1",
repoPath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git",
repoName: "repo",
},
Err: nil,
},
{
Name: "worktree .git file missing gitdir directive",
BeforeFunc: func(fs afero.Fs) {
@@ -73,7 +91,7 @@ func TestGetRepoPathsAux(t *testing.T) {
},
Path: "/path/to/repo/worktree2",
Expected: nil,
Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/worktree2"),
Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/worktree2. /nonexistant does not exist"),
},
{
Name: "submodule",
@@ -92,6 +110,33 @@ func TestGetRepoPathsAux(t *testing.T) {
},
Err: nil,
},
{
Name: "submodule in nested directory",
BeforeFunc: func(fs afero.Fs) {
_ = fs.MkdirAll("/path/to/repo/.git/modules/my/submodule1", 0o755)
_ = afero.WriteFile(fs, "/path/to/repo/my/submodule1/.git", []byte("gitdir: /path/to/repo/.git/modules/my/submodule1"), 0o644)
},
Path: "/path/to/repo/my/submodule1",
Expected: &RepoPaths{
currentPath: "/path/to/repo/my/submodule1",
worktreePath: "/path/to/repo/my/submodule1",
worktreeGitDirPath: "/path/to/repo/.git/modules/my/submodule1",
repoPath: "/path/to/repo/my/submodule1",
repoGitDirPath: "/path/to/repo/.git/modules/my/submodule1",
repoName: "submodule1",
},
Err: nil,
},
{
Name: "submodule git dir not under .git/modules",
BeforeFunc: func(fs afero.Fs) {
_ = fs.MkdirAll("/random/submodule1", 0o755)
_ = afero.WriteFile(fs, "/path/to/repo/my/submodule1/.git", []byte("gitdir: /random/submodule1"), 0o644)
},
Path: "/path/to/repo/my/submodule1",
Expected: nil,
Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/my/submodule1: the path '/random/submodule1' is not under `worktrees` or `modules` directories"),
},
}
for _, s := range scenarios {
+3 -3
View File
@@ -80,13 +80,13 @@ func (self *StashCommands) Sha(index int) (string, error) {
return strings.Trim(sha, "\r\n"), err
}
func (self *StashCommands) ShowStashEntryCmdObj(index int, ignoreWhitespace bool) oscommands.ICmdObj {
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
cmdArgs := NewGitCmd("stash").Arg("show").
Arg("-p").
Arg("--stat").
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--unified=%d", self.UserConfig.Git.DiffContextSize)).
ArgIf(ignoreWhitespace, "--ignore-all-space").
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("stash@{%d}", index)).
ToArgv()
+2 -2
View File
@@ -5,11 +5,11 @@ import (
"strconv"
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type StashLoader struct {
@@ -69,7 +69,7 @@ func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%gs").ToArgv()
rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
return lo.Map(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
return self.stashEntryFromLine(line, index)
})
}
+5 -3
View File
@@ -134,10 +134,12 @@ func TestStashStashEntryCmdObj(t *testing.T) {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
userConfig.Git.DiffContextSize = s.contextSize
instance := buildStashCommands(commonDeps{userConfig: userConfig})
appState := &config.AppState{}
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
instance := buildStashCommands(commonDeps{userConfig: userConfig, appState: appState})
cmdStr := instance.ShowStashEntryCmdObj(s.index, s.ignoreWhitespace).Args()
cmdStr := instance.ShowStashEntryCmdObj(s.index).Args()
assert.Equal(t, s.expected, cmdStr)
})
}
+26 -9
View File
@@ -1,6 +1,7 @@
package git_commands
import (
"os"
"path/filepath"
"strconv"
"strings"
@@ -24,19 +25,16 @@ func NewStatusCommands(
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
// and "interactive" for interactive rebase
func (self *StatusCommands) RebaseMode() (enums.RebaseMode, error) {
exists, err := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply"))
if err != nil {
return enums.REBASE_MODE_NONE, err
}
if exists {
ok, err := self.IsInNormalRebase()
if err == nil && ok {
return enums.REBASE_MODE_NORMAL, nil
}
exists, err = self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
if exists {
ok, err = self.IsInInteractiveRebase()
if err == nil && ok {
return enums.REBASE_MODE_INTERACTIVE, err
} else {
return enums.REBASE_MODE_NONE, err
}
return enums.REBASE_MODE_NONE, err
}
func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
@@ -67,7 +65,26 @@ func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
return strconv.ParseBool(strings.TrimSpace(res))
}
func (self *StatusCommands) IsInNormalRebase() (bool, error) {
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply"))
}
func (self *StatusCommands) IsInInteractiveRebase() (bool, error) {
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
}
// IsInMergeState states whether we are still mid-merge
func (self *StatusCommands) IsInMergeState() (bool, error) {
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "MERGE_HEAD"))
}
// Full ref (e.g. "refs/heads/mybranch") of the branch that is currently
// being rebased, or empty string when we're not in a rebase
func (self *StatusCommands) BranchBeingRebased() string {
for _, dir := range []string{"rebase-merge", "rebase-apply"} {
if bytesContent, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), dir, "head-name")); err == nil {
return strings.TrimSpace(string(bytesContent))
}
}
return ""
}
+4 -5
View File
@@ -36,7 +36,7 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
ToArgv()
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex)
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task)
return cmdObj, nil
}
@@ -70,7 +70,6 @@ func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
cmdObj := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest()
cmdObj.WithMutex(self.syncMutex)
return cmdObj
}
@@ -96,7 +95,7 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
// has 'pull.rebase = interactive' configured.
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).Run()
}
func (self *SyncCommands) FastForward(
@@ -110,7 +109,7 @@ func (self *SyncCommands) FastForward(
Arg(remoteBranchName + ":" + branchName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error {
@@ -118,5 +117,5 @@ func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error
Arg(remoteName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
+17 -5
View File
@@ -12,16 +12,19 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands {
}
}
func (self *TagCommands) CreateLightweight(tagName string, ref string) error {
cmdArgs := NewGitCmd("tag").Arg("--", tagName).
func (self *TagCommands) CreateLightweight(tagName string, ref string, force bool) error {
cmdArgs := NewGitCmd("tag").
ArgIf(force, "--force").
Arg("--", tagName).
ArgIf(len(ref) > 0, ref).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string, force bool) error {
cmdArgs := NewGitCmd("tag").Arg(tagName).
ArgIf(force, "--force").
ArgIf(len(ref) > 0, ref).
Arg("-m", msg).
ToArgv()
@@ -29,7 +32,16 @@ func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
return self.cmd.New(cmdArgs).Run()
}
func (self *TagCommands) Delete(tagName string) error {
func (self *TagCommands) HasTag(tagName string) bool {
cmdArgs := NewGitCmd("show-ref").
Arg("--tags", "--quiet", "--verify", "--").
Arg("refs/tags/" + tagName).
ToArgv()
return self.cmd.New(cmdArgs).Run() == nil
}
func (self *TagCommands) LocalDelete(tagName string) error {
cmdArgs := NewGitCmd("tag").Arg("-d", tagName).
ToArgv()
@@ -40,5 +52,5 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string
cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
+2 -2
View File
@@ -3,11 +3,11 @@ package git_commands
import (
"regexp"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type TagLoader struct {
@@ -38,7 +38,7 @@ func (self *TagLoader) GetTags() ([]*models.Tag, error) {
lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`)
tags := slices.Map(split, func(line string) *models.Tag {
tags := lo.Map(split, func(line string, _ int) *models.Tag {
matches := lineRegex.FindStringSubmatch(line)
tagName := matches[1]
message := ""
+19 -16
View File
@@ -228,28 +228,31 @@ func (self *WorkingTreeCommands) Exclude(filename string) error {
}
// WorktreeFileDiff returns the diff of a file
func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool, ignoreWhitespace bool) string {
func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool) string {
// for now we assume an error means the file was deleted
s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace).RunWithOutput()
s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached).RunWithOutput()
return s
}
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj {
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) oscommands.ICmdObj {
colorArg := self.UserConfig.Git.Paging.ColorArg
if plain {
colorArg = "never"
}
contextSize := self.UserConfig.Git.DiffContextSize
contextSize := self.AppState.DiffContextSize
prevPath := node.GetPreviousPath()
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" && !plain
cmdArgs := NewGitCmd("diff").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg("--no-ext-diff").
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg(fmt.Sprintf("--color=%s", colorArg)).
ArgIf(ignoreWhitespace, "--ignore-all-space").
ArgIf(!plain && self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
ArgIf(cached, "--cached").
ArgIf(noIndex, "--no-index").
Arg("--").
@@ -263,32 +266,32 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc
// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode.
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool,
ignoreWhitespace bool,
) (string, error) {
return self.ShowFileDiffCmdObj(from, to, reverse, fileName, plain, ignoreWhitespace).RunWithOutput()
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) {
return self.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput()
}
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool,
ignoreWhitespace bool,
) oscommands.ICmdObj {
contextSize := self.UserConfig.Git.DiffContextSize
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj {
contextSize := self.AppState.DiffContextSize
colorArg := self.UserConfig.Git.Paging.ColorArg
if plain {
colorArg = "never"
}
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" && !plain
cmdArgs := NewGitCmd("diff").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg("--no-ext-diff").
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--no-renames").
Arg(fmt.Sprintf("--color=%s", colorArg)).
Arg(from).
Arg(to).
ArgIf(reverse, "-R").
ArgIf(ignoreWhitespace, "--ignore-all-space").
ArgIf(!plain && self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg("--").
Arg(fileName).
ToArgv()
+19 -15
View File
@@ -231,7 +231,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "cached",
@@ -245,7 +245,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "plain",
@@ -259,7 +259,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "File not tracked and file has no staged changes",
@@ -273,7 +273,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
},
{
testName: "Default case (ignore whitespace)",
@@ -287,7 +287,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: true,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom context size",
@@ -301,7 +301,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false,
contextSize: 17,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
}
@@ -309,10 +309,12 @@ func TestWorkingTreeDiff(t *testing.T) {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
userConfig.Git.DiffContextSize = s.contextSize
appState := &config.AppState{}
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig})
result := instance.WorktreeFileDiff(s.file, s.plain, s.cached, s.ignoreWhitespace)
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: appState})
result := instance.WorktreeFileDiff(s.file, s.plain, s.cached)
assert.Equal(t, expectedResult, result)
s.runner.CheckForMissingCalls()
})
@@ -343,7 +345,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom context size",
@@ -354,7 +356,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: false,
contextSize: 123,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Default case (ignore whitespace)",
@@ -365,7 +367,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: true,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
},
}
@@ -373,11 +375,13 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
userConfig.Git.DiffContextSize = s.contextSize
appState := &config.AppState{}
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig})
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: appState})
result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain, s.ignoreWhitespace)
result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain)
assert.NoError(t, err)
assert.Equal(t, expectedResult, result)
s.runner.CheckForMissingCalls()
+1 -1
View File
@@ -58,7 +58,7 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
isPathMissing := self.pathExists(path)
var gitDir string
gitDir, err := worktreeGitDirPath(self.Fs, path)
gitDir, err := getWorktreeGitDirPath(self.Fs, path)
if err != nil {
self.Log.Warnf("Could not find git dir for worktree %s: %v", path, err)
}
@@ -186,7 +186,7 @@ branch refs/heads/mybranch-worktree
assert.EqualError(t, errors.New(s.expectedErr), err.Error())
} else {
assert.NoError(t, err)
assert.EqualValues(t, worktrees, s.expectedWorktrees)
assert.EqualValues(t, s.expectedWorktrees, worktrees)
}
})
}
+2 -4
View File
@@ -7,8 +7,6 @@ import (
"os/exec"
"strings"
"syscall"
"github.com/jesseduffield/lazygit/pkg/secureexec"
)
// including license from https://github.com/tcnksm/go-gitconfig because this file is an adaptation of that repo's code
@@ -55,10 +53,10 @@ func runGitConfigCmd(cmd *exec.Cmd) (string, error) {
func getGitConfigCmd(key string) *exec.Cmd {
gitArgs := []string{"config", "--get", "--null", key}
return secureexec.Command("git", gitArgs...)
return exec.Command("git", gitArgs...)
}
func getGitConfigGeneralCmd(args string) *exec.Cmd {
gitArgs := append([]string{"config"}, strings.Split(args, " ")...)
return secureexec.Command("git", gitArgs...)
return exec.Command("git", gitArgs...)
}
+24 -26
View File
@@ -8,9 +8,10 @@ import (
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sirupsen/logrus"
"github.com/jesseduffield/generics/slices"
"golang.org/x/exp/slices"
)
// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, gitea, etc.
@@ -98,33 +99,30 @@ func (self *HostingServiceMgr) getCandidateServiceDomains() []ServiceDomain {
serviceDomains := slices.Clone(defaultServiceDomains)
if len(self.configServiceDomains) > 0 {
for gitDomain, typeAndDomain := range self.configServiceDomains {
splitData := strings.Split(typeAndDomain, ":")
if len(splitData) != 2 {
self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain)
continue
}
for gitDomain, typeAndDomain := range self.configServiceDomains {
provider, webDomain, success := strings.Cut(typeAndDomain, ":")
provider := splitData[0]
webDomain := splitData[1]
serviceDefinition, ok := serviceDefinitionByProvider[provider]
if !ok {
providerNames := slices.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition) string {
return serviceDefinition.provider
})
self.log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", "))
continue
}
serviceDomains = append(serviceDomains, ServiceDomain{
gitDomain: gitDomain,
webDomain: webDomain,
serviceDefinition: serviceDefinition,
})
// we allow for one ':' for specifying the TCP port
if !success || strings.Count(webDomain, ":") > 1 {
self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain)
continue
}
serviceDefinition, ok := serviceDefinitionByProvider[provider]
if !ok {
providerNames := lo.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition, _ int) string {
return serviceDefinition.provider
})
self.log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", "))
continue
}
serviceDomains = append(serviceDomains, ServiceDomain{
gitDomain: gitDomain,
webDomain: webDomain,
serviceDefinition: serviceDefinition,
})
}
return serviceDomains
@@ -340,6 +340,30 @@ func TestGetPullRequestURL(t *testing.T) {
},
expectedLoggedErrors: nil,
},
{
testName: "Does not log error when config service webDomain contains a port",
from: "feature/profile-page",
remoteUrl: "git@my.domain.test:johndoe/social_network.git",
configServiceDomains: map[string]string{
"my.domain.test": "gitlab:my.domain.test:1111",
},
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://my.domain.test:1111/johndoe/social_network/-/merge_requests/new?merge_request[source_branch]=feature%2Fprofile-page", url)
},
},
{
testName: "Logs error when webDomain contains more than one colon",
from: "feature/profile-page",
remoteUrl: "git@my.domain.test:johndoe/social_network.git",
configServiceDomains: map[string]string{
"my.domain.test": "gitlab:my.domain.test:1111:2222",
},
test: func(url string, err error) {
assert.Error(t, err)
},
expectedLoggedErrors: []string{"Unexpected format for git service: 'gitlab:my.domain.test:1111:2222'. Expected something like 'github.com:github.com'"},
},
{
testName: "Logs error when config service domain is malformed",
from: "feature/profile-page",
+22
View File
@@ -1,5 +1,7 @@
package models
import "fmt"
// Branch : A git branch
// duplicating this for now
type Branch struct {
@@ -43,10 +45,30 @@ func (b *Branch) ParentRefName() string {
return b.RefName() + "^"
}
func (b *Branch) FullUpstreamRefName() string {
if b.UpstreamRemote == "" || b.UpstreamBranch == "" {
return ""
}
return fmt.Sprintf("refs/remotes/%s/%s", b.UpstreamRemote, b.UpstreamBranch)
}
func (b *Branch) ShortUpstreamRefName() string {
if b.UpstreamRemote == "" || b.UpstreamBranch == "" {
return ""
}
return fmt.Sprintf("%s/%s", b.UpstreamRemote, b.UpstreamBranch)
}
func (b *Branch) ID() string {
return b.RefName()
}
func (b *Branch) URN() string {
return "branch-" + b.ID()
}
func (b *Branch) Description() string {
return b.RefName()
}
+12
View File
@@ -30,6 +30,17 @@ const (
ActionConflict = todo.Comment + 1
)
type Divergence int
// For a divergence log (left/right comparison of two refs) this is set to
// either DivergenceLeft or DivergenceRight for each commit; for normal
// commit views it is always DivergenceNone.
const (
DivergenceNone Divergence = iota
DivergenceLeft
DivergenceRight
)
// Commit : A git commit
type Commit struct {
Sha string
@@ -41,6 +52,7 @@ type Commit struct {
AuthorName string // something like 'Jesse Duffield'
AuthorEmail string // something like 'jessedduffield@gmail.com'
UnixTimestamp int64
Divergence Divergence // set to DivergenceNone unless we are showing the divergence view
// SHAs of parent commits (will be multiple if it's a merge commit)
Parents []string
+4
View File
@@ -24,6 +24,10 @@ func (t *Tag) ID() string {
return t.RefName()
}
func (t *Tag) URN() string {
return "tag-" + t.ID()
}
func (t *Tag) Description() string {
return t.Message
}
+2 -6
View File
@@ -73,10 +73,6 @@ type ICmdObj interface {
}
type CmdObj struct {
// the secureexec package will swap out the first arg with the full path to the binary,
// so we store these args separately so that ToString() will output the original
args []string
cmd *exec.Cmd
runner ICmdObjRunner
@@ -121,7 +117,7 @@ func (self *CmdObj) GetCmd() *exec.Cmd {
func (self *CmdObj) ToString() string {
// if a given arg contains a space, we need to wrap it in quotes
quotedArgs := lo.Map(self.args, func(arg string, _ int) string {
quotedArgs := lo.Map(self.cmd.Args, func(arg string, _ int) string {
if strings.Contains(arg, " ") {
return `"` + arg + `"`
}
@@ -132,7 +128,7 @@ func (self *CmdObj) ToString() string {
}
func (self *CmdObj) Args() []string {
return self.args
return self.cmd.Args
}
func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj {
+2 -3
View File
@@ -3,9 +3,9 @@ package oscommands
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/mgutz/str"
)
@@ -27,11 +27,10 @@ type CmdObjBuilder struct {
var _ ICmdObjBuilder = &CmdObjBuilder{}
func (self *CmdObjBuilder) New(args []string) ICmdObj {
cmd := secureexec.Command(args[0], args[1:]...)
cmd := exec.Command(args[0], args[1:]...)
cmd.Env = os.Environ()
return &CmdObj{
args: args,
cmd: cmd,
runner: self.runner,
}
+2 -1
View File
@@ -27,7 +27,8 @@ func TestCmdObjToString(t *testing.T) {
}
for _, scenario := range scenarios {
cmdObj := &CmdObj{args: scenario.cmdArgs}
cmd := exec.Command(scenario.cmdArgs[0], scenario.cmdArgs[1:]...)
cmdObj := &CmdObj{cmd: cmd}
actual := cmdObj.ToString()
if actual != scenario.expected {
t.Errorf("Expected %s, got %s", quote(scenario.expected), quote(actual))
+8 -3
View File
@@ -3,7 +3,6 @@ package oscommands
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
)
@@ -106,7 +105,7 @@ func CopyDir(src string, dst string) (err error) {
return //nolint: nakedret
}
entries, err := ioutil.ReadDir(src)
entries, err := os.ReadDir(src)
if err != nil {
return //nolint: nakedret
}
@@ -121,8 +120,14 @@ func CopyDir(src string, dst string) (err error) {
return //nolint: nakedret
}
} else {
var info os.FileInfo
info, err = entry.Info()
if err != nil {
return //nolint: nakedret
}
// Skip symlinks.
if entry.Mode()&os.ModeSymlink != 0 {
if info.Mode()&os.ModeSymlink != 0 {
continue
}
+2 -35
View File
@@ -3,8 +3,6 @@ package oscommands
import (
"bufio"
"fmt"
"regexp"
"runtime"
"strings"
"sync"
"testing"
@@ -124,27 +122,7 @@ func (self *FakeCmdObjRunner) ExpectFunc(description string, fn func(cmdObj ICmd
func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
description := fmt.Sprintf("matches args %s", strings.Join(expectedArgs, " "))
self.ExpectFunc(description, func(cmdObj ICmdObj) bool {
args := cmdObj.GetCmd().Args
if runtime.GOOS == "windows" {
// thanks to the secureexec package, the first arg is something like
// '"C:\\Program Files\\Git\\mingw64\\bin\\<command>.exe"
// on windows so we'll just ensure it contains our program
if !strings.Contains(args[0], expectedArgs[0]) {
return false
}
} else {
// first arg is the program name
if expectedArgs[0] != args[0] {
return false
}
}
if !slices.Equal(expectedArgs[1:], args[1:]) {
return false
}
return true
return slices.Equal(expectedArgs, cmdObj.GetCmd().Args)
}, output, err)
return self
@@ -153,18 +131,7 @@ func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, e
func (self *FakeCmdObjRunner) ExpectGitArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
description := fmt.Sprintf("matches git args %s", strings.Join(expectedArgs, " "))
self.ExpectFunc(description, func(cmdObj ICmdObj) bool {
// first arg is 'git' on unix and something like '"C:\\Program Files\\Git\\mingw64\\bin\\git.exe" on windows so we'll just ensure it ends in either 'git' or 'git.exe'
re := regexp.MustCompile(`git(\.exe)?$`)
args := cmdObj.GetCmd().Args
if !re.MatchString(args[0]) {
return false
}
if !slices.Equal(expectedArgs, args[1:]) {
return false
}
return true
return slices.Equal(expectedArgs, cmdObj.GetCmd().Args[1:])
}, output, err)
return self
+46 -7
View File
@@ -13,7 +13,6 @@ import (
"github.com/samber/lo"
"github.com/atotto/clipboard"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/kill"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
@@ -118,7 +117,15 @@ func (c *OSCommand) Quote(message string) string {
// AppendLineToFile adds a new line in file
func (c *OSCommand) AppendLineToFile(filename, line string) error {
c.LogCommand(fmt.Sprintf("Appending '%s' to file '%s'", line, filename), false)
msg := utils.ResolvePlaceholderString(
c.Tr.Log.AppendingLineToFile,
map[string]string{
"line": line,
"filename": filename,
},
)
c.LogCommand(msg, false)
f, err := os.OpenFile(filename, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0o600)
if err != nil {
return utils.WrapError(err)
@@ -155,7 +162,13 @@ func (c *OSCommand) AppendLineToFile(filename, line string) error {
// CreateFileWithContent creates a file with the given content
func (c *OSCommand) CreateFileWithContent(path string, content string) error {
c.LogCommand(fmt.Sprintf("Creating file '%s'", path), false)
msg := utils.ResolvePlaceholderString(
c.Tr.Log.CreateFileWithContent,
map[string]string{
"path": path,
},
)
c.LogCommand(msg, false)
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
c.Log.Error(err)
return err
@@ -171,7 +184,13 @@ func (c *OSCommand) CreateFileWithContent(path string, content string) error {
// Remove removes a file or directory at the specified path
func (c *OSCommand) Remove(filename string) error {
c.LogCommand(fmt.Sprintf("Removing '%s'", filename), false)
msg := utils.ResolvePlaceholderString(
c.Tr.Log.Remove,
map[string]string{
"filename": filename,
},
)
c.LogCommand(msg, false)
err := os.RemoveAll(filename)
return utils.WrapError(err)
}
@@ -189,7 +208,7 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
cmds := slices.Map(cmdObjs, func(cmdObj ICmdObj) *exec.Cmd {
cmds := lo.Map(cmdObjs, func(cmdObj ICmdObj, _ int) *exec.Cmd {
return cmdObj.GetCmd()
})
@@ -266,12 +285,32 @@ func PrepareForChildren(cmd *exec.Cmd) {
func (c *OSCommand) CopyToClipboard(str string) error {
escaped := strings.Replace(str, "\n", "\\n", -1)
truncated := utils.TruncateWithEllipsis(escaped, 40)
c.LogCommand(fmt.Sprintf("Copying '%s' to clipboard", truncated), false)
msg := utils.ResolvePlaceholderString(
c.Tr.Log.CopyToClipboard,
map[string]string{
"str": truncated,
},
)
c.LogCommand(msg, false)
if c.UserConfig.OS.CopyToClipboardCmd != "" {
cmdStr := utils.ResolvePlaceholderString(c.UserConfig.OS.CopyToClipboardCmd, map[string]string{
"text": c.Cmd.Quote(str),
})
return c.Cmd.NewShell(cmdStr).Run()
}
return clipboard.WriteAll(str)
}
func (c *OSCommand) RemoveFile(path string) error {
c.LogCommand(fmt.Sprintf("Deleting path '%s'", path), false)
msg := utils.ResolvePlaceholderString(
c.Tr.Log.RemoveFile,
map[string]string{
"path": path,
},
)
c.LogCommand(msg, false)
return c.removeFileFn(path)
}
+5 -8
View File
@@ -6,7 +6,6 @@ package oscommands
import (
"testing"
"github.com/cli/safeexec"
"github.com/go-errors/errors"
"github.com/stretchr/testify/assert"
)
@@ -20,13 +19,11 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
test func(error)
}
fullCmdPath, _ := safeexec.LookPath("cmd")
scenarios := []scenario{
{
filename: "test",
runner: NewFakeRunner(t).
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "test"}, "", errors.New("error")),
ExpectArgs([]string{"cmd", "/c", "start", "", "test"}, "", errors.New("error")),
test: func(err error) {
assert.Error(t, err)
},
@@ -34,7 +31,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
{
filename: "test",
runner: NewFakeRunner(t).
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "test"}, "", nil),
ExpectArgs([]string{"cmd", "/c", "start", "", "test"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
@@ -42,7 +39,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
{
filename: "filename with spaces",
runner: NewFakeRunner(t).
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "filename with spaces"}, "", nil),
ExpectArgs([]string{"cmd", "/c", "start", "", "filename with spaces"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
@@ -50,7 +47,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
{
filename: "let's_test_with_single_quote",
runner: NewFakeRunner(t).
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "let's_test_with_single_quote"}, "", nil),
ExpectArgs([]string{"cmd", "/c", "start", "", "let's_test_with_single_quote"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
@@ -58,7 +55,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
{
filename: "$USER.txt",
runner: NewFakeRunner(t).
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "$USER.txt"}, "", nil),
ExpectArgs([]string{"cmd", "/c", "start", "", "$USER.txt"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
+7 -2
View File
@@ -97,12 +97,12 @@ func (self *Patch) LineNumberOfLine(idx int) int {
idxInHunk := idx - hunkStartIdx
if idxInHunk == 0 {
return hunk.oldStart
return hunk.newStart
}
lines := hunk.bodyLines[:idxInHunk-1]
offset := nLinesWithKind(lines, []PatchLineKind{ADDITION, CONTEXT})
return hunk.oldStart + offset
return hunk.newStart + offset
}
// Returns hunk index containing the line at the given patch line index
@@ -149,3 +149,8 @@ func (self *Patch) LineCount() int {
}
return count
}
// Returns the number of hunks of the patch
func (self *Patch) HunkCount() int {
return len(self.hunks)
}
+2 -3
View File
@@ -5,7 +5,6 @@ import (
"strings"
"github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/generics/slices"
"github.com/samber/lo"
"github.com/sirupsen/logrus"
)
@@ -209,10 +208,10 @@ func (p *PatchBuilder) renderEachFilePatch(plain bool) []string {
filenames := maps.Keys(p.fileInfoMap)
sort.Strings(filenames)
patches := slices.Map(filenames, func(filename string) string {
patches := lo.Map(filenames, func(filename string, _ int) string {
return p.RenderPatchForFile(filename, plain, false)
})
output := slices.Filter(patches, func(patch string) bool {
output := lo.Filter(patches, func(patch string, _ int) bool {
return patch != ""
})
+29
View File
@@ -67,6 +67,29 @@ index e48a11c..b2ab81b 100644
...
`
const twoHunksWithMoreAdditionsThanRemovals = `diff --git a/filename b/filename
index bac359d75..6e5b89f36 100644
--- a/filename
+++ b/filename
@@ -1,5 +1,6 @@
apple
-grape
+orange
+kiwi
...
...
...
@@ -8,6 +9,8 @@ grape
...
...
...
+pear
+lemon
...
...
...
`
const twoChangesInOneHunk = `diff --git a/filename b/filename
index 9320895..6d79956 100644
--- a/filename
@@ -572,6 +595,12 @@ func TestLineNumberOfLine(t *testing.T) {
indexes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 1000},
expecteds: []int{1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 5, 8, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15},
},
{
testName: "twoHunksWithMoreAdditionsThanRemovals",
patchStr: twoHunksWithMoreAdditionsThanRemovals,
indexes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 1000},
expecteds: []int{1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 5, 6, 9, 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16},
},
}
for _, s := range scenarios {
+1
View File
@@ -12,6 +12,7 @@ type Common struct {
Log *logrus.Entry
Tr *i18n.TranslationSet
UserConfig *config.UserConfig
AppState *config.AppState
Debug bool
// for interacting with the filesystem. We use afero rather than the default
// `os` package for the sake of mocking the filesystem in tests
+9 -5
View File
@@ -283,11 +283,13 @@ func (c *AppConfig) SaveAppState() error {
// loadAppState loads recorded AppState from file
func loadAppState() (*AppState, error) {
appState := getDefaultAppState()
filepath, err := configFilePath("state.yml")
if err != nil {
if os.IsPermission(err) {
// apparently when people have read-only permissions they prefer us to fail silently
return getDefaultAppState(), nil
return appState, nil
}
return nil, err
}
@@ -298,10 +300,9 @@ func loadAppState() (*AppState, error) {
}
if len(appStateBytes) == 0 {
return getDefaultAppState(), nil
return appState, nil
}
appState := &AppState{}
err = yaml.Unmarshal(appStateBytes, appState)
if err != nil {
return nil, err
@@ -318,8 +319,10 @@ type AppState struct {
StartupPopupVersion int
// these are for custom commands typed in directly, not for custom commands in the lazygit config
CustomCommandsHistory []string
HideCommandLog bool
CustomCommandsHistory []string
HideCommandLog bool
IgnoreWhitespaceInDiffView bool
DiffContextSize int
}
func getDefaultAppState() *AppState {
@@ -327,6 +330,7 @@ func getDefaultAppState() *AppState {
LastUpdateCheck: 0,
RecentRepos: []string{},
StartupPopupVersion: 0,
DiffContextSize: 3,
}
}
+2 -3
View File
@@ -1,18 +1,17 @@
package config
import (
"io/ioutil"
"os"
"strings"
)
func isWSL() bool {
data, err := ioutil.ReadFile("/proc/sys/kernel/osrelease")
data, err := os.ReadFile("/proc/sys/kernel/osrelease")
return err == nil && strings.Contains(string(data), "microsoft")
}
func isContainer() bool {
data, err := ioutil.ReadFile("/proc/1/cgroup")
data, err := os.ReadFile("/proc/1/cgroup")
if strings.Contains(string(data), "docker") ||
strings.Contains(string(data), "/lxc/") ||
+24 -15
View File
@@ -28,13 +28,13 @@ func GetEditAtLineAndWaitTemplate(osConfig *OSConfig, guessDefaultEditor func()
return template
}
func GetOpenDirInEditorTemplate(osConfig *OSConfig, guessDefaultEditor func() string) string {
func GetOpenDirInEditorTemplate(osConfig *OSConfig, guessDefaultEditor func() string) (string, bool) {
preset := getPreset(osConfig, guessDefaultEditor)
template := osConfig.OpenDirInEditor
if template == "" {
template = preset.openDirInEditorTemplate
}
return template
return template, getEditInTerminal(osConfig, preset)
}
type editPreset struct {
@@ -42,16 +42,25 @@ type editPreset struct {
editAtLineTemplate string
editAtLineAndWaitTemplate string
openDirInEditorTemplate string
editInTerminal bool
suspend bool
}
// IF YOU ADD A PRESET TO THIS FUNCTION YOU MUST UPDATE THE `Supported presets` SECTION OF docs/Config.md
func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset {
presets := map[string]*editPreset{
"vi": standardTerminalEditorPreset("vi"),
"vim": standardTerminalEditorPreset("vim"),
"nvim": standardTerminalEditorPreset("nvim"),
"vi": standardTerminalEditorPreset("vi"),
"vim": standardTerminalEditorPreset("vim"),
"nvim": standardTerminalEditorPreset("nvim"),
"nvim-remote": {
editTemplate: `nvim --server "$NVIM" --remote-tab {{filename}}`,
editAtLineTemplate: `nvim --server "$NVIM" --remote-tab {{filename}}; [ -z "$NVIM" ] || nvim --server "$NVIM" --remote-send ":{{line}}<CR>"`,
// No remote-wait support yet. See https://github.com/neovim/neovim/pull/17856
editAtLineAndWaitTemplate: `nvim +{{line}} {{filename}}`,
openDirInEditorTemplate: `nvim --server "$NVIM" --remote-tab {{dir}}`,
suspend: false,
},
"emacs": standardTerminalEditorPreset("emacs"),
"micro": standardTerminalEditorPreset("micro"),
"nano": standardTerminalEditorPreset("nano"),
"kakoune": standardTerminalEditorPreset("kak"),
"helix": {
@@ -59,35 +68,35 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
editAtLineTemplate: "hx -- {{filename}}:{{line}}",
editAtLineAndWaitTemplate: "hx -- {{filename}}:{{line}}",
openDirInEditorTemplate: "hx -- {{dir}}",
editInTerminal: true,
suspend: true,
},
"vscode": {
editTemplate: "code --reuse-window -- {{filename}}",
editAtLineTemplate: "code --reuse-window --goto -- {{filename}}:{{line}}",
editAtLineAndWaitTemplate: "code --reuse-window --goto --wait -- {{filename}}:{{line}}",
openDirInEditorTemplate: "code -- {{dir}}",
editInTerminal: false,
suspend: false,
},
"sublime": {
editTemplate: "subl -- {{filename}}",
editAtLineTemplate: "subl -- {{filename}}:{{line}}",
editAtLineAndWaitTemplate: "subl --wait -- {{filename}}:{{line}}",
openDirInEditorTemplate: "subl -- {{dir}}",
editInTerminal: false,
suspend: false,
},
"bbedit": {
editTemplate: "bbedit -- {{filename}}",
editAtLineTemplate: "bbedit +{{line}} -- {{filename}}",
editAtLineAndWaitTemplate: "bbedit +{{line}} --wait -- {{filename}}",
openDirInEditorTemplate: "bbedit -- {{dir}}",
editInTerminal: false,
suspend: false,
},
"xcode": {
editTemplate: "xed -- {{filename}}",
editAtLineTemplate: "xed --line {{line}} -- {{filename}}",
editAtLineAndWaitTemplate: "xed --line {{line}} --wait -- {{filename}}",
openDirInEditorTemplate: "xed -- {{dir}}",
editInTerminal: false,
suspend: false,
},
}
@@ -123,13 +132,13 @@ func standardTerminalEditorPreset(editor string) *editPreset {
editAtLineTemplate: editor + " +{{line}} -- {{filename}}",
editAtLineAndWaitTemplate: editor + " +{{line}} -- {{filename}}",
openDirInEditorTemplate: editor + " -- {{dir}}",
editInTerminal: true,
suspend: true,
}
}
func getEditInTerminal(osConfig *OSConfig, preset *editPreset) bool {
if osConfig.EditInTerminal != nil {
return *osConfig.EditInTerminal
if osConfig.SuspendOnEdit != nil {
return *osConfig.SuspendOnEdit
}
return preset.editInTerminal
return preset.suspend
}
+12 -12
View File
@@ -16,7 +16,7 @@ func TestGetEditTemplate(t *testing.T) {
expectedEditTemplate string
expectedEditAtLineTemplate string
expectedEditAtLineAndWaitTemplate string
expectedEditInTerminal bool
expectedSuspend bool
}{
{
"Default template is vim",
@@ -52,9 +52,9 @@ func TestGetEditTemplate(t *testing.T) {
{
"Overriding a preset with explicit config (edit)",
&OSConfig{
EditPreset: "vscode",
Edit: "myeditor {{filename}}",
EditInTerminal: &trueVal,
EditPreset: "vscode",
Edit: "myeditor {{filename}}",
SuspendOnEdit: &trueVal,
},
func() string { return "" },
"myeditor {{filename}}",
@@ -65,9 +65,9 @@ func TestGetEditTemplate(t *testing.T) {
{
"Overriding a preset with explicit config (edit at line)",
&OSConfig{
EditPreset: "vscode",
EditAtLine: "myeditor --line={{line}} {{filename}}",
EditInTerminal: &trueVal,
EditPreset: "vscode",
EditAtLine: "myeditor --line={{line}} {{filename}}",
SuspendOnEdit: &trueVal,
},
func() string { return "" },
"code --reuse-window -- {{filename}}",
@@ -80,7 +80,7 @@ func TestGetEditTemplate(t *testing.T) {
&OSConfig{
EditPreset: "vscode",
EditAtLineAndWait: "myeditor --line={{line}} -w {{filename}}",
EditInTerminal: &trueVal,
SuspendOnEdit: &trueVal,
},
func() string { return "" },
"code --reuse-window -- {{filename}}",
@@ -111,13 +111,13 @@ func TestGetEditTemplate(t *testing.T) {
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
template, editInTerminal := GetEditTemplate(s.osConfig, s.guessDefaultEditor)
template, suspend := GetEditTemplate(s.osConfig, s.guessDefaultEditor)
assert.Equal(t, s.expectedEditTemplate, template)
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
assert.Equal(t, s.expectedSuspend, suspend)
template, editInTerminal = GetEditAtLineTemplate(s.osConfig, s.guessDefaultEditor)
template, suspend = GetEditAtLineTemplate(s.osConfig, s.guessDefaultEditor)
assert.Equal(t, s.expectedEditAtLineTemplate, template)
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
assert.Equal(t, s.expectedSuspend, suspend)
template = GetEditAtLineAndWaitTemplate(s.osConfig, s.guessDefaultEditor)
assert.Equal(t, s.expectedEditAtLineAndWaitTemplate, template)
+317 -137
View File
@@ -5,139 +5,269 @@ import (
)
type UserConfig struct {
Gui GuiConfig `yaml:"gui"`
Git GitConfig `yaml:"git"`
Update UpdateConfig `yaml:"update"`
Refresher RefresherConfig `yaml:"refresher"`
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
Keybinding KeybindingConfig `yaml:"keybinding"`
// OS determines what defaults are set for opening files and links
OS OSConfig `yaml:"os,omitempty"`
DisableStartupPopups bool `yaml:"disableStartupPopups"`
CustomCommands []CustomCommand `yaml:"customCommands"`
Services map[string]string `yaml:"services"`
NotARepository string `yaml:"notARepository"`
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
// Config relating to the Lazygit UI
Gui GuiConfig `yaml:"gui"`
// Config relating to git
Git GitConfig `yaml:"git"`
// Periodic update checks
Update UpdateConfig `yaml:"update"`
// Background refreshes
Refresher RefresherConfig `yaml:"refresher"`
// If true, show a confirmation popup before quitting Lazygit
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
// If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
// Keybindings
Keybinding KeybindingConfig `yaml:"keybinding"`
// Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc
OS OSConfig `yaml:"os,omitempty"`
// If true, don't display introductory popups upon opening Lazygit.
// Lazygit sets this to true upon first runninng the program so that you don't see introductory popups every time you open the program.
DisableStartupPopups bool `yaml:"disableStartupPopups"`
// User-configured commands that can be invoked from within Lazygit
CustomCommands []CustomCommand `yaml:"customCommands"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-pull-request-urls
Services map[string]string `yaml:"services"`
// What to do when opening Lazygit outside of a git repo.
// - 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo
// - 'create': initialize a new repo
// - 'skip': open most recent repo
// - 'quit': exit Lazygit
NotARepository string `yaml:"notARepository"`
// If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
}
type RefresherConfig struct {
// File/submodule refresh interval in seconds.
// Auto-refresh can be disabled via option 'git.autoRefresh'.
RefreshInterval int `yaml:"refreshInterval"`
FetchInterval int `yaml:"fetchInterval"`
// Re-fetch interval in seconds.
// Auto-fetch can be disabled via option 'git.autoFetch'.
FetchInterval int `yaml:"fetchInterval"`
}
type GuiConfig struct {
AuthorColors map[string]string `yaml:"authorColors"`
BranchColors map[string]string `yaml:"branchColors"`
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
MouseEvents bool `yaml:"mouseEvents"`
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
SidePanelWidth float64 `yaml:"sidePanelWidth"`
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
Language string `yaml:"language"`
TimeFormat string `yaml:"timeFormat"`
ShortTimeFormat string `yaml:"shortTimeFormat"`
Theme ThemeConfig `yaml:"theme"`
CommitLength CommitLengthConfig `yaml:"commitLength"`
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
ShowListFooter bool `yaml:"showListFooter"`
ShowFileTree bool `yaml:"showFileTree"`
ShowRandomTip bool `yaml:"showRandomTip"`
ShowCommandLog bool `yaml:"showCommandLog"`
ShowBottomLine bool `yaml:"showBottomLine"`
ShowIcons bool `yaml:"showIcons"`
NerdFontsVersion string `yaml:"nerdFontsVersion"`
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
ExperimentalShowBranchHeads bool `yaml:"experimentalShowBranchHeads"`
CommandLogSize int `yaml:"commandLogSize"`
SplitDiff string `yaml:"splitDiff"`
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
WindowSize string `yaml:"windowSize"`
Border string `yaml:"border"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color
AuthorColors map[string]string `yaml:"authorColors"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
BranchColors map[string]string `yaml:"branchColors"`
// The number of lines you scroll by when scrolling the main window
ScrollHeight int `yaml:"scrollHeight"`
// If true, allow scrolling past the bottom of the content in the main window
ScrollPastBottom bool `yaml:"scrollPastBottom"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin
ScrollOffMargin int `yaml:"scrollOffMargin"`
// One of: 'margin' (default) | 'jump'
ScrollOffBehavior string `yaml:"scrollOffBehavior"`
// If true, capture mouse events.
// When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
MouseEvents bool `yaml:"mouseEvents"`
// If true, do not show a warning when discarding changes in the staging view.
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
// If true, do not show warning when applying/popping the stash
SkipStashWarning bool `yaml:"skipStashWarning"`
// If true, do not show a warning when attempting to commit without any staged files; instead stage all unstaged files.
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
// If true, do not show a warning when rewording a commit via an external editor
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
// Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section.
// Number from 0 to 1.0.
SidePanelWidth float64 `yaml:"sidePanelWidth"`
// If true, increase the height of the focused side window; creating an accordion effect.
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
// Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split.
// Options are:
// - 'horizontal': split the window horizontally
// - 'vertical': split the window vertically
// - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
// One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
Language string `yaml:"language"`
// Format used when displaying time e.g. commit time.
// Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
TimeFormat string `yaml:"timeFormat"`
// Format used when displaying time if the time is less than 24 hours ago.
// Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
ShortTimeFormat string `yaml:"shortTimeFormat"`
// Config relating to colors and styles.
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes
Theme ThemeConfig `yaml:"theme"`
// Config relating to the commit length indicator
CommitLength CommitLengthConfig `yaml:"commitLength"`
// If true, show the '5 of 20' footer at the bottom of list views
ShowListFooter bool `yaml:"showListFooter"`
// If true, display the files in the file views as a tree. If false, display the files as a flat list.
// This can be toggled from within Lazygit with the '~' key, but that will not change the default.
ShowFileTree bool `yaml:"showFileTree"`
// If true, show a random tip in the command log when Lazygit starts
ShowRandomTip bool `yaml:"showRandomTip"`
// If true, show the command log
ShowCommandLog bool `yaml:"showCommandLog"`
// If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action.
ShowBottomLine bool `yaml:"showBottomLine"`
// If true, show jump-to-window keybindings in window titles.
ShowPanelJumps bool `yaml:"showPanelJumps"`
// Deprecated: use nerdFontsVersion instead
ShowIcons bool `yaml:"showIcons"`
// Nerd fonts version to use.
// One of: '2' | '3' | empty string (default)
// If empty, do not show icons.
NerdFontsVersion string `yaml:"nerdFontsVersion"`
// If true, show commit hashes alongside branch names in the branches view.
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
// Height of the command log view
CommandLogSize int `yaml:"commandLogSize"`
// Whether to split the main window when viewing file changes.
// One of: 'auto' | 'always'
// If 'auto', only split the main window when a file has both staged and unstaged changes
SplitDiff string `yaml:"splitDiff"`
// Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default).
// One of: 'normal' (default) | 'half' | 'full'
WindowSize string `yaml:"windowSize"`
// Window border style.
// One of 'rounded' (default) | 'single' | 'double' | 'hidden'
Border string `yaml:"border"`
// If true, show a seriously epic explosion animation when nuking the working tree.
AnimateExplosion bool `yaml:"animateExplosion"`
// Whether to stack UI components on top of each other.
// One of 'auto' (default) | 'always' | 'never'
PortraitMode string `yaml:"portraitMode"`
}
type ThemeConfig struct {
ActiveBorderColor []string `yaml:"activeBorderColor"`
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
// Border color of focused window
ActiveBorderColor []string `yaml:"activeBorderColor"`
// Border color of non-focused windows
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
// Border color of focused window when searching in that window
SearchingActiveBorderColor []string `yaml:"searchingActiveBorderColor"`
OptionsTextColor []string `yaml:"optionsTextColor"`
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
DefaultFgColor []string `yaml:"defaultFgColor"`
// Color of keybindings help text in the bottom line
OptionsTextColor []string `yaml:"optionsTextColor"`
// Background color of selected line.
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
// Background color of selected range
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
// Foreground color of copied commit
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
// Background color of copied commit
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
// Foreground color of marked base commit (for rebase)
MarkedBaseCommitFgColor []string `yaml:"markedBaseCommitFgColor"`
// Background color of marked base commit (for rebase)
MarkedBaseCommitBgColor []string `yaml:"markedBaseCommitBgColor"`
// Color for file with unstaged changes
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
// Default text color
DefaultFgColor []string `yaml:"defaultFgColor"`
}
type CommitLengthConfig struct {
// If true, show an indicator of commit message length
Show bool `yaml:"show"`
}
type GitConfig struct {
Paging PagingConfig `yaml:"paging"`
Commit CommitConfig `yaml:"commit"`
Merging MergingConfig `yaml:"merging"`
MainBranches []string `yaml:"mainBranches"`
SkipHookPrefix string `yaml:"skipHookPrefix"`
AutoFetch bool `yaml:"autoFetch"`
AutoRefresh bool `yaml:"autoRefresh"`
FetchAll bool `yaml:"fetchAll"`
BranchLogCmd string `yaml:"branchLogCmd"`
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
OverrideGpg bool `yaml:"overrideGpg"`
DisableForcePushing bool `yaml:"disableForcePushing"`
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
// this should really be under 'gui', not 'git'
ParseEmoji bool `yaml:"parseEmoji"`
Log LogConfig `yaml:"log"`
DiffContextSize int `yaml:"diffContextSize"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
Paging PagingConfig `yaml:"paging"`
// Config relating to committing
Commit CommitConfig `yaml:"commit"`
// Config relating to merging
Merging MergingConfig `yaml:"merging"`
// list of branches that are considered 'main' branches, used when displaying commits
MainBranches []string `yaml:"mainBranches"`
// Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP'
SkipHookPrefix string `yaml:"skipHookPrefix"`
// If true, periodically fetch from remote
AutoFetch bool `yaml:"autoFetch"`
// If true, periodically refresh files and submodules
AutoRefresh bool `yaml:"autoRefresh"`
// If true, pass the --all arg to git fetch
FetchAll bool `yaml:"fetchAll"`
// Command used when displaying the current branch git log in the main window
BranchLogCmd string `yaml:"branchLogCmd"`
// Command used to display git log of all branches in the main window
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
// If true, do not spawn a separate process when using GPG
OverrideGpg bool `yaml:"overrideGpg"`
// If true, do not allow force pushes
DisableForcePushing bool `yaml:"disableForcePushing"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
// If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀
// (This should really be under 'gui', not 'git')
ParseEmoji bool `yaml:"parseEmoji"`
// Config for showing the log in the commits view
Log LogConfig `yaml:"log"`
}
type PagingConfig struct {
ColorArg string `yaml:"colorArg"`
Pager string `yaml:"pager"`
UseConfig bool `yaml:"useConfig"`
// Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never'
ColorArg string `yaml:"colorArg"`
// e.g.
// diff-so-fancy
// delta --dark --paging=never
// ydiff -p cat -s --wrap --width={{columnWidth}}
Pager string `yaml:"pager"`
// If 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).
UseConfig bool `yaml:"useConfig"`
// e.g. 'difft --color=always'
ExternalDiffCommand string `yaml:"externalDiffCommand"`
}
type CommitConfig struct {
// If true, pass '--signoff' flag when committing
SignOff bool `yaml:"signOff"`
}
type MergingConfig struct {
ManualCommit bool `yaml:"manualCommit"`
Args string `yaml:"args"`
// If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang
// Only applicable to unix users.
ManualCommit bool `yaml:"manualCommit"`
// Extra args passed to `git merge`, e.g. --no-ff
Args string `yaml:"args"`
}
type LogConfig struct {
Order string `yaml:"order"` // one of date-order, author-date-order, topo-order
ShowGraph string `yaml:"showGraph"` // one of always, never, when-maximised
ShowWholeGraph bool `yaml:"showWholeGraph"`
// One of: 'date-order' | 'author-date-order' | 'topo-order'
// 'topo-order' makes it easier to read the git log graph, but commits may not
// appear chronologically. See https://git-scm.com/docs/
Order string `yaml:"order"`
// This determines whether the git graph is rendered in the commits panel
// One of 'always' | 'never' 'when-maximised'
ShowGraph string `yaml:"showGraph"`
// displays the whole git graph by default in the commits view (equivalent to passing the `--all` argument to `git log`)
ShowWholeGraph bool `yaml:"showWholeGraph"`
}
type CommitPrefixConfig struct {
// pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*"
Pattern string `yaml:"pattern"`
// Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] "
Replace string `yaml:"replace"`
}
type UpdateConfig struct {
// One of: 'prompt' (default) | 'background' | 'never'
Method string `yaml:"method"`
Days int64 `yaml:"days"`
// Period in days between update checks
Days int64 `yaml:"days"`
}
type KeybindingConfig struct {
Universal KeybindingUniversalConfig `yaml:"universal"`
Status KeybindingStatusConfig `yaml:"status"`
Files KeybindingFilesConfig `yaml:"files"`
Branches KeybindingBranchesConfig `yaml:"branches"`
Worktrees KeybindingWorktreesConfig `yaml:"worktrees"`
Commits KeybindingCommitsConfig `yaml:"commits"`
Stash KeybindingStashConfig `yaml:"stash"`
CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"`
Main KeybindingMainConfig `yaml:"main"`
Submodules KeybindingSubmodulesConfig `yaml:"submodules"`
Universal KeybindingUniversalConfig `yaml:"universal"`
Status KeybindingStatusConfig `yaml:"status"`
Files KeybindingFilesConfig `yaml:"files"`
Branches KeybindingBranchesConfig `yaml:"branches"`
Worktrees KeybindingWorktreesConfig `yaml:"worktrees"`
Commits KeybindingCommitsConfig `yaml:"commits"`
Stash KeybindingStashConfig `yaml:"stash"`
CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"`
Main KeybindingMainConfig `yaml:"main"`
Submodules KeybindingSubmodulesConfig `yaml:"submodules"`
CommitMessage KeybindingCommitMessageConfig `yaml:"commitMessage"`
}
// damn looks like we have some inconsistencies here with -alt and -alt1
@@ -218,6 +348,7 @@ type KeybindingFilesConfig struct {
CommitChangesWithoutHook string `yaml:"commitChangesWithoutHook"`
AmendLastCommit string `yaml:"amendLastCommit"`
CommitChangesWithEditor string `yaml:"commitChangesWithEditor"`
ConfirmDiscard string `yaml:"confirmDiscard"`
IgnoreFile string `yaml:"ignoreFile"`
RefreshFiles string `yaml:"refreshFiles"`
StashAllChanges string `yaml:"stashAllChanges"`
@@ -268,6 +399,7 @@ type KeybindingCommitsConfig struct {
CherryPickCopy string `yaml:"cherryPickCopy"`
CherryPickCopyRange string `yaml:"cherryPickCopyRange"`
PasteCommits string `yaml:"pasteCommits"`
MarkCommitAsBaseForRebase string `yaml:"markCommitAsBaseForRebase"`
CreateTag string `yaml:"tagCommit"`
CheckoutCommit string `yaml:"checkoutCommit"`
ResetCherryPick string `yaml:"resetCherryPick"`
@@ -300,6 +432,10 @@ type KeybindingSubmodulesConfig struct {
BulkMenu string `yaml:"bulkMenu"`
}
type KeybindingCommitMessageConfig struct {
SwitchToEditor string `yaml:"switchToEditor"`
}
// OSConfig contains config on the level of the os
type OSConfig struct {
// Command for editing a file. Should contain "{{filename}}".
@@ -313,10 +449,10 @@ type OSConfig struct {
// window is closed.
EditAtLineAndWait string `yaml:"editAtLineAndWait,omitempty"`
// Whether the given edit commands use the terminal. Used to decide whether
// lazygit needs to suspend to the background before calling the editor.
// Whether lazygit suspends until an edit process returns
// Pointer to bool so that we can distinguish unset (nil) from false.
EditInTerminal *bool `yaml:"editInTerminal,omitempty"`
// We're naming this `editInTerminal` for backwards compatibility
SuspendOnEdit *bool `yaml:"editInTerminal,omitempty"`
// For opening a directory in an editor
OpenDirInEditor string `yaml:"openDirInEditor,omitempty"`
@@ -354,6 +490,10 @@ type OSConfig struct {
// OpenLinkCommand is the command for opening a link
// Deprecated: use OpenLink instead.
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
// CopyToClipboardCmd is the command for copying to clipboard.
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-clipboard
CopyToClipboardCmd string `yaml:"copyToClipboardCmd,omitempty"`
}
type CustomCommandAfterHook struct {
@@ -361,50 +501,79 @@ type CustomCommandAfterHook struct {
}
type CustomCommand struct {
Key string `yaml:"key"`
Context string `yaml:"context"`
Command string `yaml:"command"`
Subprocess bool `yaml:"subprocess"`
Prompts []CustomCommandPrompt `yaml:"prompts"`
LoadingText string `yaml:"loadingText"`
Description string `yaml:"description"`
Stream bool `yaml:"stream"`
ShowOutput bool `yaml:"showOutput"`
After CustomCommandAfterHook `yaml:"after"`
// The key to trigger the command. Use a single letter or one of the values from https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md
Key string `yaml:"key"`
// The context in which to listen for the key
Context string `yaml:"context"`
// The command to run (using Go template syntax for placeholder values)
Command string `yaml:"command"`
// If true, run the command in a subprocess (e.g. if the command requires user input)
Subprocess bool `yaml:"subprocess"`
// A list of prompts that will request user input before running the final command
Prompts []CustomCommandPrompt `yaml:"prompts"`
// Text to display while waiting for command to finish
LoadingText string `yaml:"loadingText"`
// Label for the custom command when displayed in the keybindings menu
Description string `yaml:"description"`
// If true, stream the command's output to the Command Log panel
Stream bool `yaml:"stream"`
// If true, show the command's output in a popup within Lazygit
ShowOutput bool `yaml:"showOutput"`
// Actions to take after the command has completed
After CustomCommandAfterHook `yaml:"after"`
}
type CustomCommandPrompt struct {
// one of 'input', 'menu', 'confirm', or 'menuFromCommand'
Type string `yaml:"type"`
Key string `yaml:"key"`
// One of: 'input' | 'menu' | 'confirm' | 'menuFromCommand'
Type string `yaml:"type"`
// 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
Key string `yaml:"key"`
// The title to display in the popup panel
Title string `yaml:"title"`
// these only apply to input prompts
InitialValue string `yaml:"initialValue"`
Suggestions CustomCommandSuggestions `yaml:"suggestions"`
// The initial value to appear in the text box.
// Only for input prompts.
InitialValue string `yaml:"initialValue"`
// Shows suggestions as the input is entered
// Only for input prompts.
Suggestions CustomCommandSuggestions `yaml:"suggestions"`
// this only applies to confirm prompts
// The message of the confirmation prompt.
// Only for confirm prompts.
Body string `yaml:"body"`
// this only applies to menus
// Menu options.
// Only for menu prompts.
Options []CustomCommandMenuOption
// this only applies to menuFromCommand
Command string `yaml:"command"`
Filter string `yaml:"filter"`
// The command to run to generate menu options
// Only for menuFromCommand prompts.
Command string `yaml:"command"`
// The regexp to run specifying groups which are going to be kept from the command's output.
// Only for menuFromCommand prompts.
Filter string `yaml:"filter"`
// How to format matched groups from the filter to construct a menu item's value.
// Only for menuFromCommand prompts.
ValueFormat string `yaml:"valueFormat"`
// Like valueFormat but for the labels. If `labelFormat` is not specified, `valueFormat` is shown instead.
// Only for menuFromCommand prompts.
LabelFormat string `yaml:"labelFormat"`
}
type CustomCommandSuggestions struct {
Preset string `yaml:"preset"`
// Uses built-in logic to obtain the suggestions. One of 'authors' | 'branches' | 'files' | 'refs' | 'remotes' | 'remoteBranches' | 'tags'
Preset string `yaml:"preset"`
// Command to run such that each line in the output becomes a suggestion. Mutually exclusive with 'preset' field.
Command string `yaml:"command"`
}
type CustomCommandMenuOption struct {
Name string `yaml:"name"`
// The first part of the label
Name string `yaml:"name"`
// The second part of the label
Description string `yaml:"description"`
Value string `yaml:"value"`
// The value that will be used in the command
Value string `yaml:"value"`
}
func GetDefaultConfig() *UserConfig {
@@ -412,6 +581,8 @@ func GetDefaultConfig() *UserConfig {
Gui: GuiConfig{
ScrollHeight: 2,
ScrollPastBottom: true,
ScrollOffMargin: 2,
ScrollOffBehavior: "margin",
MouseEvents: true,
SkipDiscardChangeWarning: false,
SkipStashWarning: false,
@@ -430,30 +601,35 @@ func GetDefaultConfig() *UserConfig {
SelectedRangeBgColor: []string{"blue"},
CherryPickedCommitBgColor: []string{"cyan"},
CherryPickedCommitFgColor: []string{"blue"},
MarkedBaseCommitBgColor: []string{"yellow"},
MarkedBaseCommitFgColor: []string{"blue"},
UnstagedChangesColor: []string{"red"},
DefaultFgColor: []string{"default"},
},
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
NerdFontsVersion: "",
ExperimentalShowBranchHeads: false,
ShowBranchCommitHash: false,
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "single",
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowPanelJumps: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
NerdFontsVersion: "",
ShowBranchCommitHash: false,
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "rounded",
AnimateExplosion: true,
PortraitMode: "auto",
},
Git: GitConfig{
Paging: PagingConfig{
ColorArg: "always",
Pager: "",
UseConfig: false,
ColorArg: "always",
Pager: "",
UseConfig: false,
ExternalDiffCommand: "",
},
Commit: CommitConfig{
SignOff: false,
@@ -477,7 +653,6 @@ func GetDefaultConfig() *UserConfig {
DisableForcePushing: false,
CommitPrefixes: map[string]CommitPrefixConfig(nil),
ParseEmoji: false,
DiffContextSize: 3,
},
Refresher: RefresherConfig{
RefreshInterval: 10,
@@ -516,7 +691,7 @@ func GetDefaultConfig() *UserConfig {
NextMatch: "n",
PrevMatch: "N",
StartSearch: "/",
OptionMenu: "",
OptionMenu: "<disabled>",
OptionMenuAlt1: "?",
Select: "<space>",
GoInto: "<enter>",
@@ -575,6 +750,7 @@ func GetDefaultConfig() *UserConfig {
ToggleTreeView: "`",
OpenMergeTool: "M",
OpenStatusFilter: "<c-b>",
ConfirmDiscard: "x",
},
Branches: KeybindingBranchesConfig{
CopyPullRequestURL: "<c-y>",
@@ -612,6 +788,7 @@ func GetDefaultConfig() *UserConfig {
CherryPickCopy: "c",
CherryPickCopyRange: "C",
PasteCommits: "v",
MarkCommitAsBaseForRebase: "B",
CreateTag: "T",
CheckoutCommit: "<space>",
ResetCherryPick: "<c-R>",
@@ -639,6 +816,9 @@ func GetDefaultConfig() *UserConfig {
Update: "u",
BulkMenu: "b",
},
CommitMessage: KeybindingCommitMessageConfig{
SwitchToEditor: "<c-o>",
},
},
OS: OSConfig{},
DisableStartupPopups: false,
+10 -1
View File
@@ -14,6 +14,15 @@ func SetGitDirEnv(value string) {
os.Setenv("GIT_DIR", value)
}
func UnsetGitDirEnv() {
func GetWorkTreeEnv() string {
return os.Getenv("GIT_WORK_TREE")
}
func SetWorkTreeEnv(value string) {
os.Setenv("GIT_WORK_TREE", value)
}
func UnsetGitLocationEnvVars() {
_ = os.Unsetenv("GIT_DIR")
_ = os.Unsetenv("GIT_WORK_TREE")
}
+3 -3
View File
@@ -4,9 +4,9 @@ import (
"errors"
"sync"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -134,7 +134,7 @@ func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, ty
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
contextsToDeactivate = append(contextsToDeactivate, topContext)
_, self.ContextStack = slices.Pop(self.ContextStack)
_, self.ContextStack = utils.Pop(self.ContextStack)
}
self.ContextStack = append(self.ContextStack, c)
@@ -154,7 +154,7 @@ func (self *ContextMgr) Pop() error {
}
var currentContext types.Context
currentContext, self.ContextStack = slices.Pop(self.ContextStack)
currentContext, self.ContextStack = utils.Pop(self.ContextStack)
newContext := self.ContextStack[len(self.ContextStack)-1]
+12 -4
View File
@@ -24,14 +24,16 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
},
)
getDisplayStrings := func(startIdx int, length int) [][]string {
getDisplayStrings := func(_ int, _ int) [][]string {
return presentation.GetBranchListDisplayStrings(
viewModel.GetItems(),
c.State().GetItemOperation,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().Diffing.Ref,
c.Tr,
c.UserConfig,
c.Model().Worktrees,
c.Model().MergedBranches,
)
}
@@ -45,9 +47,11 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
ListRenderer: ListRenderer{
list: viewModel,
getDisplayStrings: getDisplayStrings,
},
c: c,
},
}
@@ -83,3 +87,7 @@ func (self *BranchesContext) GetDiffTerminals() []string {
}
return nil
}
func (self *BranchesContext) ShowBranchHeadsInSubCommits() bool {
return true
}
+8 -6
View File
@@ -1,12 +1,12 @@
package context
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
type CommitFilesContext struct {
@@ -28,13 +28,13 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
c.UserConfig.Gui.ShowFileTree,
)
getDisplayStrings := func(startIdx int, length int) [][]string {
getDisplayStrings := func(_ int, _ int) [][]string {
if viewModel.Len() == 0 {
return [][]string{{style.FgRed.Sprint("(none)")}}
}
lines := presentation.RenderCommitFileTree(viewModel, c.Modes().Diffing.Ref, c.Git().Patch.PatchBuilder)
return slices.Map(lines, func(line string) []string {
return lo.Map(lines, func(line string, _ int) []string {
return []string{line}
})
}
@@ -54,9 +54,11 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
Transient: true,
}),
),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
ListRenderer: ListRenderer{
list: viewModel,
getDisplayStrings: getDisplayStrings,
},
c: c,
},
}

Some files were not shown because too many files have changed in this diff Show More