Compare commits

..

478 Commits

Author SHA1 Message Date
README-bot
16802a048e Updated README.md 2023-02-01 10:52:32 +00:00
Jesse Duffield
c0e805718d Merge pull request #2358 from phanithinks/#2319_default_screen_mode 2023-02-01 21:52:09 +11:00
Phanindra kumar Paladi
35c5f940a4 Fixing indent in user_config.go 2023-02-01 09:50:37 +05:30
README-bot
77093451d4 Updated README.md 2023-01-31 06:03:06 +00:00
Jesse Duffield
368d6437b8 Merge pull request #2373 from phanithinks/clipboard_patch_option_2357 2023-01-31 17:02:46 +11:00
Phanindra Kumar Paladi
01f0efb997 Merge branch 'master' into #2319_default_screen_mode 2023-01-29 10:25:14 +05:30
Phanindra kumar Paladi
d0851113d1 Skipping copy_patch_to_clipboard test case 2023-01-29 10:20:56 +05:30
Phanindra kumar Paladi
df58c75ca4 Fixed breaking integrtion tests(old) 2023-01-29 10:03:59 +05:30
Jesse Duffield
d8c7d47067 Merge pull request #2395 from stefanhaller/trailing-lf-when-copying-diff-lines 2023-01-29 14:19:29 +11:00
Jesse Duffield
f79a8c281f Merge pull request #2398 from Ryooooooga/fix-detached-head
fix https://github.com/jesseduffield/lazygit/issues/1467
2023-01-29 14:19:05 +11:00
Jesse Duffield
996a1e469f Merge pull request #2401 from Ryooooooga/disable-log-order 2023-01-29 14:05:31 +11:00
README-bot
9d5d61260a Updated README.md 2023-01-29 02:43:45 +00:00
Jesse Duffield
18db5eafd4 Merge pull request #2384 from stefanhaller/disable-reword-in-editor-prompt 2023-01-29 13:43:23 +11:00
Ryooooooga
2183c157d4 feat(log): allow to disable git.log.order 2023-01-28 21:17:05 +09:00
Ryooooooga
5dec080719 fix: fix RefName of detached HEAD to works in Chinese 2023-01-27 20:45:18 +09:00
stk
fc38e3b54d Don't omit final line feed when copying diff lines to clipboard 2023-01-26 10:30:05 +01:00
stk
93d845cb01 Cleanup: remove unused function RenderPlain 2023-01-26 10:30:05 +01:00
stk
67fb28e2b8 Add user config gui.skipRewordInEditorWarning 2023-01-26 09:01:22 +01:00
Jesse Duffield
679b0456f3 Merge pull request #2388 from Ryooooooga/remove-unused-texts 2023-01-26 13:46:01 +11:00
Jesse Duffield
f7f24dbfc1 better test 2023-01-26 13:25:56 +11:00
Jesse Duffield
b942df02a1 Merge pull request #2376 from Ryooooooga/fix-resolve-placeholder 2023-01-26 13:03:19 +11:00
Jesse Duffield
f12a2edefa Merge pull request #2372 from Ryooooooga/fix-installation-ubuntu 2023-01-26 12:58:49 +11:00
README-bot
7e54b5641f Updated README.md 2023-01-26 00:51:36 +00:00
Jesse Duffield
89a09be6a0 Merge pull request #2262 from daramayis/uffizzi 2023-01-26 11:51:17 +11:00
Ryooooooga
069af50f50 chore(i18n): remove unused texts 2023-01-24 21:24:46 +09:00
Phanindra kumar Paladi
c6929c36ae Corrected test assert 2023-01-23 15:53:21 +05:30
Phanindra kumar Paladi
e8f4508cba Fixed integration test case 2023-01-23 15:48:43 +05:30
stk
b8d33b8f7b Extract helper function doRewordEditor
No behavior change, just a preparation for the next commit.
2023-01-22 15:59:32 +01:00
Phanindra kumar Paladi
946b8b5670 Fixed the lable in the custom_patch_options_panel.go 2023-01-18 21:13:31 +05:30
Phanindra kumar Paladi
d479a41cad Added Integration testing the copy to clipboard in patchbuilding 2023-01-18 21:05:40 +05:30
Ryooooooga
7149cfeb11 fix: fix ReplacePlaceholderString 2023-01-18 20:56:22 +09:00
Phanindra kumar Paladi
265cdde7bc Fixed typo 2023-01-18 10:14:48 +05:30
Phanindra kumar Paladi
e87fc4a229 Change key of clipboard copy 2023-01-18 05:50:47 +05:30
Ryooooooga
b45c5d8491 docs(README.md): fix installation scripts for Ubuntu 2023-01-17 16:42:51 +09:00
Phanindra kumar Paladi
f6f82091bc Added copy to clipboard option to the patch options 2023-01-17 09:07:07 +05:30
README-bot
48df9b7f4e Updated README.md 2023-01-16 22:19:40 +00:00
Jesse Duffield
fd86d29400 Merge pull request #2343 from Ryooooooga/commit-verbose 2023-01-17 09:19:22 +11:00
Phanindra kumar Paladi
a11e91e651 replaced 'screenMode' to 'windowSize' in config 2023-01-16 20:07:21 +05:30
README-bot
6127e487dd Updated README.md 2023-01-16 08:10:18 +00:00
Jesse Duffield
1d89a5daa1 Merge pull request #2356 from Ryooooooga/missing-config-docs 2023-01-16 19:09:56 +11:00
Phanindra kumar Paladi
f4ccb68464 Added screenMode configuration to gui configuration 2023-01-11 16:51:46 +05:30
Aramayis
b90af5461a feat: uffizzi integration 2023-01-10 19:29:45 +04:00
Ryooooooga
acbcf9933d docs(Config.md): add missing keybindings 2023-01-10 20:43:23 +09:00
Ryooooooga
21f8857d36 refactor: simplify log format 2023-01-06 11:15:33 +09:00
Ryooooooga
965f7bfcb2 feat(config): change git.commit.verbose to accept "default" 2023-01-06 11:15:33 +09:00
README-bot
c769a78db5 Updated README.md 2023-01-06 02:10:51 +00:00
Jesse Duffield
1cf24a02d3 Merge pull request #2345 from Ryooooooga/fix-goroutine-leaks 2023-01-06 13:10:27 +11:00
Ryooooooga
657b1e897f build: bump gocui 2023-01-06 10:59:09 +09:00
Ryooooooga
00b922604a fix: fix goroutine leaks 2023-01-06 10:51:09 +09:00
Jesse Duffield
1bb138c79c Merge pull request #2341 from knutwalker/commit-verbose 2023-01-01 13:57:49 +11:00
README-bot
c8fc1c3f5a Updated README.md 2023-01-01 01:38:27 +00:00
Jesse Duffield
c5ea80fa67 Merge pull request #2340 from Ryooooooga/improve-backward-compatibility 2023-01-01 12:38:10 +11:00
Paul Horn
d98130c3ef Add option to allow --verbose commit in editor commits 2023-01-01 02:01:04 +01:00
Ryooooooga
7c5f33980f fix: improve backward compatibility 2022-12-31 22:47:21 +09:00
Jesse Duffield
cceff63823 Merge pull request #2339 from jesseduffield/integration-tests-5 2022-12-30 22:57:17 +11:00
Jesse Duffield
5c42e1a5dc defend against possible nil function 2022-12-30 22:49:08 +11:00
Jesse Duffield
6c3671f807 appease linter 2022-12-30 22:47:56 +11:00
Jesse Duffield
89ba3a38b4 migrate filter path tests 2022-12-30 22:42:32 +11:00
Jesse Duffield
6f709456fe migrate test for rename branch and pull 2022-12-30 22:42:32 +11:00
Jesse Duffield
277ca706eb migrate fetchPrune integration test 2022-12-30 22:42:32 +11:00
Jesse Duffield
8a1c763942 more git ignore stuff in integration test 2022-12-30 22:42:32 +11:00
Jesse Duffield
31bdd27e88 Merge pull request #2333 from Ryooooooga/push-force-if-includes 2022-12-30 22:33:04 +11:00
Ryooooooga
e00f248cf7 feat: support for push --force-if-includes 2022-12-30 20:01:15 +09:00
Ryooooooga
cd9111837e feat: add GitVersion struct 2022-12-30 20:01:14 +09:00
Ryooooooga
41222f07ed chore(gui): remove unused gitConfig 2022-12-30 20:01:14 +09:00
README-bot
ae780fdb81 Updated README.md 2022-12-30 03:28:33 +00:00
Jesse Duffield
a05bdc3ee4 Merge pull request #2338 from jesseduffield/snake 2022-12-30 14:28:14 +11:00
Jesse Duffield
1da0427e3a appease linter 2022-12-30 12:18:59 +11:00
Jesse Duffield
af5b3be286 integrate snake game into lazygit 2022-12-30 12:18:59 +11:00
Jesse Duffield
81281a49b2 add snake game 2022-12-29 14:32:33 +11:00
Jesse Duffield
ff8823093c Merge pull request #2336 from jesseduffield/migrate-even-more-tests 2022-12-28 16:01:11 +11:00
Jesse Duffield
0300bfdec2 update readme 2022-12-28 15:35:12 +11:00
Jesse Duffield
f770a6246b rename function 2022-12-28 14:19:56 +11:00
Jesse Duffield
5e9a897348 migrate ignore gitignore integration test 2022-12-28 13:35:07 +11:00
Jesse Duffield
f2d0f362d4 migrate discard staged changes test 2022-12-28 13:24:23 +11:00
Jesse Duffield
ae07cf5506 migrate discard old file change test 2022-12-28 13:01:32 +11:00
Jesse Duffield
f3fa9ec2d1 Merge pull request #2311 from wakaka6/add_return_alt1 2022-12-28 11:54:16 +11:00
Jesse Duffield
e661916ba6 Merge pull request #2331 from Ryooooooga/remove-unused-config 2022-12-28 11:43:08 +11:00
Jesse Duffield
0a8731eecf Merge pull request #2335 from jesseduffield/alas-more-test-refactoring 2022-12-28 11:30:51 +11:00
Jesse Duffield
14a974742f rename from asserter to driver 2022-12-28 11:27:48 +11:00
Jesse Duffield
534703a023 Merge pull request #2334 from jesseduffield/more-test-refactoring 2022-12-28 11:23:39 +11:00
Jesse Duffield
9fef4447b6 move popup assertions into a struct 2022-12-28 11:00:22 +11:00
Jesse Duffield
7aa843c75a create actions struct 2022-12-28 10:54:38 +11:00
Jesse Duffield
a27092a7ad remove broken test 2022-12-28 10:43:14 +11:00
Jesse Duffield
a3450dfdfc fix suggestions test 2022-12-28 10:41:42 +11:00
Jesse Duffield
b4e9806352 fix test 2022-12-28 10:32:36 +11:00
Jesse Duffield
f495945b87 fix bug 2022-12-28 10:29:32 +11:00
Jesse Duffield
47de61b57c update integration test readme 2022-12-28 10:23:59 +11:00
Jesse Duffield
06c878c051 minor changes 2022-12-28 10:23:54 +11:00
Jesse Duffield
ed93e0a2b0 remove dependency on model 2022-12-27 22:52:20 +11:00
Jesse Duffield
c5050ecabd move shell into test driver 2022-12-27 21:47:37 +11:00
Jesse Duffield
78b495f50a rename input to t 2022-12-27 21:35:36 +11:00
Jesse Duffield
53e06b71ae add tap function 2022-12-27 21:26:18 +11:00
Jesse Duffield
b166b8f776 combine assert and input structs, clean up interface 2022-12-27 21:26:18 +11:00
Jesse Duffield
c5c9f5bb94 rename 2022-12-27 21:26:18 +11:00
Jesse Duffield
09e80e5f2a better namespacing for assertions 2022-12-27 21:26:18 +11:00
Jesse Duffield
be30cbb375 add view asserter getter struct 2022-12-27 21:26:18 +11:00
Jesse Duffield
b64f55518b refactor commit message stuff in integration tests 2022-12-27 21:26:18 +11:00
Jesse Duffield
926ed7b9b2 more refactoring of popup stuff 2022-12-27 21:26:18 +11:00
Jesse Duffield
8052ac4fd6 add prompt asserter 2022-12-27 21:26:18 +11:00
Jesse Duffield
c976839a63 refactor prompt handling in integration tests 2022-12-27 21:26:17 +11:00
Ryooooooga
ac127f017e chore(config): remove unused config 2022-12-26 16:14:30 +09:00
README-bot
17140e1d8d Updated README.md 2022-12-26 06:59:43 +00:00
Jesse Duffield
cd418ec929 Merge pull request #2330 from jesseduffield/yet-more-test-migrations 2022-12-26 17:59:30 +11:00
Jesse Duffield
8c89069965 update readme 2022-12-26 17:51:19 +11:00
Jesse Duffield
09db4c4397 allow checking if line is selected in Lines and TopLines methods 2022-12-26 17:45:10 +11:00
Jesse Duffield
96310288ee allow chaining matchers 2022-12-26 17:15:33 +11:00
Jesse Duffield
c841ba8237 add switch-to-view methods 2022-12-26 16:49:54 +11:00
Jesse Duffield
9a6f21ce42 cleaner test assertions 2022-12-26 12:20:13 +11:00
Jesse Duffield
fa0414777f rename SelectedLine to CurrentLine in tests 2022-12-26 10:42:19 +11:00
Jesse Duffield
5d2584a188 introduce ViewLines functions 2022-12-25 11:38:00 +11:00
Jesse Duffield
fb15a2f4f8 Merge pull request #2326 from Ryooooooga/fix-scroll
fix https://github.com/jesseduffield/lazygit/issues/2309
2022-12-24 19:20:23 +11:00
README-bot
ef62a35d79 Updated README.md 2022-12-24 08:19:34 +00:00
Jesse Duffield
05425cfba0 Merge pull request #2329 from jesseduffield/yet-more-test-migrations 2022-12-24 19:19:15 +11:00
Jesse Duffield
b623ecf898 add helper functions for popups in tests 2022-12-24 19:15:59 +11:00
Jesse Duffield
aedfce2845 refactor to not have Match at the start of assert method names, because it reads better that way 2022-12-24 19:14:52 +11:00
Jesse Duffield
c19f52255c add task for opening deprecated tests TUI 2022-12-24 19:14:52 +11:00
Jesse Duffield
fa97b0c76e move background code into its own file 2022-12-24 19:14:52 +11:00
Jesse Duffield
588850b090 focus terminal when running a test 2022-12-24 19:05:46 +11:00
Jesse Duffield
13639ac924 faster test 2022-12-24 19:05:46 +11:00
Jesse Duffield
5c11b1ecb7 discard changes integration test 2022-12-24 19:05:46 +11:00
Jesse Duffield
7c7f7bf9b9 migrate diffing integration tests 2022-12-21 22:52:23 +11:00
Jesse Duffield
57a1817deb don't kill long-running sandbox sessions 2022-12-21 22:51:39 +11:00
wakaka6
b6c73b3620 Change null as the default return-alt1 2022-12-20 21:39:24 +08:00
Ryooooooga
7bdba1abe4 fix(#2309): fix diff scroll 2022-12-20 22:25:49 +09:00
Jesse Duffield
c77df59b9b Merge pull request #2325 from jesseduffield/migrate-even-more-tests 2022-12-20 23:14:10 +11:00
Jesse Duffield
f910b42c9c migrate confirm-on-quit integration test 2022-12-20 23:08:39 +11:00
Jesse Duffield
dde70486a1 apply user config changes in sandbox mode 2022-12-20 23:07:43 +11:00
Jesse Duffield
186b7197e4 clean up some integration test stuff 2022-12-20 22:54:00 +11:00
Jesse Duffield
6ec88ce8ba Merge pull request #2323 from jesseduffield/migrate-more-tests 2022-12-20 22:51:04 +11:00
Jesse Duffield
e3c6738535 remove snapshot approach for new integration tests 2022-12-20 22:45:03 +11:00
Jesse Duffield
bc4ace8357 add commit revert integration test 2022-12-20 22:45:02 +11:00
Jesse Duffield
b40190bd94 add multi-line commit integration test 2022-12-20 22:45:02 +11:00
Jesse Duffield
abbd598992 bump gocui 2022-12-20 22:06:44 +11:00
Jesse Duffield
5679efe174 Merge pull request #2239 from bdach/u2f-key-prompts
close https://github.com/jesseduffield/lazygit/issues/2230
2022-12-20 21:44:29 +11:00
wakaka6
6bf28d325f Ament description about return-alt1 2022-12-20 14:08:33 +08:00
Jesse Duffield
43b5a80738 Merge pull request #2320 from jesseduffield/migrate-yet-another-integration-test 2022-12-19 23:01:42 +11:00
Jesse Duffield
b13cfdfea0 migrate branch reset integration test 2022-12-19 22:38:32 +11:00
README-bot
b647241521 Updated README.md 2022-12-19 11:26:28 +00:00
Jesse Duffield
1e85bc3d62 Merge pull request #2315 from navazjm/iss2302 2022-12-19 22:26:09 +11:00
navazjm
3a1921cab0 updated rebase confirmation message 2022-12-16 17:36:37 -06:00
wakaka6
6386a03805 add return alt1 2022-12-11 15:44:25 +08:00
Jesse Duffield
d69b2fef9a Merge pull request #2298 from arnaudperalta/commit-in-staged-menu
Closes undefined
2022-12-01 10:01:55 +11:00
Arnaud PERALTA
50b0d85cd3 integration tests for commit without pre-commit hooks in staging files menu 2022-12-01 09:12:18 +11:00
Arnaud PERALTA
bfcbf228bf commit integrations test with window name's assertion 2022-12-01 09:12:18 +11:00
Arnaud PERALTA
87e0f6b92d integration tests for commit in staged files and unstaged files menus 2022-12-01 09:12:18 +11:00
Arnaud PERALTA
d0499286e2 keybindings cheatsheet for commit in unstaged/staged 2022-12-01 09:12:18 +11:00
Arnaud PERALTA
0af63daf18 workingtree controller fixed with new references for commit in staged menu 2022-12-01 09:12:18 +11:00
Arnaud PERALTA
8b894d7bf5 wip: commit logic in helper and reported in files/staging controllers 2022-12-01 09:12:18 +11:00
README-bot
f7449ed53a Updated README.md 2022-11-30 08:40:32 +00:00
Jesse Duffield
ff25016a6a Merge pull request #2303 from jesseduffield/fix-ignore-keybinding 2022-11-30 19:40:12 +11:00
Jesse Duffield
65d6d7fb2d fix ignore file keybinding 2022-11-30 19:36:35 +11:00
Bartłomiej Dach
1a1f042f49 Add credential prompts for U2F-backed SSH keys
The 8.2 release of OpenSSH added support for FIDO/U2F hardware
authenticators, which manifests in being able to create new types of SSH
key, named `ecdsa-sk` nad `ed25519-sk`. This is relevant to lazygit,
as those SSH keys can be used to authorise git operations over SSH, as
well as signing git commits. Actual code changes are required for
correct support, as the authentication process for these types of keys
is different than the process for types supported previously.

When an operation requiring credentials is initialised with a U2F
authenticator-backed key, the first prompt is:

	Enter PIN for ${key_type} key ${path_to_key}:

at which point the user is supposed to enter a numeric (and secret) PIN,
specific to the particular FIDO/U2F authenticator using which the SSH
keypair was generated. Upon entering the correct key, the user is
supposed to physically interact with the authenticator to confirm
presence. Sometimes this is accompanied by the following text prompt:

	Confirm user presence for key ${key_type} ${key_fingerprint}

This second prompt does not always occur and it is presumed that the
user will know to perform this step even if not prompted specifically.
At this stage some authenticator devices may also begin to blink a LED
to indicate that they're waiting for input.

To facilitate lazygit's interoperability with these types of keys, add
support for the first PIN prompt, which allows "fetch", "pull", and
"push" git operations to complete.
2022-11-30 13:34:32 +11:00
Jesse Duffield
5b3f684afb Merge pull request #2276 from Ryooooooga/bump-gocui 2022-11-26 13:42:38 +11:00
README-bot
618b845f5e Updated README.md 2022-11-26 02:42:33 +00:00
Jesse Duffield
f96246c038 Merge pull request #2284 from arnaudperalta/defaultfgcolor-config
Closes https://github.com/jesseduffield/lazygit/issues/2279
2022-11-26 13:41:41 +11:00
Jesse Duffield
773eb0310f Merge pull request #2296 from jesseduffield/fix-prompt-response-not-stored 2022-11-26 13:41:30 +11:00
Jesse Duffield
03ce22f3c9 Update docs/Config.md 2022-11-26 13:39:00 +11:00
Luka Markušić
b3d086bdc1 Resolve the prompt just before using it
In case a later command depends on a prompt input from a previous one we
need to evaluate it only after the previous prompt has been confirmed.
2022-11-25 22:46:36 +01:00
Ryooooooga
cf048e4807 bump gocui 2022-11-25 21:48:44 +09:00
README-bot
a6ebc5869e Updated README.md 2022-11-24 22:54:15 +00:00
Jesse Duffield
ffc2a6805a Merge pull request #2290 from nils-a/buxfix/quote-regex 2022-11-25 09:53:57 +11:00
Nils Andresen
d24feb14e5 added test data 2022-11-24 13:17:02 +00:00
Nils Andresen
245563bc99 (#2288) quote remoteName before compiling regex
If the remote name contains special regex-chars,
the compilation of the regex might fail.
Quoting the remoteName ensures that all special chars
in the remoteName are properly escaped before compiling
the regex.
2022-11-24 12:56:28 +00:00
Arnaud PERALTA
37997dcbcd [#2279] defaultFgColor entry in theme config 2022-11-21 21:48:18 +01:00
Jesse Duffield
924a152ae9 Update CONTRIBUTING.md 2022-11-15 16:00:46 +11:00
Jesse Duffield
2b8e412ed3 Update CONTRIBUTING.md 2022-11-14 20:54:27 +11:00
Jesse Duffield
d1a8b05401 use github native to generate release notes 2022-11-14 20:21:33 +11:00
Jesse Duffield
de22238589 Merge pull request #2224 from Ryooooooga/ambiguous-branch 2022-11-14 19:09:37 +11:00
Ryooooooga
52a2e4c1dc fix: fix ambiguous branch name
test: add an integration test for checkout branch by name

fix: fix full ref name of detached head

refactor: refactor current branch loader

chore: use field name explicitly
2022-11-14 19:05:07 +11:00
Jesse Duffield
e953659ebf Merge pull request #2273 from artvi/fix/show_loading_state_when_bottomline_disabled 2022-11-14 19:01:26 +11:00
Jesse Duffield
b33ec5a050 Merge pull request #1980 from ajhynes7/stash-untracked-changes 2022-11-14 18:46:53 +11:00
Jesse Duffield
fbfec75a99 Merge pull request #2261 from sudoburt/merge-loaders-git-commands 2022-11-14 18:17:54 +11:00
sudoburt
3e73dacce3 Merge loaders package into git_commands package 2022-11-14 18:11:45 +11:00
Andrew Hynes
684d1e955e lint: try deleting blank line 2022-11-13 19:51:30 -03:30
Jesse Duffield
f67824b349 Merge pull request #2265 from nitinmewar/gitVersion 2022-11-14 10:00:58 +11:00
Art V
c53752a5f9 [#2258] hide options panel when showBottom line is disabled 2022-11-14 01:04:56 +03:00
Art V
dc163bfc4d [#2258] show bottom line when having status 2022-11-14 00:53:55 +03:00
Andrew Hynes
3c436b3457 lint: delete unused argument from handleStashSave 2022-11-13 11:02:06 -03:30
nitin mewar
526d02de1c added gitVersion for version flag
Signed-off-by: nitin mewar <nitinmewar28@gmail.com>
2022-11-13 19:27:12 +05:30
Jesse Duffield
a905a28e41 better hiding of underscores 2022-11-13 14:46:13 +11:00
Jesse Duffield
5964472ec1 hide underscores more 2022-11-13 14:41:43 +11:00
Jesse Duffield
c77c02c879 Merge pull request #2272 from jesseduffield/no-push-commits-when-resetting 2022-11-13 14:33:48 +11:00
Jesse Duffield
d26350502c stop switching focus to commits view when resetting 2022-11-13 03:26:50 +00:00
Jesse Duffield
2ddd3ddfc0 Merge pull request #2271 from jesseduffield/less-pollution-in-tests 2022-11-13 14:15:04 +11:00
Jesse Duffield
df3cd941d7 use tempdir in tests to prevent polluting worktree 2022-11-13 14:10:21 +11:00
Jesse Duffield
ea28529cbb set default branch in dev container for the sake of integration tests 2022-11-13 03:00:58 +00:00
Jesse Duffield
db592e1dc6 Merge pull request #2269 from jesseduffield/disable-underscores-in-vscode 2022-11-13 13:45:08 +11:00
Jesse Duffield
863a65cf94 disable underscores in vscode while we wait for underscore glitch to be fixed 2022-11-13 13:41:49 +11:00
Jesse Duffield
5eeaebde98 Update CONTRIBUTING.md 2022-11-13 12:05:14 +11:00
Jesse Duffield
e059641f3f more tasks 2022-11-13 12:05:07 +11:00
Jesse Duffield
fbac05fff7 Update README.md 2022-11-13 11:54:37 +11:00
README-bot
99e05ee138 Updated README.md 2022-11-13 00:51:52 +00:00
Jesse Duffield
7144691ae5 Merge pull request #2266 from jesseduffield/codespaces 2022-11-13 11:51:38 +11:00
Jesse Duffield
fb170b55a2 add some tasks 2022-11-13 11:37:52 +11:00
Jesse Duffield
903e65ae8d add devcontainer folder 2022-11-13 10:56:22 +11:00
Andrew Hynes
e56b05cbac Merge branch 'master' into stash-untracked-changes 2022-11-12 18:14:51 -03:30
Andrew Hynes
9e91aa8b43 fix: delete duplicate import 2022-11-12 18:11:34 -03:30
Andrew Hynes
7977c2deb5 fix: delete duplicate line 2022-11-12 18:10:34 -03:30
Andrew Hynes
d7fb441a3c refactor: sort list of integration tests 2022-11-12 18:09:55 -03:30
Lukasz Piatkowski
f03e1e4e42 Update go-git to handle negative refspecs 2022-11-12 18:09:16 -03:30
Jesse Duffield
91f83c6be7 use better colour defaults 2022-11-12 18:09:16 -03:30
README-bot
fd581ef70c Updated README.md 2022-11-12 18:09:15 -03:30
Jesse Duffield
f98b2edae5 fix broken CI (see https://vielmetti.typepad.com/logbook/2022/10/git-security-fixes-lead-to-fatal-transport-file-not-allowed-error-in-ci-systems-cve-2022-39253.html)
try this

WIP
2022-11-12 18:09:15 -03:30
Jesse Duffield
97ced9e14f fix could-not-access error 2022-11-12 18:09:15 -03:30
Jesse Duffield
ea09686770 Update README.md 2022-11-12 18:09:15 -03:30
Jesse Duffield
c769e5828c Update README.md 2022-11-12 18:09:15 -03:30
README-bot
33410213f1 Updated README.md 2022-11-12 18:09:15 -03:30
Jesse Duffield
af57dbd225 Update README.md 2022-11-12 18:09:15 -03:30
Andrew Hynes
66a253916e test: add more assertions 2022-11-12 18:09:15 -03:30
nitin mewar
188890fc49 added gitVersion to version flag
Signed-off-by: nitin mewar <nitinmewar28@gmail.com>
2022-11-12 16:00:24 +05:30
Jesse Duffield
3e15315339 Merge pull request #2232 from lukaspiatkowski/fix-negative-refspec
fix https://github.com/jesseduffield/go-git/pull/1
2022-11-12 19:11:52 +11:00
Lukasz Piatkowski
4fa8586191 Update go-git to handle negative refspecs 2022-11-12 07:24:14 +01:00
Jesse Duffield
3a295f34df Merge pull request #2257 from jesseduffield/better-colour-defaults 2022-11-12 15:04:22 +11:00
Jesse Duffield
1ac3ae1ad1 use better colour defaults 2022-11-12 14:59:15 +11:00
README-bot
34404162e6 Updated README.md 2022-11-11 23:58:16 +00:00
Jesse Duffield
e6e95343af Merge pull request #2260 from jesseduffield/could-not-access 2022-11-12 10:58:01 +11:00
Jesse Duffield
52316e628e fix broken CI (see https://vielmetti.typepad.com/logbook/2022/10/git-security-fixes-lead-to-fatal-transport-file-not-allowed-error-in-ci-systems-cve-2022-39253.html)
try this

WIP
2022-11-12 10:53:12 +11:00
Jesse Duffield
e8b97c9fe2 fix could-not-access error 2022-11-11 12:30:14 +11:00
Jesse Duffield
ab03cf8bcf Update README.md 2022-11-09 21:14:14 +11:00
Jesse Duffield
cc01c0de6d Update README.md 2022-11-09 19:57:26 +11:00
README-bot
4f471f352f Updated README.md 2022-11-09 08:54:28 +00:00
Jesse Duffield
a63d5c5644 Update README.md 2022-11-09 19:54:11 +11:00
Andrew Hynes
a47e72892a Merge branch 'master' into stash-untracked-changes 2022-11-01 16:08:34 -02:30
README-bot
c4e71356bb Updated README.md 2022-10-25 05:49:39 +00:00
Jesse Duffield
b0f7cf092f Merge pull request #2222 from Ryooooooga/disable-keybindings 2022-10-24 22:49:20 -07:00
Ryooooooga
808b35d8f2 docs: add examples of disabling keybindings 2022-10-18 22:23:56 +09:00
Ryooooooga
4aa9147dfa build: $ ./scripts/bump_gocui.sh 2022-10-18 22:20:04 +09:00
Ryooooooga
14ec0cd92e feat: allow null in keybindings 2022-10-18 22:20:03 +09:00
Jesse Duffield
36c6462a53 Merge pull request #2220 from Ryooooooga/rename-stash 2022-10-16 09:56:32 -07:00
Ryooooooga
2ec0b671e6 test: update stash/rename integration test 2022-10-16 16:26:17 +09:00
Ryooooooga
3103398e31 chore: refactor rename stash 2022-10-16 09:30:04 +09:00
Ryooooooga
e78e829e3a test: add an integration test for rename stash 2022-10-16 09:30:03 +09:00
Ryooooooga
eceb3a5aa6 chore: refactor rename stash 2022-10-16 09:12:43 +09:00
Ryoga
8a9eefa4d2 chore: remove unnecessary space
Co-authored-by: Jesse Duffield <jessedduffield@gmail.com>
2022-10-16 09:12:43 +09:00
Ryooooooga
11316b7a48 feat: add rename stash 2022-10-16 09:12:42 +09:00
Jesse Duffield
8f3ccd07db Merge pull request #2223 from gusandrioli/use-lazycore-to-get-dir 2022-10-15 10:05:03 -07:00
Jesse Duffield
79334b84f0 Merge pull request #2221 from Ryooooooga/commit-message-prompt 2022-10-15 10:00:14 -07:00
Gustavo Andrioli
39e84e13f4 Use lazycore utils: Clamp and GetLazyRootDirectory 2022-10-15 13:55:44 -03:00
Ryooooooga
0c3eab4059 fix: fix initial origin of commit message panel 2022-10-15 20:09:23 +09:00
Jesse Duffield
05089b9a9a Merge pull request #2219 from Ryooooooga/stash-icon 2022-10-14 08:41:39 -07:00
Ryooooooga
d90fedfbf8 feat: add stash icon 2022-10-14 21:58:58 +09:00
README-bot
6af0afdb1c Updated README.md 2022-10-14 00:46:34 +00:00
Jesse Duffield
6cb5e628b3 Merge pull request #2218 from Ryooooooga/feature/empty-stash-message 2022-10-13 17:46:13 -07:00
Ryooooooga
a4239c7a37 fix: fix stash with empty message 2022-10-13 22:23:56 +09:00
Jesse Duffield
fc0b14edef Update README.md 2022-10-11 08:40:35 -07:00
Jesse Duffield
6b9f43c73b Merge pull request #2213 from jesseduffield/go-mod-vendor 2022-10-11 08:38:46 -07:00
README-bot
1358662a21 Updated README.md 2022-10-11 15:17:01 +00:00
Jesse Duffield
ffe5e16688 add profiling guide to contributor docs 2022-10-11 08:16:34 -07:00
Jesse Duffield
575afa1377 update vendor directory 2022-10-11 08:12:56 -07:00
README-bot
d22f6d73ff Updated README.md 2022-10-09 16:00:39 +00:00
Jesse Duffield
867975ad31 Merge pull request #2210 from jesseduffield/move-box-layout-into-lazycore 2022-10-09 09:00:24 -07:00
Jesse Duffield
24a07ae85a update docs for lazycore 2022-10-09 08:49:49 -07:00
Jesse Duffield
c370a5e728 add bump lazycore script 2022-10-09 08:47:38 -07:00
Jesse Duffield
dba0edb998 use boxlayout from lazycore 2022-10-09 08:31:14 -07:00
Andrew Hynes
69040f094d clean: delete old integration artifacts 2022-10-06 23:03:46 -02:30
Andrew Hynes
8c46a0110d Merge branch 'master' into stash-untracked-changes 2022-10-06 22:59:06 -02:30
Andrew Hynes
a30d924afe test: add test for stash including untracked files 2022-10-06 22:53:13 -02:30
Andrew Hynes
f4c188fa5b fix(test): add stash name 2022-10-06 22:42:49 -02:30
README-bot
7b4b42abd6 Updated README.md 2022-10-06 18:26:36 +00:00
Jesse Duffield
2f76ef58f1 Merge pull request #2204 from Ryooooooga/move-by-word 2022-10-06 11:26:10 -07:00
Ryooooooga
e436922eb6 feat(editors.go): move by words 2022-10-05 22:29:55 +09:00
Jesse Duffield
056cc14821 Merge pull request #2199 from TomBaxter/master 2022-10-03 20:15:50 -07:00
TomBaxter
c686c7f9ed Replace regex for retrieving latest version 2022-10-03 13:58:04 -04:00
Jesse Duffield
b27a2ce39a Merge pull request #2197 from jesseduffield/fix-extra-spaces-in-tests 2022-10-03 09:41:12 -07:00
Jesse Duffield
e3f21f0588 strip NUL bytes instead of replacing with space 2022-10-03 09:29:41 -07:00
Jesse Duffield
3375cc1b3d Merge pull request #2196 from jesseduffield/no-glitchy-render-to-main 2022-10-02 21:05:54 -07:00
Jesse Duffield
ed98b60078 use thread safe map 2022-10-02 20:57:44 -07:00
Jesse Duffield
e76fa5a6cb fix glitchy render of stale data when flicking through files and directories 2022-10-02 20:41:24 -07:00
README-bot
a77aa4d75a Updated README.md 2022-10-03 03:37:49 +00:00
Jesse Duffield
68a2a4c6b5 Merge pull request #2195 from jesseduffield/bumo-gocui 2022-10-02 20:37:31 -07:00
Jesse Duffield
8858f03606 adjust test temporarily to unblock master 2022-10-02 20:34:14 -07:00
Jesse Duffield
5670c0a301 bump gocui 2022-10-02 18:43:25 -07:00
Jesse Duffield
c953871ec7 use lowercase 'quote' for consistency with existing custom command template functions 2022-10-02 18:43:25 -07:00
README-bot
b0c19b291f Updated README.md 2022-10-02 19:42:47 +00:00
Jesse Duffield
8a022ddf0e Merge pull request #2193 from Ryooooooga/feature/template-funcs 2022-10-02 12:42:28 -07:00
Ryooooooga
e16f1ba84f test: add integration test for Quote 2022-10-01 20:55:49 +09:00
Ryooooooga
19df238b77 feat: allow OSCommand.Quote to be invoked within a custom command 2022-09-30 21:16:45 +09:00
Jesse Duffield
092363a986 Merge pull request #2164 from mark2185/fix-rebasing-over-merge-commits 2022-09-24 23:59:13 -07:00
Jesse Duffield
23d39c79b2 update test 2022-09-24 23:37:17 +02:00
Luka Markušić
0141bbde0e Add test for amending a merge commit 2022-09-24 23:37:17 +02:00
Luka Markušić
4c7d363959 Add CheckoutBranch and Merge helpers for integration tests 2022-09-24 23:37:17 +02:00
Luka Markušić
41f86f6535 Rebase merges by default 2022-09-24 23:37:17 +02:00
Jesse Duffield
90feb4bae6 Merge pull request #1636 from kawaemon/partially-fix-1629 2022-09-24 10:15:23 -07:00
Jesse Duffield
1d40bd1707 Merge pull request #2104 from LiamKearn/feat-emacs-char-nav 2022-09-24 10:08:13 -07:00
kawaemon
17df42e517 fix: scan to buffer to empty character input in stdin 2022-09-23 23:42:45 -07:00
Jesse Duffield
fd66499c8f Merge pull request #2167 from xiaoliwang/remove_deprecated 2022-09-23 23:01:40 -07:00
README-bot
e001183768 Updated README.md 2022-09-24 02:27:14 +00:00
Jesse Duffield
268d8f99b8 Merge pull request #2183 from Ryooooooga/emacs-keybindings 2022-09-23 19:26:52 -07:00
Ryooooooga
212e19f598 feat: add support for emacs keybindings 2022-09-24 00:00:30 +09:00
Ryooooooga
1c82924307 build: $ ./scripts/bump_gocui.sh 2022-09-23 20:01:44 +09:00
Jesse Duffield
c8066c54b5 Merge pull request #2081 from Ryooooooga/feature/fix-loading-files 2022-09-19 08:17:40 -07:00
Ryooooooga
438038a4f1 fix(loaders/file.go): changed to ignore stderr when loading git status 2022-09-19 18:46:32 +09:00
Jesse Duffield
f23547580a Merge pull request #2092 from mark2185/fix-wrong-git-path 2022-09-17 15:12:04 -07:00
TomCao New Macbook Pro
3d79c6a3d3 formatter 2022-09-17 15:10:41 -07:00
jiepeng
bc8050d8ac typo 2022-09-17 15:10:41 -07:00
jiepeng
b8900baf1a remove deprecated calls 2022-09-17 15:10:41 -07:00
Jesse Duffield
dcbebef897 Merge pull request #2109 from Mihai22125/improve_custom_commands_interface 2022-09-17 15:06:43 -07:00
Mihai22125
7e9dffe1b9 Add Key field to CustomCommandPrompt struct
Add Form field to CustomCommandObjects struct

Write user prompts responses to Form field

Ensure that map keys exists

Add form prompts integration test

Remove redundant index
2022-09-17 14:58:44 -07:00
Jesse Duffield
c81333fefe Merge pull request #2169 from 0123takaokeita/feature/mod_config_dir 2022-09-17 11:20:42 -07:00
README-bot
5639af9918 Updated README.md 2022-09-17 18:10:08 +00:00
Jesse Duffield
ee348751a0 Merge pull request #2137 from jesseduffield/more-integration-tests 2022-09-17 11:09:49 -07:00
Jesse Duffield
a92f0f7c89 increase recording leeway 2022-09-17 10:50:04 -07:00
Jesse Duffield
6dca3e1766 allow two attempts on CI 2022-09-16 22:31:46 -07:00
Jesse Duffield
850a82784a earlier failure 2022-09-16 22:22:20 -07:00
Jesse Duffield
74acb3e86a add integration tests for cherry picking 2022-09-16 22:15:16 -07:00
Jesse Duffield
9351af3829 yet another retry to reduce flakiness 2022-09-16 08:55:16 -07:00
Jesse Duffield
7af7af27c6 various changes to improve integration tests 2022-09-16 08:42:39 -07:00
Andrew Hynes
db9373662a test: add test for basic stash 2022-09-15 23:11:27 -02:30
Andrew Hynes
e189546acb refactor: move checks for clean working tree 2022-09-15 21:48:49 -02:30
Andrew Hynes
c7733aa5e5 refactor: rename method to StashIncludeUntrackedChanges 2022-09-15 21:48:49 -02:30
Andrew Hynes
a0fd47348b test: add stash message 2022-09-15 21:48:49 -02:30
Andrew Hynes
6feb301c2a fix: use message in git stash command 2022-09-15 21:48:49 -02:30
Andrew Hynes
e66b162726 refactor: remove redundant if statement 2022-09-15 21:48:49 -02:30
Andrew Hynes
4f8816ebf2 refactor: use extended flag name 2022-09-15 21:48:49 -02:30
Andrew Hynes
50cf7ac5bc refactor: change command order 2022-09-15 21:48:49 -02:30
Andrew Hynes
088445b7be test: add integration test 2022-09-15 21:48:49 -02:30
Andrew Hynes
c7fd218308 fix: add condition to if statement 2022-09-15 21:48:49 -02:30
Andrew Hynes
7ddb80a13e feat: add stash option to include untracked changes 2022-09-15 21:48:48 -02:30
Takao
fecf2ab810 fix: how to change the config dir for MacOS 2022-09-15 00:15:50 +09:00
Luka Markušić
3232f46a8b Validate --path argument when starting lazygit 2022-09-12 17:18:42 +02:00
Jesse Duffield
7b757d1cfe add branch rebase integration test 2022-09-09 20:55:47 -07:00
Jesse Duffield
843488bff4 add branch delete integration test 2022-09-09 20:55:47 -07:00
Jesse Duffield
a9d4ff2aee cleaning up imports 2022-09-09 20:55:47 -07:00
Jesse Duffield
5d5471c017 remove already migrated test 2022-09-09 20:55:47 -07:00
Jesse Duffield
8cdfc6758f add another bisect integration test 2022-09-09 20:55:47 -07:00
Jesse Duffield
010f430d1f add bisect integration test 2022-09-09 20:55:47 -07:00
Jesse Duffield
79620fc6cf don't quit integration test tui upon error 2022-09-09 20:55:47 -07:00
Jesse Duffield
47f84b6aea better assertions 2022-09-09 20:55:47 -07:00
README-bot
de6d278e57 Updated README.md 2022-09-07 22:55:18 +00:00
Jesse Duffield
46b08e7d70 Merge pull request #2146 from Ryooooooga/fix-initial-origin-editor 2022-09-07 15:54:57 -07:00
Ryooooooga
eb9fbb0a33 fix(confirmation_panel.go): fix initial origin of editor box 2022-09-01 19:44:17 +09:00
Jesse Duffield
448ff80d7d Merge pull request #2123 from Ryooooooga/feature/edit-initial-scroll 2022-08-31 22:43:06 -07:00
Jesse Duffield
74f9b8a3b4 Merge pull request #2143 from Abirdcfly/master 2022-08-31 22:42:32 -07:00
README-bot
25c4aa532e Updated README.md 2022-09-01 05:42:26 +00:00
Jesse Duffield
e7aacafc2e Merge pull request #2132 from jtraub/fix-open-gitlab-commit 2022-08-31 22:42:05 -07:00
Abirdcfly
d78d694959 chore: remove duplicate word in comments
Signed-off-by: Abirdcfly <fp544037857@gmail.com>
2022-08-30 13:23:37 +08:00
Konstantin Mikhailov
a67a08eeac Fix open commit in browser for some Gitlab repos 2022-08-24 11:23:02 +10:00
Jesse Duffield
f6d6b5dec0 Merge pull request #2126 from yofreee/master 2022-08-19 08:30:30 +10:00
Jesse Duffield
ce98279896 Merge pull request #2124 from Ryooooooga/feature/improve-integration-test-portability 2022-08-19 08:25:50 +10:00
README-bot
e0e4138396 Updated README.md 2022-08-18 22:25:16 +00:00
Jesse Duffield
014daf7bc0 Merge pull request #2067 from nullishamy/feat/detect-bare-repo 2022-08-19 08:24:53 +10:00
Yofre Ormaza
67d6b69115 docs(readme): Added lazygit install method on ubuntu 2022-08-18 14:43:38 -05:00
nullishamy
956372cf8a Run gofumpt 2022-08-18 18:26:34 +01:00
nullishamy
290e865584 Merge branch 'master' into feat/detect-bare-repo 2022-08-18 17:46:07 +01:00
Ryooooooga
6248091e9c test: improve integration test portability 2022-08-18 23:48:53 +09:00
Ryooooooga
3ada4dde12 fix: fix initial scroll position of edit box 2022-08-18 23:39:15 +09:00
Jesse Duffield
ef82f39431 better PR template 2022-08-16 08:08:39 +10:00
nullishamy
21a4522a51 Merge branch 'master' into feat/detect-bare-repo 2022-08-15 14:00:34 +01:00
nullishamy
154bd975a6 Apply refactoring suggestions 2022-08-15 13:59:34 +01:00
Jesse Duffield
fbe54512a8 formatting 2022-08-15 20:10:25 +10:00
Jesse Duffield
7fd0e55b7f add PR template 2022-08-15 20:09:17 +10:00
Jesse Duffield
13b04e9e8c Merge pull request #2116 from jesseduffield/test-go-mod-vendor 2022-08-15 20:05:06 +10:00
Jesse Duffield
8a1937787d fix gocui mismatch 2022-08-15 20:01:43 +10:00
Jesse Duffield
a94c703afb fail on vendor directory mismatch
try this

or this

more
2022-08-15 19:55:27 +10:00
Jesse Duffield
6d7a7afbbc update test readme 2022-08-15 19:24:36 +10:00
Jesse Duffield
6abcfd5cba missed a spot 2022-08-14 21:39:07 +10:00
Jesse Duffield
a95d3e26b3 Merge pull request #2114 from jesseduffield/more-test-examples 2022-08-14 21:37:09 +10:00
Jesse Duffield
af45692e24 fix CI 2022-08-14 21:34:37 +10:00
Jesse Duffield
fed2aaf37f migrate menuFromCommand integration test 2022-08-14 21:30:37 +10:00
Jesse Duffield
b2ae651686 add slow flag to integration tests 2022-08-14 20:49:20 +10:00
Jesse Duffield
e875d6b448 ensuring you can't accidentally forget to add a test to the tests list 2022-08-14 20:49:20 +10:00
Jesse Duffield
53979f7cec a more complex custom command test 2022-08-14 20:49:20 +10:00
Jesse Duffield
9c0d860980 basic custom command test 2022-08-14 20:49:20 +10:00
Jesse Duffield
4aea005f26 Merge pull request #2098 from Ryooooooga/feature/not-a-repository-quit 2022-08-14 17:37:07 +10:00
Jesse Duffield
39e9266089 Merge pull request #2110 from mark2185/fix-ignore-or-exclude-file-menu 2022-08-14 17:35:35 +10:00
Jesse Duffield
f999c90a7b Merge pull request #2113 from jesseduffield/better-test-structure 2022-08-14 17:31:53 +10:00
Jesse Duffield
502723421b build integration binaries on CI to ensure they compile 2022-08-14 17:20:52 +10:00
Jesse Duffield
5173d7f5e1 better CLI interface 2022-08-14 17:20:52 +10:00
Jesse Duffield
349a7d2453 even better structure 2022-08-14 11:24:07 +10:00
Jesse Duffield
f3837000dd bump gocui 2022-08-13 20:12:04 +10:00
Jesse Duffield
d1b093e703 no need for this 2022-08-13 19:30:51 +10:00
README-bot
c66a2c3465 Updated README.md 2022-08-13 04:18:48 +00:00
Jesse Duffield
ba7d639940 Merge pull request #2094 from jesseduffield/better-integration-tests 2022-08-13 14:18:27 +10:00
Jesse Duffield
cad84c9e74 ensure we don't try to run another test when lazygit is invoked as a daemon 2022-08-13 14:12:35 +10:00
Jesse Duffield
5e475355bf add tests for my tests 2022-08-13 13:56:50 +10:00
Jesse Duffield
304d74370e refactor to ensure code doesn't depend on integration code 2022-08-13 13:56:50 +10:00
Jesse Duffield
2bdefe2049 add assertion to prevent flakiness 2022-08-13 13:56:50 +10:00
Jesse Duffield
faed509bfd fix CI 2022-08-13 13:56:50 +10:00
Jesse Duffield
b8d9443999 rename helpers to components 2022-08-13 13:55:17 +10:00
Jesse Duffield
de84b6c4b9 remove buggy-ass action 2022-08-13 13:55:09 +10:00
Jesse Duffield
610eddfe05 fix CI 2022-08-13 13:55:09 +10:00
Jesse Duffield
1ef6f4c0e1 renaming 2022-08-13 13:55:08 +10:00
Jesse Duffield
ae798157d2 update comments 2022-08-13 13:55:08 +10:00
Jesse Duffield
a45b22e12f re-name Input and improve documentation 2022-08-13 13:55:08 +10:00
Jesse Duffield
ba96baee32 move code from main into app package to allow test to be injected 2022-08-13 13:55:08 +10:00
Jesse Duffield
d890238c7b move input and assert into integration tests package 2022-08-13 13:52:13 +10:00
Jesse Duffield
46ae55f91e introduce gui adapter 2022-08-13 13:51:56 +10:00
Luka Markušić
0ff5b74d80 IgnoreOrExclude should be a menu 2022-08-11 14:23:02 +02:00
Jesse Duffield
225c563c63 another integration test 2022-08-11 21:24:16 +10:00
Jesse Duffield
77881a9c7d add new integration test pattern 2022-08-11 21:24:15 +10:00
Jesse Duffield
c7f9d5801b Merge pull request #2108 from eetann/fix-document-link 2022-08-10 16:04:39 +10:00
eetann
77622e5638 fix: document link 2022-08-10 13:17:07 +09:00
lkearn
6422b399e7 Feat: Add emacs character navigation, because I'm weird like that :) 2022-08-09 17:58:43 +10:00
nullishamy
3016469708 Merge branch 'master' into feat/detect-bare-repo 2022-08-08 13:51:14 +01:00
Ryooooooga
8b371ada73 feat(config): add notARepository: quit 2022-08-08 18:11:58 +09:00
Jesse Duffield
fc49068a6f better bug report template 2022-08-08 18:53:22 +10:00
Jesse Duffield
95297f72a3 even better 2022-08-08 18:50:48 +10:00
Jesse Duffield
121872ac27 new feature request template 2022-08-08 18:46:11 +10:00
nullishamy
d072b0c75e Merge branch 'master' into feat/detect-bare-repo 2022-08-07 16:20:59 +01:00
Jesse Duffield
70a46028e1 Merge pull request #2093 from jesseduffield/fix-hidden-suggestions 2022-08-07 19:30:43 +10:00
Jesse Duffield
e4e04cfa8f fix hidden suggestions 2022-08-07 19:20:02 +10:00
Jesse Duffield
2e7b935bfb Merge pull request #2089 from jesseduffield/render-to-main-refactor 2022-08-07 13:03:28 +10:00
Jesse Duffield
71a9389ca4 Merge pull request #2087 from xxdavid/bitbucket_custom_username 2022-08-07 13:01:04 +10:00
Jesse Duffield
b77f3160f1 Merge pull request #2004 from mark2185/fix-add-to-gitignore-newline 2022-08-07 12:49:39 +10:00
Jesse Duffield
92bde6dda4 Merge pull request #2086 from luzpaz/README 2022-08-07 12:48:04 +10:00
Jesse Duffield
d73a236d7c allow rendering to main panels from anywhere 2022-08-07 12:05:51 +10:00
Jesse Duffield
fcf20f3b93 Merge pull request #2088 from jesseduffield/merge-conflict-refactor 2022-08-07 11:26:46 +10:00
Jesse Duffield
9578329cd9 update cheatsheet 2022-08-07 11:20:25 +10:00
Jesse Duffield
4c5fa83566 ensure we remove temporary directory 2022-08-07 11:16:14 +10:00
Jesse Duffield
755ae0ef84 add deadlock mutex package
write to deadlock stderr after closing gocui

more deadlock checking
2022-08-07 11:16:14 +10:00
Jesse Duffield
7410acd1aa move merge conflicts code into controller 2022-08-07 11:16:03 +10:00
David Pavlík
4db22aec0e support custom SSH usernames on BitBucket, fixes #1890 2022-08-06 22:54:57 +02:00
nullishamy
a91d977f89 Merge branch 'master' into feat/detect-bare-repo 2022-08-06 14:08:59 +01:00
Jesse Duffield
445a625b56 rename merging context to mergeConflicts 2022-08-06 18:05:00 +10:00
README-bot
cd2c01d1cf Updated README.md 2022-08-06 03:56:13 +00:00
Jesse Duffield
17316ca061 Merge pull request #2023 from jesseduffield/one-context-per-view 2022-08-06 13:55:56 +10:00
Jesse Duffield
54fb73080a use ptmx map so that we can have multiple ptmx's stored for resizing 2022-08-06 13:49:11 +10:00
Jesse Duffield
524bf83a4a refactor to only have one context per view 2022-08-06 13:49:11 +10:00
luz paz
7fcd1d2a6e fix typo in README.md
'Maintenance' is spelled incorrectly.
2022-08-05 13:25:59 -04:00
Luka Markušić
fdf6a9cc2b Test appending to empty file 2022-08-04 13:52:04 +02:00
Luka Markušić
57f86b8f90 Rerun integration test 2022-08-03 14:08:52 +02:00
Luka Markušić
6160d85d4f Use tmpdir for tests 2022-08-03 14:06:12 +02:00
Luka Markušić
64224e7caa Update pkg/commands/oscommands/os.go
Co-authored-by: Ryoga <eial5q265e5+github@gmail.com>
2022-08-03 13:57:26 +02:00
Luka Markušić
d56bb0b8ef Fix the integration test 2022-08-03 07:55:51 +02:00
Luka Markušić
d238d8952b Add AppendLineToFile tests 2022-08-03 07:55:51 +02:00
Luka Markušić
86d5654d20 Preserve trailing newline setting when adding to gitignore 2022-08-03 07:55:51 +02:00
Jesse Duffield
6dfef08efc Merge pull request #2079 from jesseduffield/fix-menu-popup-focus-issue 2022-08-02 09:21:33 +10:00
Jesse Duffield
c1c6e2fac2 make exception for searching from menu 2022-08-02 09:16:01 +10:00
nullishamy
0b4f9f8c76 Refactor branching logic 2022-08-01 21:58:00 +01:00
nullishamy
a658cd4076 Factor out opening of recent repos 2022-08-01 20:05:35 +01:00
nullishamy
69718fb557 Factor out redundant statement 2022-08-01 19:39:39 +01:00
nullishamy
b9b2f58bc8 Format, bug fixes 2022-08-01 17:41:20 +01:00
nullishamy
bdb0b9ae6e Merge branch 'master' into feat/detect-bare-repo 2022-08-01 17:05:33 +01:00
nullishamy
2866827ca8 Apply suggestions from code review 2022-08-01 17:05:16 +01:00
Jesse Duffield
932b0b593e add integration test to ensure we don't run into issues with popup focus 2022-08-01 22:17:06 +10:00
Jesse Duffield
3ee2ad511e prevent crash when opening recent repos panel if no recent repos are present 2022-08-01 22:10:08 +10:00
Jesse Duffield
debc58b6c5 fix popup focus issue 2022-08-01 22:09:48 +10:00
Jesse Duffield
fab2e14b55 fix issue caused by opening a menu over a prompt 2022-08-01 21:38:57 +10:00
Jesse Duffield
81f80ce968 Merge pull request #2077 from jesseduffield/file-node-fix
handle nil properly with file nodes
2022-08-01 20:36:26 +10:00
Jesse Duffield
95426c5e46 handle nil properly with file nodes 2022-08-01 20:32:01 +10:00
Jesse Duffield
4ffc9a5395 Merge pull request #2076 from jesseduffield/build-info 2022-08-01 20:22:27 +10:00
Jesse Duffield
86ac309e08 add build info when building from source 2022-08-01 20:16:50 +10:00
nullishamy
9987e65c35 Merge branch 'master' into feat/detect-bare-repo 2022-08-01 03:14:49 +01:00
Jesse Duffield
69f4292fe3 Merge pull request #2075 from jesseduffield/generic-trees 2022-07-31 19:47:06 +10:00
Jesse Duffield
682be18507 refactor to use generics for file nodes
use less generic names
2022-07-31 19:43:14 +10:00
Jesse Duffield
2ca2acaca5 Merge pull request #2072 from jesseduffield/optimistic-file-rendering 2022-07-31 19:31:23 +10:00
Jesse Duffield
5f4c29d7b5 Merge pull request #2005 from mark2185/feature/recent-repos-path
Show active branch for recent repo
2022-07-31 19:30:30 +10:00
Luka Markušić
44de380c2b Add i18n for unknown branch 2022-07-31 08:57:57 +02:00
Luka Markušić
37bdbd9a21 Display short SHA when in detached HEAD state 2022-07-31 08:51:27 +02:00
Luka Markušić
767ef31661 Use filepath.Join instead of manual concat 2022-07-31 08:43:31 +02:00
README-bot
bb64fe4309 Updated README.md 2022-07-31 06:20:16 +00:00
Jesse Duffield
c81c046615 Merge pull request #2059 from sportshead/master 2022-07-31 16:19:59 +10:00
Jesse Duffield
a905165046 remove double negatives 2022-07-31 16:11:39 +10:00
Jesse Duffield
7077ea428f add optimistic rendering for staging and unstaging files 2022-07-31 14:34:57 +10:00
sportshead
f2880ecb46 Add empty output message and refreshing to showOutput 2022-07-31 12:04:40 +08:00
Luka Markušić
966733240c Refactor a bit, enable worktrees 2022-07-30 19:02:19 +02:00
Luka Markušić
25ddac0d8f Gotta go fast 2022-07-30 17:42:15 +02:00
Luka Markušić
7c09ce3871 Parallelize fetching current branch 2022-07-30 17:05:17 +02:00
Luka Markušić
e6e4513f45 Show active branch for recent repo
Split recent repo menu into three columns
2022-07-30 17:05:17 +02:00
Luka Markušić
9c6239df3d Worktrees should not be filtered out
But non-git directories should
2022-07-30 17:03:30 +02:00
Jesse Duffield
c26650258d Merge pull request #2064 from jesseduffield/fix-update-gocui
Update gocui
2022-07-30 21:08:47 +10:00
Jesse Duffield
c943cdd8d2 Merge pull request #2071 from jesseduffield/ellipsis-with-menu
Add better support for OpensMenu option when creating a menu
2022-07-30 20:40:54 +10:00
Jesse Duffield
ab5a8091f5 add better support for OpensMenu option when creating a menu 2022-07-30 20:27:51 +10:00
Jesse Duffield
95678b1b43 Merge pull request #2065 from jesseduffield/fix-rewording-merge-commit-with-editor
Fix rewording a merge commit with editor
2022-07-30 19:51:59 +10:00
Jesse Duffield
f947214ce0 Merge pull request #2070 from mark2185/feature/document-SelectedPath
Add SelectedPath to the list of placeholder values
2022-07-30 19:50:01 +10:00
Jesse Duffield
8c65f63e76 Merge pull request #2062 from jesseduffield/mark2185-patch-1
Update bug_report.md
2022-07-30 19:48:58 +10:00
Luka Markušić
085904d209 Add link to the installation instructions 2022-07-30 08:46:53 +02:00
Luka Markušić
cc377b5c49 Fix rewording a merge commit with editor 2022-07-30 08:43:42 +02:00
README-bot
1f97ddd9b1 Updated README.md 2022-07-30 06:38:57 +00:00
Jesse Duffield
5cef0f2921 Merge pull request #2069 from mark2185/fix-github-linter
Fix github linter errors
2022-07-30 16:38:41 +10:00
Luka Markušić
61e14c46b2 Add SelectedPath to the list of placeholder values 2022-07-30 08:25:40 +02:00
Luka Markušić
1f482e585e Fix github linter errors 2022-07-30 08:10:29 +02:00
nullishamy
41b54d742f Check for bare repositories 2022-07-29 23:55:34 +01:00
Luka Markušić
83dfc3b28b Update gocui 2022-07-29 06:59:52 +02:00
Luka Markušić
017d44ff17 Update bug_report.md
Add the `please try updating` tidbit to the bug report because that's one of the most common answers (and it cuts down debugging time)
2022-07-28 21:17:15 +02:00
sportshead
168fbe0a6c Add showOutput option to docs 2022-07-28 18:57:16 +08:00
sportshead
e1f41b653c Add showOutput option to custom commands (#1163) 2022-07-28 18:40:30 +08:00
1950 changed files with 67426 additions and 18655 deletions

13
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
# adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/Dockerfile
# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster
ARG VARIANT=1-bullseye
FROM golang:${VARIANT}
RUN go install mvdan.cc/gofumpt@latest
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.0
RUN golangci-lint --version
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>

View File

@@ -0,0 +1,69 @@
// adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/devcontainer.json
{
"build": {
"dockerfile": "./Dockerfile",
"context": "."
},
"features": {
"ghcr.io/devcontainers/features/common-utils:1": {
"installZsh": "true",
"username": "vscode",
"uid": "1000",
"gid": "1000",
"upgradePackages": "true"
},
"ghcr.io/devcontainers/features/go:1": {
"version": "none"
},
"ghcr.io/devcontainers/features/git:1": {
"version": "latest",
"ppa": "false"
}
},
"overrideFeatureInstallOrder": [
"ghcr.io/devcontainers/features/common-utils"
],
// not sure if we actually need these
"runArgs": [
"--cap-add=SYS_PTRACE",
"--security-opt",
"seccomp=unconfined"
],
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"go.toolsManagement.checkForUpdates": "local",
"go.useLanguageServer": true,
"go.gopath": "/go",
"[go]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"go.lintTool": "golangci-lint",
"gopls": {
"formatting.gofumpt": true,
"usePlaceholders": false // add parameter placeholders when completing a function
},
"files.eol": "\n"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"golang.Go"
]
}
},
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "go version",
// See https://www.kenmuse.com/blog/avoiding-dubious-ownership-in-dev-containers/ for the safe.directory part
// The defaultBranch part is required for our deprecated integration tests.
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && git config --global init.defaultBranch master",
// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

View File

@@ -4,7 +4,6 @@ about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -23,10 +23,15 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows]
- Lazygit Version [e.g. v0.1.45]
- The last commit id if you built project from sources (run : ```git rev-parse HEAD```)
**Version info:**
_Run `lazygit --version` and paste the result here_
_Run `git --version` and paste the result here_
**Additional context**
Add any other context about the problem here.
**Note:** please try updating to the latest version or [manually building](https://github.com/jesseduffield/lazygit/#manual) the latest `master` to see if the issue still occurs.
<!--
If you want to try and debug this issue yourself, you can run `lazygit --debug` in one terminal panel and `lazygit --logs` in another to view the logs.
-->

View File

@@ -4,7 +4,6 @@ about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
@@ -18,3 +17,13 @@ A clear and concise description of any alternative solutions or features you've
**Additional context**
Add any other context or screenshots about the feature request here.
<!--
You may be able to add your desired feature with a custom command. Check out the examples here: https://github.com/jesseduffield/lazygit/wiki/Custom-Commands-Compendium
If a custom command does what you want but you still want to see the feature built-in to lazygit, feel free to paste the custom command into the issue to help us better understand the functionality you want.
We also encourage you to put up a PR yourself! Who cares if you've never written Go before, neither did any of the existing contributors before their first lazygit PR! Check out the PR tutorial here: https://www.youtube.com/watch?v=kNavnhzZHtk&ab_channel=JesseDuffield
Also check out the contributing guide here: https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md
-->

10
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,10 @@
- **PR Description**
- **Please check if the PR fulfills these requirements**
* [ ] Cheatsheets are up-to-date (run `go run scripts/cheatsheet/main.go generate`)
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] Docs (specifically `docs/Config.md`) have been updated if necessary
* [ ] You've read through your own file changes for silly mistakes etc

View File

@@ -1,28 +0,0 @@
name: automerge
on:
pull_request:
types:
- labeled
- unlabeled
- synchronize
- opened
- edited
- ready_for_review
- reopened
- unlocked
pull_request_review:
types:
- submitted
check_suite:
types:
- completed
status: {}
jobs:
automerge:
runs-on: ubuntu-latest
steps:
- name: automerge
uses: "pascalgn/automerge-action@135f0bdb927d9807b5446f7ca9ecc2c51de03c4a"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_METHOD: rebase

View File

@@ -46,14 +46,39 @@ jobs:
# we're passing -short so that we skip the integration tests, which will be run in parallel below
run: |
go test ./... -short
integration-tests:
integration-tests-old:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
parallelism: [5]
index: [0,1,2,3,4]
name: "Integration Tests (${{ matrix.index }}/${{ matrix.parallelism }})"
name: "Integration Tests (Old pattern) (${{ matrix.index }}/${{ matrix.parallelism }})"
env:
GOFLAGS: -mod=vendor
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v1
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-
- name: Test code
# for file.allow thing see https://vielmetti.typepad.com/logbook/2022/10/git-security-fixes-lead-to-fatal-transport-file-not-allowed-error-in-ci-systems-cve-2022-39253.html
run: |
git config --global protocol.file.allow always && PARALLEL_TOTAL=${{ matrix.parallelism }} PARALLEL_INDEX=${{ matrix.index }} go test pkg/integration/deprecated/*.go
integration-tests:
runs-on: ubuntu-latest
name: "Integration Tests"
env:
GOFLAGS: -mod=vendor
steps:
@@ -74,7 +99,7 @@ jobs:
${{runner.os}}-go-
- name: Test code
run: |
PARALLEL_TOTAL=${{ matrix.parallelism }} PARALLEL_INDEX=${{ matrix.index }} go test pkg/gui/gui_test.go
go test pkg/integration/clients/*.go
build:
runs-on: ubuntu-latest
env:
@@ -105,7 +130,13 @@ jobs:
- name: Build darwin binary
run: |
GOOS=darwin go build
check-cheatsheet:
- name: Build integration test binary
run: |
GOOS=linux go build cmd/integration_test/main.go
- name: Build integration test injector
run: |
GOOS=linux go build pkg/integration/clients/injector/main.go
check-codebase:
runs-on: ubuntu-latest
env:
GOFLAGS: -mod=vendor
@@ -129,6 +160,10 @@ jobs:
- name: Check Cheatsheet
run: |
go run scripts/cheatsheet/main.go check
- 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)
lint:
runs-on: ubuntu-latest
env:
@@ -153,12 +188,6 @@ jobs:
uses: golangci/golangci-lint-action@v3.1.0
with:
version: latest
- name: Format code
run: |
if [ $(find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;|wc -l) -gt 0 ]; then
find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;
exit 1
fi
- name: errors
run: golangci-lint run
if: ${{ failure() }}

89
.github/workflows/uffizzi-build.yml vendored Normal file
View File

@@ -0,0 +1,89 @@
name: Build PR Image
on:
pull_request:
types: [opened, synchronize, reopened, closed]
jobs:
build-application:
name: Build and Push `lazygit`
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }}
outputs:
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Generate UUID image name
id: uuid
run: echo "UUID_APP_TAG=$(uuidgen)" >> $GITHUB_ENV
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: registry.uffizzi.com/${{ env.UUID_APP_TAG }}
tags: type=raw,value=60d
- name: Build and Push Image to registry.uffizzi.com ephemeral registry
uses: docker/build-push-action@v2
with:
push: true
context: ./
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
file: ./uffizzi/DockerfileTtyd
cache-from: type=gha
cache-to: type=gha,mode=max
render-compose-file:
name: Render Docker Compose File
# Pass output of this workflow to another triggered by `workflow_run` event.
runs-on: ubuntu-latest
needs:
- build-application
outputs:
compose-file-cache-key: ${{ steps.hash.outputs.hash }}
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Render Compose File
run: |
APP_IMAGE=$(echo ${{ needs.build-application.outputs.tags }})
export APP_IMAGE
# Render simple template from environment variables.
envsubst < ./uffizzi/docker-compose.uffizzi.yml > docker-compose.rendered.yml
cat docker-compose.rendered.yml
- name: Upload Rendered Compose File as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
path: docker-compose.rendered.yml
retention-days: 2
- name: Serialize PR Event to File
run: |
cat << EOF > event.json
${{ toJSON(github.event) }}
EOF
- name: Upload PR Event as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
path: event.json
retention-days: 2
delete-preview:
name: Call for Preview Deletion
runs-on: ubuntu-latest
if: ${{ github.event.action == 'closed' }}
steps:
# If this PR is closing, we will not render a compose file nor pass it to the next workflow.
- name: Serialize PR Event to File
run: echo '${{ toJSON(github.event) }}' > event.json
- name: Upload PR Event as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
path: event.json
retention-days: 2

84
.github/workflows/uffizzi-preview.yml vendored Normal file
View File

@@ -0,0 +1,84 @@
name: Deploy Uffizzi Preview
on:
workflow_run:
workflows:
- "Build PR Image"
types:
- completed
jobs:
cache-compose-file:
name: Cache Compose File
runs-on: ubuntu-latest
outputs:
compose-file-cache-key: ${{ env.COMPOSE_FILE_HASH }}
pr-number: ${{ env.PR_NUMBER }}
steps:
- name: 'Download artifacts'
# Fetch output (zip archive) from the workflow run that triggered this workflow.
uses: actions/github-script@v6
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "preview-spec"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data));
- name: 'Unzip artifact'
run: unzip preview-spec.zip
- name: Read Event into ENV
run: |
echo 'EVENT_JSON<<EOF' >> $GITHUB_ENV
cat event.json >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Hash Rendered Compose File
id: hash
# If the previous workflow was triggered by a PR close event, we will not have a compose file artifact.
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
run: echo "COMPOSE_FILE_HASH=$(md5sum docker-compose.rendered.yml | awk '{ print $1 }')" >> $GITHUB_ENV
- name: Cache Rendered Compose File
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
uses: actions/cache@v3
with:
path: docker-compose.rendered.yml
key: ${{ env.COMPOSE_FILE_HASH }}
- name: Read PR Number From Event Object
id: pr
run: echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV
- name: DEBUG - Print Job Outputs
if: ${{ runner.debug }}
run: |
echo "PR number: ${{ env.PR_NUMBER }}"
echo "Compose file hash: ${{ env.COMPOSE_FILE_HASH }}"
cat event.json
deploy-uffizzi-preview:
name: Use Remote Workflow to Preview on Uffizzi
needs:
- cache-compose-file
uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml@v2.6.1
with:
# If this workflow was triggered by a PR close event, cache-key will be an empty string
# and this reusable workflow will delete the preview deployment.
compose-file-cache-key: ${{ needs.cache-compose-file.outputs.compose-file-cache-key }}
compose-file-cache-path: docker-compose.rendered.yml
server: https://app.uffizzi.com
pr-number: ${{ needs.cache-compose-file.outputs.pr-number }}
permissions:
contents: read
pull-requests: write
id-token: write

7
.gitignore vendored
View File

@@ -27,17 +27,22 @@ lazygit.exe
!.circleci/
!.github/
!.vscode/
!.devcontainer/
# these are for our integration tests
!.git_keep
!.gitmodules_keep
test/git_server/data
# we'll scrap these lines once we've fully moved over to the new integration test approach
test/integration/*/actual/
test/integration/*/used_config/
# these sample hooks waste too much space
test/integration/*/expected/**/hooks/
test/integration/*/expected_remote/**/hooks/
test/integration_new/**
oryxBuildBinary
__debug_bin
__debug_bin

View File

@@ -32,12 +32,8 @@ checksum:
snapshot:
name_template: '{{ .Tag }}-next'
changelog:
use: github-native
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- '^bump'
brews:
-
# Repository to push the tap to.

74
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,74 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Generate cheatsheet",
"type": "process",
"command": "go run scripts/cheatsheet/main.go ",
"problemMatcher": [],
},
{
"label": "Bump gocui",
"type": "shell",
"command": "./scripts/bump_gocui.sh",
"problemMatcher": [],
},
{
"label": "Bump lazycore",
"type": "shell",
"command": "./scripts/bump_lazycore.sh",
"problemMatcher": [],
},
{
"label": "Run current file integration test",
"type": "shell",
"command": "go run cmd/integration_test/main.go cli ${relativeFile}",
"problemMatcher": [],
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"focus": true
}
},
{
"label": "Run current file integration test (slow)",
"type": "shell",
"command": "go run cmd/integration_test/main.go cli --slow ${relativeFile}",
"problemMatcher": [],
"group": {
"kind": "test",
},
"presentation": {
"focus": true
}
},
{
"label": "Run current file integration test (sandbox)",
"type": "shell",
"command": "go run cmd/integration_test/main.go cli --sandbox ${relativeFile}",
"problemMatcher": [],
"group": {
"kind": "test",
},
"presentation": {
"focus": true
}
},
{
"label": "Open deprecated test TUI",
"type": "shell",
"command": "go run pkg/integration/deprecated/cmd/tui/main.go",
"problemMatcher": [],
"group": {
"kind": "test",
},
"presentation": {
"focus": true
}
}
],
}

View File

@@ -23,6 +23,28 @@ welcome your pull requests:
If you've never written Go in your life, then join the club! Lazygit was the maintainer's first Go program, and most contributors have never used Go before. Go is widely considered an easy-to-learn language, so if you're looking for an open source project to gain dev experience, you've come to the right place.
## Running in a VSCode dev container
If you want to spare yourself the hassle of setting up your dev environment yourself (i.e. installing Go, extensions, and extra tools), you can run the Lazygit code in a VSCode dev container like so:
![image](https://user-images.githubusercontent.com/8456633/201500508-0d55f99f-5035-4a6f-a0f8-eaea5c003e5d.png)
This requires that:
* you have docker installed
* you have the dev containers extension installed in VSCode
See [here](https://code.visualstudio.com/docs/devcontainers/containers) for more info about dev containers.
## Running in a Github Codespace
If you want to start contributing to Lazygit with the click of a button, you can open the lazygit codebase in a Codespace. First fork the repo, then click to create a codespace:
![image](https://user-images.githubusercontent.com/8456633/201500566-ffe9105d-6030-4cc7-a525-6570b0b413a2.png)
To run lazygit from within the integrated terminal just go `go run main.go`
This allows you to contribute to Lazygit without needing to install anything on your local machine. The Codespace has all the necessary tools and extensions pre-installed.
## Code of conduct
Please note by participating in this project, you agree to abide by the [code of conduct].
@@ -63,15 +85,21 @@ by setting [`formatting.gofumpt`](https://github.com/golang/tools/blob/master/go
```jsonc
// .vscode/settings.json
{
"gopls": {
"formatting.gofumpt": true
}
"gopls": {
"formatting.gofumpt": true
}
}
```
To run gofumpt from your terminal go:
```
go install mvdan.cc/gofumpt@latest && gofumpt -l -w .
```
## Internationalisation
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `app.Tr.YourNewText`, etc). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
## Debugging
@@ -82,6 +110,7 @@ From most places in the codebase you have access to a logger e.g. `gui.Log.Warn(
If you find that the existing logs are too noisy, you can set the log level with e.g. `LOG_LEVEL=warn go run main.go -debug` and then only use `Warn` logs yourself.
If you need to log from code in the vendor directory (e.g. the `gocui` package), you won't have access to the logger, but you can easily add logging support by adding the following:
```go
func newLogger() *logrus.Entry {
// REPLACE THE BELOW PATH WITH YOUR ACTUAL LOG PATH (YOU'LL SEE THIS PRINTED WHEN YOU RUN `lazygit --logs`
@@ -118,24 +147,50 @@ If you want to trigger a debug session from VSCode, you can use the following sn
"request": "launch",
"mode": "auto",
"program": "main.go",
"args": [
"--debug"
],
"args": ["--debug"],
"console": "externalTerminal" // <-- you need this to actually see the lazygit UI in a window while debugging
}
]
}
```
## Profiling
If you want to investigate what's contributing to CPU usage you can add the following to the top of the `main()` function in `main.go`
```go
import "runtime/pprof"
func main() {
f, err := os.Create("cpu.prof")
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
...
```
Then run lazygit, and afterwards, from your terminal, run:
```sh
go tool pprof --web cpu.prof
```
That should open an application which allows you to view the breakdown of CPU usage.
## Testing
Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go in files that end in `_test.go`, and are written in Go. Lazygit has its own integration test system where you can build a sandbox repo with a shell script, record yourself doing something, and commit the resulting repo snapshot. It's pretty damn cool! To learn more see [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Integration_Tests.md)
Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go in files that end in `_test.go`, and are written in Go. For integration tests, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
## Updating Gocui
Sometimes you will need to make a change in the gocui fork (https://github.com/jesseduffield/gocui). Gocui is the package responsible for rendering windows and handling user input. Here's the typical process to follow:
1. Make the changes in gocui inside the vendor directory so it's easy to test against lazygit
1. Make the changes in gocui inside lazygit's vendor directory so it's easy to test against lazygit
2. Copy the changes over to the actual gocui repo (clone it if you haven't already, and use the `awesome` branch, not `master`)
3. Raise a PR on the gocui repo with your changes
4. After that PR is merged, make a PR in lazygit bumping the gocui version. You can bump the version by running the following at the lazygit repo root:
@@ -146,6 +201,23 @@ Sometimes you will need to make a change in the gocui fork (https://github.com/j
5. Raise a PR in lazygit with those changes
## Updating Lazycore
[Lazycore](https://github.com/jesseduffield/lazycore) is a repo containing shared functionality between lazygit and lazydocker. Sometimes you will need to make a change to that repo and import the changes into lazygit. Similar to updating Gocui, here's what you do:
1. Make the changes in lazycore inside lazygit's vendor directory so it's easy to test against lazygit
2. Copy the changes over to the actual lazycore repo (clone it if you haven't already, and use the `master` branch)
3. Raise a PR on the lazycore repo with your changes
4. After that PR is merged, make a PR in lazygit bumping the lazycore version. You can bump the version by running the following at the lazygit repo root:
```sh
./scripts/bump_lazycore.sh
```
Or if you're using VSCode, there is a bump lazycore task you can find by going `cmd+shift+p` and typing 'Run task'
5. Raise a PR in lazygit with those changes
## Improvements
If you can think of any way to improve these docs let us know.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,63 @@
package main
import (
"fmt"
"log"
"os"
"github.com/jesseduffield/lazygit/pkg/integration/clients"
)
var usage = `
Usage:
See https://github.com/jesseduffield/lazygit/tree/master/pkg/integration/README.md
CLI mode:
> 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
MODE:
* ask (default): if a snapshot test fails, asks if you want to update the snapshot
* check: if a snapshot test fails, exits with an error
* update: if a snapshot test fails, updates the snapshot
* sandbox: uses the test's setup step to run the test in a sandbox where you can do whatever you want
TUI mode:
> go run cmd/integration_test/main.go tui
This will open up a terminal UI where you can run tests
Help:
> go run cmd/integration_test/main.go help
`
func main() {
if len(os.Args) < 2 {
log.Fatal(usage)
}
switch os.Args[1] {
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)
case "tui":
clients.RunTUI()
default:
log.Fatal(usage)
}
}

View File

@@ -12,11 +12,16 @@ For old installations (slightly embarrassing: I didn't realise at the time that
- MacOS: `~/Library/Application Support/jesseduffield/lazygit/config.yml`
- Windows: `%APPDATA%\jesseduffield\lazygit\config.yml`
If you want to change the config directory:
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
## Default
```yaml
gui:
# stuff relating to the UI
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
sidePanelWidth: 0.3333 # number from 0 to 1
@@ -25,7 +30,6 @@ gui:
language: 'auto' # one of 'auto' | 'en' | 'zh' | 'pl' | 'nl' | 'ja' | 'ko'
timeFormat: '02 Jan 06 15:04 MST' # https://pkg.go.dev/time#Time.Format
theme:
lightTheme: false # For terminals with a light background
activeBorderColor:
- green
- bold
@@ -43,6 +47,8 @@ gui:
- blue
unstagedChangesColor:
- red
defaultFgColor:
- default
commitLength:
show: true
mouseEvents: true
@@ -56,19 +62,21 @@ gui:
showIcons: false
commandLogSize: 8
splitDiff: 'auto' # one of 'auto' | 'always'
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
git:
paging:
colorArg: always
useConfig: false
commit:
signOff: false
verbose: default # one of 'default' | 'always' | 'never'
merging:
# only applicable to unix users
manualCommit: false
# extra args passed to `git merge`, e.g. --no-ff
args: ''
log:
# one of date-order, author-date-order, topo-order.
# one of date-order, author-date-order, topo-order or default.
# topo-order makes it easier to read the git log graph, but commits may not
# appear chronologically. See https://git-scm.com/docs/git-log#_commit_ordering
order: 'topo-order'
@@ -96,18 +104,19 @@ refresher:
update:
method: prompt # can be: prompt | background | never
days: 14 # how often an update is checked for
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
confirmOnQuit: false
# determines whether hitting 'esc' will quit the application when there is nothing to cancel/close
quitOnTopLevelReturn: false
disableStartupPopups: false
notARepository: 'prompt' # one of: 'prompt' | 'create' | 'skip'
notARepository: 'prompt' # one of: 'prompt' | 'create' | 'skip' | 'quit'
promptToReturnFromSubprocess: true # display confirmation when subprocess terminates
keybinding:
universal:
quit: 'q'
quit-alt1: '<c-c>' # alternative/alias of quit
return: '<esc>' # return to previous menu, will quit if there's nowhere to return
# When set to a printable character, this will work for returning from non-prompt panels
return-alt1: null
quitWithoutChangingDirectory: 'Q'
togglePanel: '<tab>' # goto the next panel
prevItem: '<up>' # go one line up
@@ -182,6 +191,8 @@ keybinding:
viewResetOptions: 'D'
fetch: 'f'
toggleTreeView: '`'
openMergeTool: 'M'
openStatusFilter: '<c-b>'
branches:
createPullRequest: 'o'
viewPullRequestOptions: 'O'
@@ -219,6 +230,7 @@ keybinding:
viewBisectOptions: 'b'
stash:
popStash: 'g'
renameStash: 'r'
commitFiles:
checkoutCommitFile: 'c'
main:
@@ -274,7 +286,7 @@ os:
Lazygit will log an error if none of these options are set.
You can specify a line number you are currently at when in the line-by-line mode.
You can specify the current line number when you're in the patch explorer.
```yaml
os:
@@ -339,23 +351,6 @@ The available attributes are:
- reverse # useful for high-contrast
- underline
## Light terminal theme
If you have issues with a light terminal theme where you can't read / see the text add these settings
```yaml
gui:
theme:
lightTheme: true
activeBorderColor:
- black
- bold
inactiveBorderColor:
- black
selectedLineBgColor:
- default
```
## Highlighting the selected line
If you don't like the default behaviour of highlighting the selected line with a blue background, you can use the `selectedLineBgColor` and `selectedRangeBgColor` keys to customise the behaviour. If you just want to embolden the selected line (this was the original default), you can do the following:
@@ -431,6 +426,14 @@ gui:
For all possible keybinding options, check [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md)
You can disable certain key bindings by specifying `null`.
```yaml
keybinding:
universal:
edit: null # disable 'edit file'
```
### Example Keybindings For Colemak Users
```yaml
@@ -531,3 +534,8 @@ notARepository: 'create'
# to skip without creating a new repo
notARepository: 'skip'
```
```yaml
# to exit immediately if run outside of the Git repository
notARepository: 'quit'
```

View File

@@ -8,7 +8,7 @@ customCommands:
command: 'hub browse -- "commit/{{.SelectedLocalCommit.Sha}}"'
context: 'commits'
- key: 'a'
command: "git {{if .SelectedFile.HasUnstagedChanges}} add {{else}} reset {{end}} {{.SelectedFile.Name}}"
command: "git {{if .SelectedFile.HasUnstagedChanges}} add {{else}} reset {{end}} {{.SelectedFile.Name | quote}}"
context: 'files'
description: 'toggle file staged'
- key: 'C'
@@ -77,6 +77,7 @@ For a given custom command, here are the allowed fields:
| loadingText | text to display while waiting for command to finish | no |
| description | text to display in the keybindings menu that appears when you press 'x' | no |
| stream | whether you want to stream the command's output to the Command Log panel | no |
| showOutput | whether you want to show the command's output in a gui prompt | no |
### Contexts
@@ -135,13 +136,14 @@ If an option has no name the value will be displayed to the user in place of the
### Placeholder values
Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/go/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects:
Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/golang/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects:
```
SelectedLocalCommit
SelectedReflogCommit
SelectedSubCommit
SelectedFile
SelectedPath
SelectedLocalBranch
SelectedRemoteBranch
SelectedRemote
@@ -159,7 +161,7 @@ If your custom keybinding collides with an inbuilt keybinding that is defined fo
### Debugging
If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `subprocess: true` so that it doesn't actually execute the command but you can see how the placeholders were resolved. Alternatively you can run lazygit in debug mode with `lazygit --debug` and in another terminal window run `lazygit --logs` to see which commands are actually run
If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `showOutput: true` so that it doesn't actually execute the command but you can see how the placeholders were resolved. Alternatively you can run lazygit in debug mode with `lazygit --debug` and in another terminal window run `lazygit --logs` to see which commands are actually run
### More Examples

View File

@@ -1,122 +1 @@
# How To Make And Run Integration Tests For lazygit
Integration tests are located in `test/integration`. Each test will run a bash script to prepare a test repo, then replay a recorded lazygit session from within that repo, and then the resultant repo will be compared to an expected repo that was created upon the initial recording. Each integration test lives in its own directory, and the name of the directory becomes the name of the test. Within the directory must be the following files:
### `test.json`
An example of a `test.json` is:
```
{ "description": "stage a file and commit the change", "speed": 20 }
```
The `speed` key refers to the playback speed as a multiple of the original recording speed. So 20 means the test will run 20 times faster than the original recording speed. If a test fails for a given speed, it will drop the speed and re-test, until finally attempting the test at the original speed. If you omit the speed, it will default to 10.
### `setup.sh`
This is a bash script containing the instructions for creating the test repo from scratch. For example:
```
#!/bin/sh
cd $1
git init
git config user.email "CI@example.com"
git config user.name "CI"
echo test1 > myfile1
git add .
git commit -am "myfile1"
```
Be sure to:
- ensure that by the end of the test you've got at least one commit in the repo, as we've had issues in the past when that wasn't the case.
- set the git user email and name as above so that your own user details aren't included in the snapshot.
## Running tests
### From a TUI
You can run/record/sandbox tests via a TUI with the following command:
```
go run test/lazyintegration/main.go
```
This TUI makes much of the following documentation redundant, but feel free to read through anyway!
### From command line
To run all tests - assuming you're at the project root:
```
go test ./pkg/gui/
```
To run them in parallel
```
PARALLEL=true go test ./pkg/gui
```
To run a single test
```
go test ./pkg/gui -run /<test name>
# For example, to run the `tags` test:
go test ./pkg/gui -run /tags
```
To run a test at a certain speed
```
SPEED=2 go test ./pkg/gui -run /<test name>
```
To update a snapshot
```
MODE=updateSnapshot go test ./pkg/gui -run /<test name>
```
## Creating a new test
To create a new test:
1. Copy and paste an existing test directory and rename the new directory to whatever you want the test name to be. Update the test.json file's description to describe your test.
2. Update the `setup.sh` any way you like
3. If you want to have a config folder for just that test, create a `config` directory to contain a `config.yml` and optionally a `state.yml` file. Otherwise, the `test/default_test_config` directory will be used.
4. From the lazygit root directory, run:
```
MODE=record go test ./pkg/gui -run /<test name>
```
5. Feel free to re-attempt recording as many times as you like. In the absence of a proper testing framework, the more deliberate your keypresses, the better!
6. Once satisfied with the recording, stage all the newly created files: `test.json`, `setup.sh`, `recording.json` and the `expected` directory that contains a copy of the repo you created.
The resulting directory will look like:
```
actual/ (the resulting repo(s) after running the test, ignored by git)
expected/ (the 'snapshot' repo(s))
config/ (need not be present)
test.json
setup.sh
recording.json
```
## Sandboxing
The integration tests serve a secondary purpose of providing a setup for easy sandboxing. If you want to run a test in sandbox mode (meaning the session won't be recorded and we won't create/update snapshots), go:
```
MODE=sandbox go test ./pkg/gui -run /<test name>
```
## Feedback
If you think this process can be improved, let me know! It shouldn't be too hard to change things.
see new docs [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)

View File

@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## List Panel Navigation
<pre>
<kbd>.</kbd>: next page
<kbd>,</kbd>: previous page
<kbd>.</kbd>: next page
<kbd><</kbd>: scroll to top
<kbd>></kbd>: scroll to bottom
<kbd>/</kbd>: start search
<kbd>></kbd>: scroll to bottom
<kbd>H</kbd>: scroll left
<kbd>L</kbd>: scroll right
<kbd>]</kbd>: next tab
@@ -102,7 +102,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>C</kbd>: commit changes using git editor
<kbd>e</kbd>: edit file
<kbd>o</kbd>: open file
<kbd>i</kbd>: Ignore or Exclude file
<kbd>i</kbd>: ignore or exclude file
<kbd>r</kbd>: refresh files
<kbd>s</kbd>: stash all changes
<kbd>S</kbd>: view stash options
@@ -140,60 +140,60 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Main Panel (Merging)
<pre>
<kbd>esc</kbd>: return to files panel
<kbd>M</kbd>: open external merge tool (git mergetool)
<kbd>space</kbd>: pick hunk
<kbd>b</kbd>: pick all hunks
<kbd>e</kbd>: edit file
<kbd>o</kbd>: open file
<kbd>◄</kbd>: select previous conflict
<kbd>►</kbd>: select next conflict
<kbd>▲</kbd>: select previous hunk
<kbd>▼</kbd>: select next hunk
<kbd>e</kbd>: edit file
<kbd>o</kbd>: open file
<kbd>z</kbd>: undo
<kbd>M</kbd>: open external merge tool (git mergetool)
<kbd>space</kbd>: pick hunk
<kbd>b</kbd>: pick all hunks
<kbd>esc</kbd>: return to files panel
</pre>
## Main Panel (Normal)
<pre>
<kbd>mouse wheel down</kbd>: scroll down (fn+up)
<kbd>mouse wheel up</kbd>: scroll up (fn+down)
<kbd>mouse wheel </kbd>: scroll down (fn+up)
<kbd>mouse wheel </kbd>: scroll up (fn+down)
</pre>
## Main Panel (Patch Building)
<pre>
<kbd>esc</kbd>: exit line-by-line mode
<kbd>o</kbd>: open file
<kbd>▲</kbd>: select previous line
<kbd>▼</kbd>: select next line
<kbd>◄</kbd>: select previous hunk
<kbd>►</kbd>: select next hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>space</kbd>: add/remove line(s) to patch
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open file
<kbd>e</kbd>: edit file
<kbd>space</kbd>: add/remove line(s) to patch
<kbd>esc</kbd>: exit custom patch builder
</pre>
## Main Panel (Staging)
<pre>
<kbd>esc</kbd>: return to files panel
<kbd>space</kbd>: toggle line staged / unstaged
<kbd>d</kbd>: delete change (git reset)
<kbd>tab</kbd>: switch to other panel
<kbd>o</kbd>: open file
<kbd>▲</kbd>: select previous line
<kbd>▼</kbd>: select next line
<kbd>◄</kbd>: select previous hunk
<kbd>►</kbd>: select next hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>e</kbd>: edit file
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open file
<kbd>e</kbd>: edit file
<kbd>esc</kbd>: return to files panel
<kbd>tab</kbd>: switch to other panel (staged/unstaged changes)
<kbd>space</kbd>: toggle line staged / unstaged
<kbd>d</kbd>: delete change (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: commit changes
<kbd>w</kbd>: commit changes without pre-commit hook
<kbd>C</kbd>: commit changes using git editor
</pre>
## Reflog
@@ -241,6 +241,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: pop
<kbd>d</kbd>: drop
<kbd>n</kbd>: new branch
<kbd>r</kbd>: rename stash
<kbd>enter</kbd>: view selected item's files
</pre>

View File

@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 一覧パネルの操作
<pre>
<kbd>.</kbd>: 次のページ
<kbd>,</kbd>: 前のページ
<kbd>.</kbd>: 次のページ
<kbd><</kbd>: 最上部までスクロール
<kbd>></kbd>: 最下部までスクロール
<kbd>/</kbd>: 検索を開始
<kbd>></kbd>: 最下部までスクロール
<kbd>H</kbd>: 左スクロール
<kbd>L</kbd>: 右スクロール
<kbd>]</kbd>: 次のタブ
@@ -48,6 +48,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: pop
<kbd>d</kbd>: drop
<kbd>n</kbd>: 新しいブランチを作成
<kbd>r</kbd>: Stashを変更
<kbd>enter</kbd>: view selected item's files
</pre>
@@ -199,60 +200,60 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## メインパネル (Merging)
<pre>
<kbd>esc</kbd>: ファイル一覧に戻る
<kbd>M</kbd>: git mergetoolを開く
<kbd>space</kbd>: pick hunk
<kbd>b</kbd>: pick all hunks
<kbd>e</kbd>: ファイルを編集
<kbd>o</kbd>: ファイルを開く
<kbd>◄</kbd>: 前のコンフリクトを選択
<kbd>►</kbd>: 次のコンフリクトを選択
<kbd>▲</kbd>: 前のhunkを選択
<kbd>▼</kbd>: 次のhunkを選択
<kbd>e</kbd>: ファイルを編集
<kbd>o</kbd>: ファイルを開く
<kbd>z</kbd>: アンドゥ
<kbd>M</kbd>: git mergetoolを開く
<kbd>space</kbd>: pick hunk
<kbd>b</kbd>: pick all hunks
<kbd>esc</kbd>: ファイル一覧に戻る
</pre>
## メインパネル (Normal)
<pre>
<kbd>mouse wheel down</kbd>: 下にスクロール (fn+up)
<kbd>mouse wheel up</kbd>: 上にスクロール (fn+down)
<kbd>mouse wheel </kbd>: 下にスクロール (fn+up)
<kbd>mouse wheel </kbd>: 上にスクロール (fn+down)
</pre>
## メインパネル (Patch Building)
<pre>
<kbd>esc</kbd>: line-by-lineモードを終了
<kbd>o</kbd>: ファイルを開く
<kbd>▲</kbd>: 前の行を選択
<kbd>▼</kbd>: 次の行を選択
<kbd>◄</kbd>: 前のhunkを選択
<kbd>►</kbd>: 次のhunkを選択
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>space</kbd>: 行をパッチに追加/削除
<kbd>v</kbd>: 範囲選択を切り替え
<kbd>V</kbd>: 範囲選択を切り替え
<kbd>a</kbd>: hunk選択を切り替え
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>o</kbd>: ファイルを開く
<kbd>e</kbd>: ファイルを編集
<kbd>space</kbd>: 行をパッチに追加/削除
<kbd>esc</kbd>: exit custom patch builder
</pre>
## メインパネル (Staging)
<pre>
<kbd>esc</kbd>: ファイル一覧に戻る
<kbd>space</kbd>: 選択行をステージ/アンステージ
<kbd>d</kbd>: 変更を削除 (git reset)
<kbd>tab</kbd>: パネルを切り替え
<kbd>o</kbd>: ファイルを開く
<kbd>▲</kbd>: 前の行を選択
<kbd>▼</kbd>: 次の行を選択
<kbd>◄</kbd>: 前のhunkを選択
<kbd>►</kbd>: 次のhunkを選択
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>e</kbd>: ファイルを編集
<kbd>v</kbd>: 範囲選択を切り替え
<kbd>V</kbd>: 範囲選択を切り替え
<kbd>a</kbd>: hunk選択を切り替え
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>o</kbd>: ファイルを開く
<kbd>e</kbd>: ファイルを編集
<kbd>esc</kbd>: ファイル一覧に戻る
<kbd>tab</kbd>: パネルを切り替え
<kbd>space</kbd>: 選択行をステージ/アンステージ
<kbd>d</kbd>: 変更を削除 (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: 変更をコミット
<kbd>w</kbd>: pre-commitフックを実行せずに変更をコミット
<kbd>C</kbd>: gitエディタを使用して変更をコミット
</pre>
## リモート

View File

@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## List Panel Navigation
<pre>
<kbd>.</kbd>: 다음 페이지
<kbd>,</kbd>: 이전 페이지
<kbd>.</kbd>: 다음 페이지
<kbd><</kbd>: 맨 위로 스크롤
<kbd>></kbd>: 맨 아래로 스크롤
<kbd>/</kbd>: 검색 시작
<kbd>></kbd>: 맨 아래로 스크롤
<kbd>H</kbd>: 우 스크롤
<kbd>L</kbd>: 좌 스크롤
<kbd>]</kbd>: 이전 탭
@@ -63,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: pop
<kbd>d</kbd>: drop
<kbd>n</kbd>: 새 브랜치 생성
<kbd>r</kbd>: rename stash
<kbd>enter</kbd>: view selected item's files
</pre>
@@ -84,60 +85,60 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 메인 패널 (Merging)
<pre>
<kbd>esc</kbd>: 파일 목록으로 돌아가기
<kbd>M</kbd>: git mergetool를 열
<kbd>space</kbd>: pick hunk
<kbd>b</kbd>: pick all hunks
<kbd>e</kbd>: 파일 편집
<kbd>o</kbd>: 파일 닫
<kbd>◄</kbd>: 이전 충돌을 선택
<kbd>►</kbd>: 다음 충돌을 선택
<kbd>▲</kbd>: 이전 hunk를 선택
<kbd>▼</kbd>: 다음 hunk를 선택
<kbd>e</kbd>: 파일 편집
<kbd>o</kbd>: 파일 닫기
<kbd>z</kbd>: 되돌리기
<kbd>M</kbd>: git mergetool를 열기
<kbd>space</kbd>: pick hunk
<kbd>b</kbd>: pick all hunks
<kbd>esc</kbd>: 파일 목록으로 돌아가기
</pre>
## 메인 패널 (Normal)
<pre>
<kbd>mouse wheel down</kbd>: 아래로 스크롤 (fn+up)
<kbd>mouse wheel up</kbd>: 위로 스크롤 (fn+down)
<kbd>mouse wheel </kbd>: 아래로 스크롤 (fn+up)
<kbd>mouse wheel </kbd>: 위로 스크롤 (fn+down)
</pre>
## 메인 패널 (Patch Building)
<pre>
<kbd>esc</kbd>: line-by-line 모드 종료
<kbd>o</kbd>: 파일 닫기
<kbd>▲</kbd>: 이전 줄 선택
<kbd>▼</kbd>: 다음 줄 선택
<kbd>◄</kbd>: 이전 hunk를 선택
<kbd>►</kbd>: 다음 hunk를 선택
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>space</kbd>: line(s)을 패치에 추가/삭제
<kbd>v</kbd>: 드래그 선택 전환
<kbd>V</kbd>: 드래그 선택 전환
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>o</kbd>: 파일 닫기
<kbd>e</kbd>: 파일 편집
<kbd>space</kbd>: line(s)을 패치에 추가/삭제
<kbd>esc</kbd>: exit custom patch builder
</pre>
## 메인 패널 (Staging)
<pre>
<kbd>esc</kbd>: 파일 목록으로 돌아가기
<kbd>space</kbd>: 선택한 행을 staged / unstaged
<kbd>d</kbd>: 변경을 삭제 (git reset)
<kbd>tab</kbd>: 패널 전환
<kbd>o</kbd>: 파일 닫기
<kbd>▲</kbd>: 이전 줄 선택
<kbd>▼</kbd>: 다음 줄 선택
<kbd>◄</kbd>: 이전 hunk를 선택
<kbd>►</kbd>: 다음 hunk를 선택
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>e</kbd>: 파일 편집
<kbd>v</kbd>: 드래그 선택 전환
<kbd>V</kbd>: 드래그 선택 전환
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>o</kbd>: 파일 닫기
<kbd>e</kbd>: 파일 편집
<kbd>esc</kbd>: 파일 목록으로 돌아가기
<kbd>tab</kbd>: 패널 전환
<kbd>space</kbd>: 선택한 행을 staged / unstaged
<kbd>d</kbd>: 변경을 삭제 (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: 커밋 변경내용
<kbd>w</kbd>: commit changes without pre-commit hook
<kbd>C</kbd>: Git 편집기를 사용하여 변경 내용을 커밋합니다.
</pre>
## 브랜치
@@ -280,7 +281,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>C</kbd>: Git 편집기를 사용하여 변경 내용을 커밋합니다.
<kbd>e</kbd>: 파일 편집
<kbd>o</kbd>: 파일 닫기
<kbd>i</kbd>: Ignore file
<kbd>i</kbd>: ignore file
<kbd>r</kbd>: 파일 새로고침
<kbd>s</kbd>: 변경사항을 Stash
<kbd>S</kbd>: Stash 옵션 보기

View File

@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Lijstpaneel Navigatie
<pre>
<kbd>.</kbd>: volgende pagina
<kbd>,</kbd>: vorige pagina
<kbd>.</kbd>: volgende pagina
<kbd><</kbd>: scroll naar boven
<kbd>></kbd>: scroll naar beneden
<kbd>/</kbd>: start met zoeken
<kbd>></kbd>: scroll naar beneden
<kbd>H</kbd>: scroll left
<kbd>L</kbd>: scroll right
<kbd>]</kbd>: volgende tabblad
@@ -55,7 +55,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>C</kbd>: commit veranderingen met de git editor
<kbd>e</kbd>: verander bestand
<kbd>o</kbd>: open bestand
<kbd>i</kbd>: Ignore or Exclude file
<kbd>i</kbd>: ignore or exclude file
<kbd>r</kbd>: refresh bestanden
<kbd>s</kbd>: stash-bestanden
<kbd>S</kbd>: bekijk stash opties
@@ -140,40 +140,39 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Mergen
<pre>
<kbd>esc</kbd>: ga terug naar het bestanden paneel
<kbd>M</kbd>: open external merge tool (git mergetool)
<kbd>space</kbd>: kies hunk
<kbd>b</kbd>: kies bijde hunks
<kbd>e</kbd>: verander bestand
<kbd>o</kbd>: open bestand
<kbd>◄</kbd>: selecteer voorgaand conflict
<kbd>►</kbd>: selecteer volgende conflict
<kbd>▲</kbd>: selecteer bovenste hunk
<kbd>▼</kbd>: selecteer onderste hunk
<kbd>e</kbd>: verander bestand
<kbd>o</kbd>: open bestand
<kbd>z</kbd>: ongedaan maken
<kbd>M</kbd>: open external merge tool (git mergetool)
<kbd>space</kbd>: kies hunk
<kbd>b</kbd>: kies bijde hunks
<kbd>esc</kbd>: ga terug naar het bestanden paneel
</pre>
## Normaal
<pre>
<kbd>mouse wheel down</kbd>: scroll omlaag (fn+up)
<kbd>mouse wheel up</kbd>: scroll omhoog (fn+down)
<kbd>mouse wheel </kbd>: scroll omlaag (fn+up)
<kbd>mouse wheel </kbd>: scroll omhoog (fn+down)
</pre>
## Patch Bouwen
<pre>
<kbd>esc</kbd>: sluit lijn-bij-lijn modus
<kbd>o</kbd>: open bestand
<kbd>▲</kbd>: selecteer de vorige lijn
<kbd>▼</kbd>: selecteer de volgende lijn
<kbd>◄</kbd>: selecteer de vorige hunk
<kbd>►</kbd>: selecteer de volgende hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>space</kbd>: voeg toe/verwijder lijn(en) in patch
<kbd>v</kbd>: toggle drag selecteer
<kbd>V</kbd>: toggle drag selecteer
<kbd>a</kbd>: toggle selecteer hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open bestand
<kbd>e</kbd>: verander bestand
<kbd>space</kbd>: voeg toe/verwijder lijn(en) in patch
<kbd>esc</kbd>: sluit lijn-bij-lijn modus
</pre>
## Reflog
@@ -217,21 +216,22 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Staging
<pre>
<kbd>esc</kbd>: ga terug naar het bestanden paneel
<kbd>space</kbd>: toggle lijnen staged / unstaged
<kbd>d</kbd>: verwijdert change (git reset)
<kbd>tab</kbd>: ga naar een ander paneel
<kbd>o</kbd>: open bestand
<kbd>▲</kbd>: selecteer de vorige lijn
<kbd>▼</kbd>: selecteer de volgende lijn
<kbd>◄</kbd>: selecteer de vorige hunk
<kbd>►</kbd>: selecteer de volgende hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>e</kbd>: verander bestand
<kbd>v</kbd>: toggle drag selecteer
<kbd>V</kbd>: toggle drag selecteer
<kbd>a</kbd>: toggle selecteer hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open bestand
<kbd>e</kbd>: verander bestand
<kbd>esc</kbd>: ga terug naar het bestanden paneel
<kbd>tab</kbd>: ga naar een ander paneel
<kbd>space</kbd>: toggle lijnen staged / unstaged
<kbd>d</kbd>: verwijdert change (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: commit veranderingen
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
<kbd>C</kbd>: commit veranderingen met de git editor
</pre>
## Stash
@@ -241,6 +241,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: pop
<kbd>d</kbd>: laten vallen
<kbd>n</kbd>: nieuwe branch
<kbd>r</kbd>: rename stash
<kbd>enter</kbd>: bekijk gecommite bestanden
</pre>

View File

@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## List Panel Navigation
<pre>
<kbd>.</kbd>: next page
<kbd>,</kbd>: previous page
<kbd>.</kbd>: next page
<kbd><</kbd>: scroll to top
<kbd>></kbd>: scroll to bottom
<kbd>/</kbd>: start search
<kbd>></kbd>: scroll to bottom
<kbd>H</kbd>: scroll left
<kbd>L</kbd>: scroll right
<kbd>]</kbd>: next tab
@@ -99,17 +99,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Main Panel (Patch Building)
<pre>
<kbd>esc</kbd>: wyście z trybu "linia po linii"
<kbd>o</kbd>: otwórz plik
<kbd>▲</kbd>: poprzednia linia
<kbd>▼</kbd>: następna linia
<kbd>◄</kbd>: poprzedni kawałek
<kbd>►</kbd>: następny kawałek
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>space</kbd>: add/remove line(s) to patch
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: otwórz plik
<kbd>e</kbd>: edytuj plik
<kbd>space</kbd>: add/remove line(s) to patch
<kbd>esc</kbd>: wyście z trybu "linia po linii"
</pre>
## Pliki
@@ -126,7 +125,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>C</kbd>: Zatwierdź zmiany używając edytora
<kbd>e</kbd>: edytuj plik
<kbd>o</kbd>: otwórz plik
<kbd>i</kbd>: Ignore or Exclude file
<kbd>i</kbd>: ignore or exclude file
<kbd>r</kbd>: odśwież pliki
<kbd>s</kbd>: przechowaj zmiany
<kbd>S</kbd>: wyświetl opcje schowka
@@ -156,21 +155,22 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Poczekalnia
<pre>
<kbd>esc</kbd>: wróć do panelu plików
<kbd>space</kbd>: toggle line staged / unstaged
<kbd>d</kbd>: delete change (git reset)
<kbd>tab</kbd>: switch to other panel
<kbd>o</kbd>: otwórz plik
<kbd>▲</kbd>: poprzednia linia
<kbd>▼</kbd>: następna linia
<kbd>◄</kbd>: poprzedni kawałek
<kbd>►</kbd>: następny kawałek
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>e</kbd>: edytuj plik
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: otwórz plik
<kbd>e</kbd>: edytuj plik
<kbd>esc</kbd>: wróć do panelu plików
<kbd>tab</kbd>: switch to other panel (staged/unstaged changes)
<kbd>space</kbd>: toggle line staged / unstaged
<kbd>d</kbd>: delete change (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: Zatwierdź zmiany
<kbd>w</kbd>: zatwierdź zmiany bez skryptu pre-commit
<kbd>C</kbd>: Zatwierdź zmiany używając edytora
</pre>
## Reflog
@@ -214,17 +214,17 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Scalanie
<pre>
<kbd>esc</kbd>: wróć do panelu plików
<kbd>M</kbd>: open external merge tool (git mergetool)
<kbd>space</kbd>: wybierz kawałek
<kbd>b</kbd>: wybierz wszystkie kawałki
<kbd>e</kbd>: edytuj plik
<kbd>o</kbd>: otwórz plik
<kbd>◄</kbd>: poprzedni konflikt
<kbd>►</kbd>: następny konflikt
<kbd>▲</kbd>: wybierz poprzedni kawałek
<kbd>▼</kbd>: wybierz następny kawałek
<kbd>e</kbd>: edytuj plik
<kbd>o</kbd>: otwórz plik
<kbd>z</kbd>: cofnij
<kbd>M</kbd>: open external merge tool (git mergetool)
<kbd>space</kbd>: wybierz kawałek
<kbd>b</kbd>: wybierz wszystkie kawałki
<kbd>esc</kbd>: wróć do panelu plików
</pre>
## Schowek
@@ -234,6 +234,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: wyciągnij
<kbd>d</kbd>: porzuć
<kbd>n</kbd>: nowa gałąź
<kbd>r</kbd>: rename stash
<kbd>enter</kbd>: przeglądaj pliki commita
</pre>
@@ -289,6 +290,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Zwykłe
<pre>
<kbd>mouse wheel down</kbd>: przewiń w dół (fn+up)
<kbd>mouse wheel up</kbd>: przewiń w górę (fn+down)
<kbd>mouse wheel </kbd>: przewiń w dół (fn+up)
<kbd>mouse wheel </kbd>: przewiń w górę (fn+down)
</pre>

View File

@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 列表面板导航
<pre>
<kbd>.</kbd>: 下一页
<kbd>,</kbd>: 上一页
<kbd>.</kbd>: 下一页
<kbd><</kbd>: 滚动到顶部
<kbd>></kbd>: 滚动到底部
<kbd>/</kbd>: 开始搜索
<kbd>></kbd>: 滚动到底部
<kbd>H</kbd>: 向左滚动
<kbd>L</kbd>: 向右滚动
<kbd>]</kbd>: 下一个标签
@@ -183,17 +183,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 构建补丁中
<pre>
<kbd>esc</kbd>: 退出逐行模式
<kbd>o</kbd>: 打开文件
<kbd>▲</kbd>: 选择上一行
<kbd>▼</kbd>: 选择下一行
<kbd>◄</kbd>: 选择上一个区块
<kbd>►</kbd>: 选择下一个区块
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
<kbd>space</kbd>: 添加/移除 行到补丁
<kbd>v</kbd>: 切换拖动选择
<kbd>V</kbd>: 切换拖动选择
<kbd>a</kbd>: 切换选择区块
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
<kbd>o</kbd>: 打开文件
<kbd>e</kbd>: 编辑文件
<kbd>space</kbd>: 添加/移除 行到补丁
<kbd>esc</kbd>: 退出逐行模式
</pre>
## 标签页面
@@ -210,44 +209,45 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 正在合并
<pre>
<kbd>esc</kbd>: 返回文件面板
<kbd>M</kbd>: 打开外部合并工具 (git mergetool)
<kbd>space</kbd>: 选中区块
<kbd>b</kbd>: 选中所有区块
<kbd>e</kbd>: 编辑文件
<kbd>o</kbd>: 打开文件
<kbd>◄</kbd>: 选择上一个冲突
<kbd>►</kbd>: 选择下一个冲突
<kbd>▲</kbd>: 选择顶部块
<kbd>▼</kbd>: 选择底部块
<kbd>e</kbd>: 编辑文件
<kbd>o</kbd>: 打开文件
<kbd>z</kbd>: 撤销
<kbd>M</kbd>: 打开外部合并工具 (git mergetool)
<kbd>space</kbd>: 选中区块
<kbd>b</kbd>: 选中所有区块
<kbd>esc</kbd>: 返回文件面板
</pre>
## 正在暂存
<pre>
<kbd>esc</kbd>: 返回文件面板
<kbd>space</kbd>: 切换行暂存状态
<kbd>d</kbd>: 取消变更 (git reset)
<kbd>tab</kbd>: 切换到其他面板
<kbd>o</kbd>: 打开文件
<kbd>▲</kbd>: 选择上一行
<kbd>▼</kbd>: 选择下一行
<kbd>◄</kbd>: 选择上一个区块
<kbd>►</kbd>: 选择下一个区块
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
<kbd>e</kbd>: 编辑文件
<kbd>v</kbd>: 切换拖动选择
<kbd>V</kbd>: 切换拖动选择
<kbd>a</kbd>: 切换选择区块
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
<kbd>o</kbd>: 打开文件
<kbd>e</kbd>: 编辑文件
<kbd>esc</kbd>: 返回文件面板
<kbd>tab</kbd>: 切换到其他面板
<kbd>space</kbd>: 切换行暂存状态
<kbd>d</kbd>: 取消变更 (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: 提交更改
<kbd>w</kbd>: 提交更改而无需预先提交钩子
<kbd>C</kbd>: 提交更改(使用编辑器编辑提交信息)
</pre>
## 正常
<pre>
<kbd>mouse wheel down</kbd>: 向下滚动 (fn+up)
<kbd>mouse wheel up</kbd>: 向上滚动 (fn+down)
<kbd>mouse wheel </kbd>: 向下滚动 (fn+up)
<kbd>mouse wheel </kbd>: 向上滚动 (fn+down)
</pre>
## 状态
@@ -267,6 +267,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: 应用并删除
<kbd>d</kbd>: 删除
<kbd>n</kbd>: 新分支
<kbd>r</kbd>: rename stash
<kbd>enter</kbd>: 查看提交的文件
</pre>

29
go.mod
View File

@@ -11,30 +11,34 @@ require (
github.com/creack/pty v1.1.11
github.com/fsmiamoto/git-todo-parser v0.0.2
github.com/fsnotify/fsnotify v1.4.7
github.com/gdamore/tcell/v2 v2.5.4
github.com/go-errors/errors v1.4.2
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.20201006095850-341962be15a4
github.com/jesseduffield/gocui v0.3.1-0.20220417002912-bce22fd599f6
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
github.com/jesseduffield/gocui v0.3.1-0.20230105224502-214a0df08da7
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
github.com/jesseduffield/yaml v2.1.0+incompatible
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.13
github.com/mattn/go-runewidth v0.0.14
github.com/mgutz/str v1.2.0
github.com/pmezard/go-difflib v1.0.0
github.com/sahilm/fuzzy v0.1.0
github.com/samber/lo v1.10.1
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/spkg/bom v0.0.0-20160624110644-59b7046e48ad
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.8.0
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1
)
require (
@@ -42,7 +46,6 @@ require (
github.com/emirpasic/gods v1.12.0 // indirect
github.com/fatih/color v1.9.0 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.5.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.0.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect
@@ -58,15 +61,15 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/term v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

77
go.sum
View File

@@ -35,8 +35,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
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.5.1 h1:zc3LPdpK184lBW7syF2a5C6MV827KmErk9jGVnmsl/I=
github.com/gdamore/tcell/v2 v2.5.1/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k=
github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw=
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=
@@ -70,12 +70,14 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8TIcC6Y4RI+1ZbJDOHfGJ570tPeYVCqo7/tws=
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.20201006095850-341962be15a4 h1:GOQrmaE8i+KEdB8NzAegKYd4tPn/inM0I1uo0NXFerg=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20220417002912-bce22fd599f6 h1:Fmay0Lz21taUpXiIbFkjjIIcn0E5GKwp5UFRuXaOiGQ=
github.com/jesseduffield/gocui v0.3.1-0.20220417002912-bce22fd599f6/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
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.20230105224502-214a0df08da7 h1:a7T87NGCrmFhzWBWvaaPQppjbY9/w0Vl4hoB7RnFaug=
github.com/jesseduffield/gocui v0.3.1-0.20230105224502-214a0df08da7/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
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=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ=
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e/go.mod h1:u60qdFGXRd36jyEXxetz0vQceQIxzI13lIo3EFUDf4I=
github.com/jesseduffield/yaml v2.1.0+incompatible h1:HWQJ1gIv2zHKbDYNp0Jwjlj24K8aqpFHnMCynY1EpmE=
@@ -112,8 +114,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
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.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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/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=
@@ -125,20 +127,25 @@ github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
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/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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/samber/lo v1.10.1 h1:0D3h7i0U3hRAbaCeQ82DLe67n0A7Bbl0/cEoWqFGp+U=
github.com/samber/lo v1.10.1/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM=
github.com/samber/lo v1.31.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
github.com/sanity-io/litter v1.5.2 h1:AnC8s9BMORWH5a4atZ4D6FPVvKGzHcnc5/IVTa87myw=
github.com/sanity-io/litter v1.5.2/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
@@ -146,34 +153,42 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
github.com/urfave/cli v1.20.1-0.20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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/exp v0.0.0-20220318154914-8dddf5d87bd8 h1:s/+U+w0teGzcoH2mdIlFQ6KfVKGaYpgyGdUefZrn9TU=
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c h1:dk0ukUIHmGHqASjP0iue2261isepFCC6XRCSd1nHgDw=
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c/go.mod h1:iQL9McJNjoIa5mjH6nYTCTZXUN6RP+XW3eib7Ya3XcI=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -183,22 +198,30 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.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-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -217,5 +240,5 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

171
main.go
View File

@@ -1,179 +1,24 @@
package main
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/integrii/flaggy"
"github.com/jesseduffield/lazygit/pkg/app"
"github.com/jesseduffield/lazygit/pkg/app/daemon"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/logs"
yaml "github.com/jesseduffield/yaml"
)
// These values may be set by the build script via the LDFLAGS argument
var (
commit string
version = "unversioned"
date string
version string
buildSource = "unknown"
)
func main() {
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
repoPath := ""
flaggy.String(&repoPath, "p", "path", "Path of git repo. (equivalent to --work-tree=<path> --git-dir=<path>/.git/)")
filterPath := ""
flaggy.String(&filterPath, "f", "filter", "Path to filter on in `git log -- <path>`. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted")
gitArg := ""
flaggy.AddPositionalValue(&gitArg, "git-arg", 1, false, "Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed.")
versionFlag := false
flaggy.Bool(&versionFlag, "v", "version", "Print the current version")
debuggingFlag := false
flaggy.Bool(&debuggingFlag, "d", "debug", "Run in debug mode with logging (see --logs flag below). Use the LOG_LEVEL env var to set the log level (debug/info/warn/error)")
logFlag := false
flaggy.Bool(&logFlag, "l", "logs", "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)")
configFlag := false
flaggy.Bool(&configFlag, "c", "config", "Print the default config")
configDirFlag := false
flaggy.Bool(&configDirFlag, "cd", "print-config-dir", "Print the config directory")
useConfigDir := ""
flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory")
workTree := ""
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
gitDir := ""
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
customConfig := ""
flaggy.String(&customConfig, "ucf", "use-config-file", "Comma separated list to custom config file(s)")
flaggy.Parse()
if repoPath != "" {
if workTree != "" || gitDir != "" {
log.Fatal("--path option is incompatible with the --work-tree and --git-dir options")
}
absRepoPath, err := filepath.Abs(repoPath)
if err != nil {
log.Fatal(err)
}
workTree = absRepoPath
gitDir = filepath.Join(absRepoPath, ".git")
ldFlagsBuildInfo := &app.BuildInfo{
Commit: commit,
Date: date,
Version: version,
BuildSource: buildSource,
}
if customConfig != "" {
os.Setenv("LG_CONFIG_FILE", customConfig)
}
if useConfigDir != "" {
os.Setenv("CONFIG_DIR", useConfigDir)
}
if workTree != "" {
env.SetGitWorkTreeEnv(workTree)
}
if gitDir != "" {
env.SetGitDirEnv(gitDir)
}
if versionFlag {
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s\n", commit, date, buildSource, version, runtime.GOOS, runtime.GOARCH)
os.Exit(0)
}
if configFlag {
var buf bytes.Buffer
encoder := yaml.NewEncoder(&buf)
err := encoder.Encode(config.GetDefaultConfig())
if err != nil {
log.Fatal(err.Error())
}
fmt.Printf("%s\n", buf.String())
os.Exit(0)
}
if configDirFlag {
fmt.Printf("%s\n", config.ConfigDir())
os.Exit(0)
}
if logFlag {
logs.TailLogs()
os.Exit(0)
}
if workTree != "" {
if err := os.Chdir(workTree); err != nil {
log.Fatal(err.Error())
}
}
tempDir, err := os.MkdirTemp("", "lazygit-*")
if err != nil {
log.Fatal(err.Error())
}
defer os.RemoveAll(tempDir)
appConfig, err := config.NewAppConfig("lazygit", version, commit, date, buildSource, debuggingFlag, tempDir)
if err != nil {
log.Fatal(err.Error())
}
common, err := app.NewCommon(appConfig)
if err != nil {
log.Fatal(err)
}
if daemon.InDaemonMode() {
daemon.Handle(common)
return
}
parsedGitArg := parseGitArg(gitArg)
app.Run(appConfig, common, types.NewStartArgs(filterPath, parsedGitArg))
}
func parseGitArg(gitArg string) types.GitArg {
typedArg := types.GitArg(gitArg)
// using switch so that linter catches when a new git arg value is defined but not handled here
switch typedArg {
case types.GitArgNone, types.GitArgStatus, types.GitArgBranch, types.GitArgLog, types.GitArgStash:
return typedArg
}
permittedValues := []string{
string(types.GitArgStatus),
string(types.GitArgBranch),
string(types.GitArgLog),
string(types.GitArgStash),
}
log.Fatalf("Invalid git arg value: '%s'. Must be one of the following values: %s. e.g. 'lazygit status'. See 'lazygit --help'.",
gitArg,
strings.Join(permittedValues, ", "),
)
panic("unreachable")
app.Start(ldFlagsBuildInfo, nil)
}

View File

@@ -7,22 +7,20 @@ import (
"log"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/go-errors/errors"
"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_config"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/gui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/updates"
)
@@ -38,7 +36,11 @@ type App struct {
Updater *updates.Updater // may only need this on the Gui
}
func Run(config config.AppConfigurer, common *common.Common, startArgs types.StartArgs) {
func Run(
config config.AppConfigurer,
common *common.Common,
startArgs appTypes.StartArgs,
) {
app, err := NewApp(config, common)
if err == nil {
@@ -96,66 +98,56 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
return app, err
}
gitVersion, err := app.validateGitVersion()
if err != nil {
return app, err
}
showRecentRepos, err := app.setupRepo()
if err != nil {
return app, err
}
gitConfig := git_config.NewStdCachedGitConfig(app.Log)
app.Gui, err = gui.NewGui(common, config, gitConfig, app.Updater, showRecentRepos, dirName)
app.Gui, err = gui.NewGui(common, config, gitVersion, app.Updater, showRecentRepos, dirName)
if err != nil {
return app, err
}
return app, nil
}
func (app *App) validateGitVersion() error {
output, err := app.OSCommand.Cmd.New("git --version").RunWithOutput()
func (app *App) validateGitVersion() (*git_commands.GitVersion, error) {
version, err := git_commands.GetGitVersion(app.OSCommand)
// if we get an error anywhere here we'll show the same status
minVersionError := errors.New(app.Tr.MinGitVersionError)
if err != nil {
return minVersionError
return nil, minVersionError
}
if isGitVersionValid(output) {
return nil
if version.IsOlderThan(2, 0, 0) {
return nil, minVersionError
}
return minVersionError
}
func isGitVersionValid(versionStr string) bool {
// output should be something like: 'git version 2.23.0 (blah)'
re := regexp.MustCompile(`[^\d]+([\d\.]+)`)
matches := re.FindStringSubmatch(versionStr)
if len(matches) == 0 {
return false
}
gitVersion := matches[1]
majorVersion, err := strconv.Atoi(gitVersion[0:1])
if err != nil {
return false
}
if majorVersion < 2 {
return false
}
return true
return version, nil
}
func isDirectoryAGitRepository(dir string) (bool, error) {
info, err := os.Stat(filepath.Join(dir, ".git"))
return info != nil && info.IsDir(), err
return info != nil, err
}
func openRecentRepo(app *App) bool {
for _, repoDir := range app.Config.GetAppState().RecentRepos {
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
if err := os.Chdir(repoDir); err == nil {
return true
}
}
}
return false
}
func (app *App) setupRepo() (bool, error) {
if err := app.validateGitVersion(); err != nil {
return false, err
}
if env.GetGitDirEnv() != "" {
// we've been given the git dir directly. We'll verify this dir when initializing our Git object
return false, nil
@@ -167,53 +159,85 @@ func (app *App) setupRepo() (bool, error) {
if err != nil {
return false, err
}
if isRepo, err := isDirectoryAGitRepository(cwd); isRepo {
return false, err
}
shouldInitRepo := true
notARepository := app.UserConfig.NotARepository
initialBranch := ""
if notARepository == "prompt" {
var shouldInitRepo bool
initialBranchArg := ""
switch app.UserConfig.NotARepository {
case "prompt":
// Offer to initialize a new repository in current directory.
fmt.Print(app.Tr.CreateRepo)
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
if strings.Trim(response, " \r\n") != "y" {
shouldInitRepo = false
} else {
shouldInitRepo = (strings.Trim(response, " \r\n") == "y")
if shouldInitRepo {
// Ask for the initial branch name
fmt.Print(app.Tr.InitialBranch)
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
if trimmedResponse := strings.Trim(response, " \r\n"); len(trimmedResponse) > 0 {
initialBranch += "--initial-branch=" + trimmedResponse
initialBranchArg += "--initial-branch=" + app.OSCommand.Quote(trimmedResponse)
}
}
} else if notARepository == "skip" {
case "create":
shouldInitRepo = true
case "skip":
shouldInitRepo = false
}
if !shouldInitRepo {
// check if we have a recent repo we can open
for _, repoDir := range app.Config.GetAppState().RecentRepos {
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
if err := os.Chdir(repoDir); err == nil {
return true, nil
}
}
}
fmt.Println(app.Tr.NoRecentRepositories)
case "quit":
fmt.Fprintln(os.Stderr, app.Tr.NotARepository)
os.Exit(1)
default:
fmt.Fprintln(os.Stderr, app.Tr.IncorrectNotARepository)
os.Exit(1)
}
if err := app.OSCommand.Cmd.New("git init " + initialBranch).Run(); err != nil {
if shouldInitRepo {
if err := app.OSCommand.Cmd.New("git init " + initialBranchArg).Run(); err != nil {
return false, err
}
return false, nil
}
// check if we have a recent repo we can open
for _, repoDir := range app.Config.GetAppState().RecentRepos {
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
if err := os.Chdir(repoDir); err == nil {
return true, nil
}
}
}
fmt.Fprintln(os.Stderr, app.Tr.NoRecentRepositories)
os.Exit(1)
}
// Run this afterward so that the previous repo creation steps can run without this interfering
if isBare, err := git_commands.IsBareRepo(app.OSCommand); isBare {
if err != nil {
return false, err
}
fmt.Print(app.Tr.BareRepo)
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
if shouldOpenRecent := strings.Trim(response, " \r\n") == "y"; !shouldOpenRecent {
os.Exit(0)
}
if didOpenRepo := openRecentRepo(app); didOpenRepo {
return true, nil
}
fmt.Println(app.Tr.NoRecentRepositories)
os.Exit(1)
}
return false, nil
}
func (app *App) Run(startArgs types.StartArgs) error {
func (app *App) Run(startArgs appTypes.StartArgs) error {
err := app.Gui.RunAndHandleError(startArgs)
return err
}

View File

@@ -1,45 +0,0 @@
package app
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsGitVersionValid(t *testing.T) {
type scenario struct {
versionStr string
expectedResult bool
}
scenarios := []scenario{
{
"",
false,
},
{
"git version 1.9.0",
false,
},
{
"git version 1.9.0 (Apple Git-128)",
false,
},
{
"git version 2.4.0",
true,
},
{
"git version 2.24.3 (Apple Git-128)",
true,
},
}
for _, s := range scenarios {
s := s
t.Run(s.versionStr, func(t *testing.T) {
result := isGitVersionValid(s.versionStr)
assert.Equal(t, result, s.expectedResult)
})
}
}

View File

@@ -1,7 +1,6 @@
package daemon
import (
"io/ioutil"
"log"
"os"
"path/filepath"
@@ -77,7 +76,7 @@ func (self *rebaseDaemon) Run() error {
self.c.Log.Info("args: ", os.Args)
if strings.HasSuffix(os.Args[1], "git-rebase-todo") {
if err := ioutil.WriteFile(os.Args[1], []byte(os.Getenv(RebaseTODOEnvKey)), 0o644); err != nil {
if err := os.WriteFile(os.Args[1], []byte(os.Getenv(RebaseTODOEnvKey)), 0o644); err != nil {
return err
}
} else if strings.HasSuffix(os.Args[1], filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test

279
pkg/app/entry_point.go Normal file
View File

@@ -0,0 +1,279 @@
package app
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"runtime/debug"
"strings"
"github.com/integrii/flaggy"
"github.com/jesseduffield/lazygit/pkg/app/daemon"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
"github.com/jesseduffield/lazygit/pkg/logs"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"gopkg.in/yaml.v3"
)
type cliArgs struct {
RepoPath string
FilterPath string
GitArg string
PrintVersionInfo bool
Debug bool
TailLogs bool
PrintDefaultConfig bool
PrintConfigDir bool
UseConfigDir string
WorkTree string
GitDir string
CustomConfigFile string
}
type BuildInfo struct {
Commit string
Date string
Version string
BuildSource string
}
func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTest) {
cliArgs := parseCliArgsAndEnvVars()
mergeBuildInfo(buildInfo)
if cliArgs.RepoPath != "" {
if cliArgs.WorkTree != "" || cliArgs.GitDir != "" {
log.Fatal("--path option is incompatible with the --work-tree and --git-dir options")
}
absRepoPath, err := filepath.Abs(cliArgs.RepoPath)
if err != nil {
log.Fatal(err)
}
if isRepo, err := isDirectoryAGitRepository(absRepoPath); err != nil || !isRepo {
log.Fatal(absRepoPath + " is not a valid git repository.")
}
cliArgs.WorkTree = absRepoPath
cliArgs.GitDir = filepath.Join(absRepoPath, ".git")
}
if cliArgs.CustomConfigFile != "" {
os.Setenv("LG_CONFIG_FILE", cliArgs.CustomConfigFile)
}
if cliArgs.UseConfigDir != "" {
os.Setenv("CONFIG_DIR", cliArgs.UseConfigDir)
}
if cliArgs.WorkTree != "" {
env.SetGitWorkTreeEnv(cliArgs.WorkTree)
}
if cliArgs.GitDir != "" {
env.SetGitDirEnv(cliArgs.GitDir)
}
if cliArgs.PrintVersionInfo {
gitVersion := getGitVersionInfo()
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s, git version=%s\n", buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, buildInfo.Version, runtime.GOOS, runtime.GOARCH, gitVersion)
os.Exit(0)
}
if cliArgs.PrintDefaultConfig {
var buf bytes.Buffer
encoder := yaml.NewEncoder(&buf)
err := encoder.Encode(config.GetDefaultConfig())
if err != nil {
log.Fatal(err.Error())
}
fmt.Printf("%s\n", buf.String())
os.Exit(0)
}
if cliArgs.PrintConfigDir {
fmt.Printf("%s\n", config.ConfigDir())
os.Exit(0)
}
if cliArgs.TailLogs {
logs.TailLogs()
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())
}
defer os.RemoveAll(tempDir)
appConfig, err := config.NewAppConfig("lazygit", buildInfo.Version, buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, cliArgs.Debug, tempDir)
if err != nil {
log.Fatal(err.Error())
}
if integrationTest != nil {
integrationTest.SetupConfig(appConfig)
}
common, err := NewCommon(appConfig)
if err != nil {
log.Fatal(err)
}
if daemon.InDaemonMode() {
daemon.Handle(common)
return
}
parsedGitArg := parseGitArg(cliArgs.GitArg)
Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, integrationTest))
}
func parseCliArgsAndEnvVars() *cliArgs {
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
repoPath := ""
flaggy.String(&repoPath, "p", "path", "Path of git repo. (equivalent to --work-tree=<path> --git-dir=<path>/.git/)")
filterPath := ""
flaggy.String(&filterPath, "f", "filter", "Path to filter on in `git log -- <path>`. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted")
gitArg := ""
flaggy.AddPositionalValue(&gitArg, "git-arg", 1, false, "Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed.")
printVersionInfo := false
flaggy.Bool(&printVersionInfo, "v", "version", "Print the current version")
debug := false
flaggy.Bool(&debug, "d", "debug", "Run in debug mode with logging (see --logs flag below). Use the LOG_LEVEL env var to set the log level (debug/info/warn/error)")
tailLogs := false
flaggy.Bool(&tailLogs, "l", "logs", "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)")
printDefaultConfig := false
flaggy.Bool(&printDefaultConfig, "c", "config", "Print the default config")
printConfigDir := false
flaggy.Bool(&printConfigDir, "cd", "print-config-dir", "Print the config directory")
useConfigDir := ""
flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory")
workTree := ""
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
gitDir := ""
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
customConfigFile := ""
flaggy.String(&customConfigFile, "ucf", "use-config-file", "Comma separated list to custom config file(s)")
flaggy.Parse()
if os.Getenv("DEBUG") == "TRUE" {
debug = true
}
return &cliArgs{
RepoPath: repoPath,
FilterPath: filterPath,
GitArg: gitArg,
PrintVersionInfo: printVersionInfo,
Debug: debug,
TailLogs: tailLogs,
PrintDefaultConfig: printDefaultConfig,
PrintConfigDir: printConfigDir,
UseConfigDir: useConfigDir,
WorkTree: workTree,
GitDir: gitDir,
CustomConfigFile: customConfigFile,
}
}
func parseGitArg(gitArg string) appTypes.GitArg {
typedArg := appTypes.GitArg(gitArg)
// using switch so that linter catches when a new git arg value is defined but not handled here
switch typedArg {
case appTypes.GitArgNone, appTypes.GitArgStatus, appTypes.GitArgBranch, appTypes.GitArgLog, appTypes.GitArgStash:
return typedArg
}
permittedValues := []string{
string(appTypes.GitArgStatus),
string(appTypes.GitArgBranch),
string(appTypes.GitArgLog),
string(appTypes.GitArgStash),
}
log.Fatalf("Invalid git arg value: '%s'. Must be one of the following values: %s. e.g. 'lazygit status'. See 'lazygit --help'.",
gitArg,
strings.Join(permittedValues, ", "),
)
panic("unreachable")
}
// the buildInfo struct we get passed in is based on what's baked into the lazygit
// binary via the LDFLAGS argument. Some lazygit distributions will make use of these
// arguments and some will not. Go recently started baking in build info
// into the binary by default e.g. the git commit hash. So in this function
// we merge the two together, giving priority to the stuff set by LDFLAGS.
// Note: this mutates the argument passed in
func mergeBuildInfo(buildInfo *BuildInfo) {
// if the version has already been set by build flags then we'll honour that.
// chances are it's something like v0.31.0 which is more informative than a
// commit hash.
if buildInfo.Version != "" {
return
}
buildInfo.Version = "unversioned"
goBuildInfo, ok := debug.ReadBuildInfo()
if !ok {
return
}
revision, ok := lo.Find(goBuildInfo.Settings, func(setting debug.BuildSetting) bool {
return setting.Key == "vcs.revision"
})
if ok {
buildInfo.Commit = revision.Value
// if lazygit was built from source we'll show the version as the
// abbreviated commit hash
buildInfo.Version = utils.ShortSha(revision.Value)
}
// if version hasn't been set we assume that neither has the date
time, ok := lo.Find(goBuildInfo.Settings, func(setting debug.BuildSetting) bool {
return setting.Key == "vcs.time"
})
if ok {
buildInfo.Date = time.Value
}
}
func getGitVersionInfo() string {
cmd := secureexec.Command("git", "--version")
stdout, _ := cmd.Output()
gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n")
return gitVersion
}

View File

@@ -1,7 +1,7 @@
package app
import (
"io/ioutil"
"io"
"log"
"os"
@@ -11,7 +11,7 @@ import (
func newLogger(config config.AppConfigurer) *logrus.Entry {
var log *logrus.Logger
if config.GetDebug() || os.Getenv("DEBUG") == "TRUE" {
if config.GetDebug() {
log = newDevelopmentLogger()
} else {
log = newProductionLogger()
@@ -21,17 +21,12 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
// https://github.com/aybabtme/humanlog
log.Formatter = &logrus.JSONFormatter{}
return log.WithFields(logrus.Fields{
"debug": config.GetDebug(),
"version": config.GetVersion(),
"commit": config.GetCommit(),
"buildDate": config.GetBuildDate(),
})
return log.WithFields(logrus.Fields{})
}
func newProductionLogger() *logrus.Logger {
log := logrus.New()
log.Out = ioutil.Discard
log.Out = io.Discard
log.SetLevel(logrus.ErrorLevel)
return log
}

View File

@@ -1,4 +1,8 @@
package types
package app
import (
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
)
// StartArgs is the struct that represents some things we want to do on program start
type StartArgs struct {
@@ -6,6 +10,8 @@ type StartArgs struct {
FilterPath string
// GitArg determines what context we open in
GitArg GitArg
// integration test (only relevant when invoking lazygit in the context of an integration test)
IntegrationTest integrationTypes.IntegrationTest
}
type GitArg string
@@ -18,9 +24,10 @@ const (
GitArgStash GitArg = "stash"
)
func NewStartArgs(filterPath string, gitArg GitArg) StartArgs {
func NewStartArgs(filterPath string, gitArg GitArg, test integrationTypes.IntegrationTest) StartArgs {
return StartArgs{
FilterPath: filterPath,
GitArg: gitArg,
FilterPath: filterPath,
GitArg: gitArg,
IntegrationTest: test,
}
}

View File

@@ -3,7 +3,6 @@ package cheatsheet
import (
"fmt"
"io/fs"
"io/ioutil"
"log"
"os"
"path/filepath"
@@ -13,7 +12,7 @@ import (
)
func Check() {
dir := GetDir()
dir := GetKeybindingsDir()
tmpDir := filepath.Join(os.TempDir(), "lazygit_cheatsheet")
err := os.RemoveAll(tmpDir)
if err != nil {
@@ -60,7 +59,7 @@ func obtainContent(dir string) string {
content := ""
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if re.MatchString(path) {
bytes, err := ioutil.ReadFile(path)
bytes, err := os.ReadFile(path)
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}

View File

@@ -15,13 +15,12 @@ import (
"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"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/integration"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -45,8 +44,8 @@ func CommandToRun() string {
return "go run scripts/cheatsheet/main.go generate"
}
func GetDir() string {
return integration.GetRootDirectory() + "/docs/keybindings"
func GetKeybindingsDir() string {
return utils.GetLazyRootDirectory() + "/docs/keybindings"
}
func generateAtDir(cheatsheetDir string) {
@@ -76,7 +75,7 @@ func generateAtDir(cheatsheetDir string) {
}
func Generate() {
generateAtDir(GetDir())
generateAtDir(GetKeybindingsDir())
}
func writeString(file *os.File, str string) {
@@ -105,10 +104,9 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
"commits": tr.CommitsTitle,
"confirmation": tr.ConfirmationTitle,
"information": tr.InformationTitle,
"main": tr.MainTitle,
"main": tr.NormalTitle,
"patchBuilding": tr.PatchBuildingTitle,
"merging": tr.MergingTitle,
"normal": tr.NormalTitle,
"mergeConflicts": tr.MergingTitle,
"staging": tr.StagingTitle,
"menu": tr.MenuTitle,
"search": tr.SearchTitle,
@@ -127,19 +125,24 @@ 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 {
return binding.Description != "" || binding.Alternative != ""
if lo.Contains(excludedViews, binding.ViewName) {
return false
}
return (binding.Description != "" || binding.Alternative != "") && binding.Key != nil
})
bindingsByHeader := utils.MuiltiGroupBy(bindingsToDisplay, func(binding *types.Binding) []header {
return getHeaders(binding, tr)
bindingsByHeader := lo.GroupBy(bindingsToDisplay, func(binding *types.Binding) header {
return getHeader(binding, tr)
})
bindingGroups := maps.MapToSlice(
bindingsByHeader,
func(header header, hBindings []*types.Binding) headerWithBindings {
uniqBindings := lo.UniqBy(hBindings, func(binding *types.Binding) string {
return binding.Description + keybindings.GetKeyDisplay(binding.Key)
return binding.Description + keybindings.LabelFromKey(binding.Key)
})
return headerWithBindings{
@@ -164,24 +167,16 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
})
}
// a binding may belong to multiple headers if it is applicable to multiple contexts,
// for example the copy-to-clipboard binding.
func getHeaders(binding *types.Binding, tr *i18n.TranslationSet) []header {
func getHeader(binding *types.Binding, tr *i18n.TranslationSet) header {
if binding.Tag == "navigation" {
return []header{{priority: 2, title: localisedTitle(tr, "navigation")}}
return header{priority: 2, title: localisedTitle(tr, "navigation")}
}
if binding.ViewName == "" {
return []header{{priority: 3, title: localisedTitle(tr, "global")}}
return header{priority: 3, title: localisedTitle(tr, "global")}
}
if len(binding.Contexts) == 0 {
return []header{}
}
return slices.Map(binding.Contexts, func(context string) header {
return header{priority: 1, title: localisedTitle(tr, context)}
})
return header{priority: 1, title: localisedTitle(tr, binding.ViewName)}
}
func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string {
@@ -207,10 +202,10 @@ func formatBinding(binding *types.Binding) string {
if binding.Alternative != "" {
return fmt.Sprintf(
" <kbd>%s</kbd>: %s (%s)\n",
keybindings.GetKeyDisplay(binding.Key),
keybindings.LabelFromKey(binding.Key),
binding.Description,
binding.Alternative,
)
}
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", keybindings.GetKeyDisplay(binding.Key), binding.Description)
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", keybindings.LabelFromKey(binding.Key), binding.Description)
}

View File

@@ -26,8 +26,8 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
Key: 'a',
},
},
expected: []*bindingSection{
@@ -36,8 +36,8 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
Key: 'a',
},
},
},
@@ -49,6 +49,7 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "",
Description: "quit",
Key: 'a',
},
},
expected: []*bindingSection{
@@ -58,6 +59,7 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "",
Description: "quit",
Key: 'a',
},
},
},
@@ -69,17 +71,17 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Contexts: []string{"files"},
Key: 'a',
},
{
ViewName: "files",
Description: "unstage file",
Contexts: []string{"files"},
Key: 'a',
},
{
ViewName: "files",
ViewName: "submodules",
Description: "drop submodule",
Contexts: []string{"submodules"},
Key: 'a',
},
},
expected: []*bindingSection{
@@ -89,12 +91,12 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Contexts: []string{"files"},
Key: 'a',
},
{
ViewName: "files",
Description: "unstage file",
Contexts: []string{"files"},
Key: 'a',
},
},
},
@@ -102,9 +104,9 @@ func TestGetBindingSections(t *testing.T) {
title: "Submodules",
bindings: []*types.Binding{
{
ViewName: "files",
ViewName: "submodules",
Description: "drop submodule",
Contexts: []string{"submodules"},
Key: 'a',
},
},
},
@@ -115,24 +117,24 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
Key: 'a',
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "unstage file",
Key: 'a',
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "scroll",
Key: 'a',
Tag: "navigation",
},
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "revert commit",
Key: 'a',
},
},
expected: []*bindingSection{
@@ -141,8 +143,8 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "scroll",
Key: 'a',
Tag: "navigation",
},
},
@@ -152,8 +154,8 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "revert commit",
Key: 'a',
},
},
},
@@ -162,13 +164,13 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
Key: 'a',
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "unstage file",
Key: 'a',
},
},
},
@@ -179,35 +181,35 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
Key: 'a',
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "unstage file",
Key: 'a',
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "scroll",
Key: 'a',
Tag: "navigation",
},
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "revert commit",
Key: 'a',
},
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "scroll",
Key: 'a',
Tag: "navigation",
},
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "page up",
Key: 'a',
Tag: "navigation",
},
},
@@ -217,14 +219,14 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "scroll",
Key: 'a',
Tag: "navigation",
},
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "page up",
Key: 'a',
Tag: "navigation",
},
},
@@ -234,8 +236,8 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "revert commit",
Key: 'a',
},
},
},
@@ -244,13 +246,13 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
Key: 'a',
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "unstage file",
Key: 'a',
},
},
},

View File

@@ -1,18 +1,16 @@
package commands
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"github.com/go-errors/errors"
"github.com/sasha-s/go-deadlock"
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/common"
@@ -43,21 +41,22 @@ type GitCommand struct {
}
type Loaders struct {
Branches *loaders.BranchLoader
CommitFiles *loaders.CommitFileLoader
Commits *loaders.CommitLoader
Files *loaders.FileLoader
ReflogCommits *loaders.ReflogCommitLoader
Remotes *loaders.RemoteLoader
Stash *loaders.StashLoader
Tags *loaders.TagLoader
BranchLoader *git_commands.BranchLoader
CommitFileLoader *git_commands.CommitFileLoader
CommitLoader *git_commands.CommitLoader
FileLoader *git_commands.FileLoader
ReflogCommitLoader *git_commands.ReflogCommitLoader
RemoteLoader *git_commands.RemoteLoader
StashLoader *git_commands.StashLoader
TagLoader *git_commands.TagLoader
}
func NewGitCommand(
cmn *common.Common,
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
syncMutex *sync.Mutex,
syncMutex *deadlock.Mutex,
) (*GitCommand, error) {
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
return nil, err
@@ -68,13 +67,14 @@ func NewGitCommand(
return nil, err
}
dotGitDir, err := findDotGitDir(os.Stat, ioutil.ReadFile)
dotGitDir, err := findDotGitDir(os.Stat, os.ReadFile)
if err != nil {
return nil, err
}
return NewGitCommandAux(
cmn,
version,
osCommand,
gitConfig,
dotGitDir,
@@ -85,11 +85,12 @@ func NewGitCommand(
func NewGitCommandAux(
cmn *common.Common,
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
dotGitDir string,
repo *gogit.Repository,
syncMutex *sync.Mutex,
syncMutex *deadlock.Mutex,
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
@@ -99,10 +100,11 @@ func NewGitCommandAux(
// on the one struct.
// common ones are: cmn, osCommand, dotGitDir, configCommands
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
gitCommon := git_commands.NewGitCommon(cmn, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex)
fileLoader := git_commands.NewFileLoader(cmn, cmd, configCommands)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex)
statusCommands := git_commands.NewStatusCommands(gitCommon)
fileLoader := loaders.NewFileLoader(cmn, cmd, configCommands)
flowCommands := git_commands.NewFlowCommands(gitCommon)
remoteCommands := git_commands.NewRemoteCommands(gitCommon)
branchCommands := git_commands.NewBranchCommands(gitCommon)
@@ -120,6 +122,14 @@ func NewGitCommandAux(
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchManager)
bisectCommands := git_commands.NewBisectCommands(gitCommon)
branchLoader := git_commands.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchInfo, configCommands)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchInfo, statusCommands.RebaseMode)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
stashLoader := git_commands.NewStashLoader(cmn, cmd)
tagLoader := git_commands.NewTagLoader(cmn, version, cmd)
return &GitCommand{
Branch: branchCommands,
Commit: commitCommands,
@@ -138,14 +148,14 @@ func NewGitCommandAux(
Bisect: bisectCommands,
WorkingTree: workingTreeCommands,
Loaders: Loaders{
Branches: loaders.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchName, configCommands),
CommitFiles: loaders.NewCommitFileLoader(cmn, cmd),
Commits: loaders.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchName, statusCommands.RebaseMode),
Files: fileLoader,
ReflogCommits: loaders.NewReflogCommitLoader(cmn, cmd),
Remotes: loaders.NewRemoteLoader(cmn, cmd, repo.Remotes),
Stash: loaders.NewStashLoader(cmn, cmd),
Tags: loaders.NewTagLoader(cmn, cmd),
BranchLoader: branchLoader,
CommitFileLoader: commitFileLoader,
CommitLoader: commitLoader,
FileLoader: fileLoader,
ReflogCommitLoader: reflogCommitLoader,
RemoteLoader: remoteLoader,
StashLoader: stashLoader,
TagLoader: tagLoader,
},
}
}

View File

@@ -21,6 +21,10 @@ func (self *gitCmdObjRunner) RunWithOutput(cmdObj oscommands.ICmdObj) (string, e
return self.innerRunner.RunWithOutput(cmdObj)
}
func (self *gitCmdObjRunner) RunWithOutputs(cmdObj oscommands.ICmdObj) (string, string, error) {
return self.innerRunner.RunWithOutputs(cmdObj)
}
func (self *gitCmdObjRunner) RunAndProcessLines(cmdObj oscommands.ICmdObj, onLine func(line string) (bool, error)) error {
return self.innerRunner.RunAndProcessLines(cmdObj, onLine)
}

View File

@@ -2,19 +2,12 @@ package git_commands
import (
"fmt"
"regexp"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// this takes something like:
// * (HEAD detached at 264fc6f5)
// remotes
// and returns '264fc6f5' as the second match
const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$`
type BranchCommands struct {
*GitCommon
}
@@ -30,29 +23,38 @@ func (self *BranchCommands) New(name string, base string) error {
return self.cmd.New(fmt.Sprintf("git checkout -b %s %s", self.cmd.Quote(name), self.cmd.Quote(base))).Run()
}
// CurrentBranchName get the current branch name and displayname.
// the first returned string is the name and the second is the displayname
// e.g. name is 123asdf and displayname is '(HEAD detached at 123asdf)'
func (self *BranchCommands) CurrentBranchName() (string, string, error) {
// CurrentBranchInfo get the current branch information.
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
branchName, err := self.cmd.New("git symbolic-ref --short HEAD").DontLog().RunWithOutput()
if err == nil && branchName != "HEAD\n" {
trimmedBranchName := strings.TrimSpace(branchName)
return trimmedBranchName, trimmedBranchName, nil
return BranchInfo{
RefName: trimmedBranchName,
DisplayName: trimmedBranchName,
DetachedHead: false,
}, nil
}
output, err := self.cmd.New("git branch --contains").DontLog().RunWithOutput()
output, err := self.cmd.New(`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`).DontLog().RunWithOutput()
if err != nil {
return "", "", err
return BranchInfo{}, err
}
for _, line := range utils.SplitLines(output) {
re := regexp.MustCompile(CurrentBranchNameRegex)
match := re.FindStringSubmatch(line)
if len(match) > 0 {
branchName = match[1]
displayBranchName := match[0][2:]
return branchName, displayBranchName, nil
split := strings.Split(strings.TrimRight(line, "\r\n"), "\x00")
if len(split) == 3 && split[0] == "*" {
sha := split[1]
displayName := split[2]
return BranchInfo{
RefName: sha,
DisplayName: displayName,
DetachedHead: true,
}, nil
}
}
return "HEAD", "HEAD", nil
return BranchInfo{
RefName: "HEAD",
DisplayName: "HEAD",
DetachedHead: true,
}, nil
}
// Delete delete branch

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"regexp"
@@ -27,24 +27,30 @@ type BranchLoaderConfigCommands interface {
Branches() (map[string]*config.Branch, error)
}
type BranchInfo struct {
RefName string
DisplayName string // e.g. '(HEAD detached at 123asdf)'
DetachedHead bool
}
// BranchLoader returns a list of Branch objects for the current repo
type BranchLoader struct {
*common.Common
getRawBranches func() (string, error)
getCurrentBranchName func() (string, string, error)
getCurrentBranchInfo func() (BranchInfo, error)
config BranchLoaderConfigCommands
}
func NewBranchLoader(
cmn *common.Common,
getRawBranches func() (string, error),
getCurrentBranchName func() (string, string, error),
getCurrentBranchInfo func() (BranchInfo, error),
config BranchLoaderConfigCommands,
) *BranchLoader {
return &BranchLoader{
Common: cmn,
getRawBranches: getRawBranches,
getCurrentBranchName: getCurrentBranchName,
getCurrentBranchInfo: getCurrentBranchInfo,
config: config,
}
}
@@ -84,11 +90,11 @@ outer:
}
}
if !foundHead {
currentBranchName, currentBranchDisplayName, err := self.getCurrentBranchName()
info, err := self.getCurrentBranchInfo()
if err != nil {
return nil, err
}
branches = slices.Prepend(branches, &models.Branch{Name: currentBranchName, DisplayName: currentBranchDisplayName, Head: true, Recency: " *"})
branches = slices.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
}
configBranches, err := self.config.Branches()

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
// "*|feat/detect-purge|origin/feat/detect-purge|[ahead 1]"
import (

View File

@@ -53,10 +53,10 @@ func TestBranchGetCommitDifferences(t *testing.T) {
func TestBranchNewBranch(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
Expect(`git checkout -b "test" "master"`, "", nil)
Expect(`git checkout -b "test" "refs/heads/master"`, "", nil)
instance := buildBranchCommands(commonDeps{runner: runner})
assert.NoError(t, instance.New("test", "master"))
assert.NoError(t, instance.New("test", "refs/heads/master"))
runner.CheckForMissingCalls()
}
@@ -162,54 +162,62 @@ func TestBranchGetAllBranchGraph(t *testing.T) {
assert.NoError(t, err)
}
func TestBranchCurrentBranchName(t *testing.T) {
func TestBranchCurrentBranchInfo(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
test func(string, string, error)
test func(BranchInfo, error)
}
scenarios := []scenario{
{
"says we are on the master branch if we are",
oscommands.NewFakeRunner(t).Expect(`git symbolic-ref --short HEAD`, "master", nil),
func(name string, displayname string, err error) {
func(info BranchInfo, err error) {
assert.NoError(t, err)
assert.EqualValues(t, "master", name)
assert.EqualValues(t, "master", displayname)
assert.EqualValues(t, "master", info.RefName)
assert.EqualValues(t, "master", info.DisplayName)
assert.False(t, info.DetachedHead)
},
},
{
"falls back to git `git branch --contains` if symbolic-ref fails",
"falls back to git `git branch --points-at=HEAD` if symbolic-ref fails",
oscommands.NewFakeRunner(t).
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
Expect(`git branch --contains`, "* master", nil),
func(name string, displayname string, err error) {
Expect(`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`, "*\x006f71c57a8d4bd6c11399c3f55f42c815527a73a4\x00(HEAD detached at 6f71c57a)\n", nil),
func(info BranchInfo, err error) {
assert.NoError(t, err)
assert.EqualValues(t, "master", name)
assert.EqualValues(t, "master", displayname)
assert.EqualValues(t, "6f71c57a8d4bd6c11399c3f55f42c815527a73a4", info.RefName)
assert.EqualValues(t, "(HEAD detached at 6f71c57a)", info.DisplayName)
assert.True(t, info.DetachedHead)
},
},
{
"handles a detached head",
"handles a detached head (LANG=zh_CN.UTF-8)",
oscommands.NewFakeRunner(t).
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
Expect(`git branch --contains`, "* (HEAD detached at 123abcd)", nil),
func(name string, displayname string, err error) {
Expect(
`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`,
"*\x00679b0456f3db7c505b398def84e7d023e5b55a8d\x00头指针在 679b0456 分离)\n"+
" \x00679b0456f3db7c505b398def84e7d023e5b55a8d\x00refs/heads/master\n",
nil),
func(info BranchInfo, err error) {
assert.NoError(t, err)
assert.EqualValues(t, "123abcd", name)
assert.EqualValues(t, "(HEAD detached at 123abcd)", displayname)
assert.EqualValues(t, "679b0456f3db7c505b398def84e7d023e5b55a8d", info.RefName)
assert.EqualValues(t, "(头指针在 679b0456 分离)", info.DisplayName)
assert.True(t, info.DetachedHead)
},
},
{
"bubbles up error if there is one",
oscommands.NewFakeRunner(t).
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
Expect(`git branch --contains`, "", errors.New("error")),
func(name string, displayname string, err error) {
Expect(`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`, "", errors.New("error")),
func(info BranchInfo, err error) {
assert.Error(t, err)
assert.EqualValues(t, "", name)
assert.EqualValues(t, "", displayname)
assert.EqualValues(t, "", info.RefName)
assert.EqualValues(t, "", info.DisplayName)
assert.False(t, info.DetachedHead)
},
},
}
@@ -218,7 +226,7 @@ func TestBranchCurrentBranchName(t *testing.T) {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner})
s.test(instance.CurrentBranchName())
s.test(instance.CurrentBranchInfo())
s.runner.CheckForMissingCalls()
})
}

View File

@@ -62,7 +62,7 @@ func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj {
// runs git commit without the -m argument meaning it will invoke the user's editor
func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
return self.cmd.New(fmt.Sprintf("git commit%s", self.signoffFlag()))
return self.cmd.New(fmt.Sprintf("git commit%s%s", self.signoffFlag(), self.verboseFlag()))
}
func (self *CommitCommands) signoffFlag() string {
@@ -73,6 +73,17 @@ func (self *CommitCommands) signoffFlag() string {
}
}
func (self *CommitCommands) verboseFlag() string {
switch self.config.UserConfig.Git.Commit.Verbose {
case "always":
return " --verbose"
case "never":
return " --no-verbose"
default:
return ""
}
}
// Get the subject of the HEAD commit
func (self *CommitCommands) GetHeadCommitMessage() (string, error) {
message, err := self.cmd.New("git log -1 --pretty=%s").DontLog().RunWithOutput()

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"fmt"

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"testing"

View File

@@ -1,9 +1,8 @@
package loaders
package git_commands
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
@@ -30,7 +29,7 @@ type CommitLoader struct {
*common.Common
cmd oscommands.ICmdObjBuilder
getCurrentBranchName func() (string, string, error)
getCurrentBranchInfo func() (BranchInfo, error)
getRebaseMode func() (enums.RebaseMode, error)
readFile func(filename string) ([]byte, error)
walkFiles func(root string, fn filepath.WalkFunc) error
@@ -42,15 +41,15 @@ func NewCommitLoader(
cmn *common.Common,
cmd oscommands.ICmdObjBuilder,
dotGitDir string,
getCurrentBranchName func() (string, string, error),
getCurrentBranchInfo func() (BranchInfo, error),
getRebaseMode func() (enums.RebaseMode, error),
) *CommitLoader {
return &CommitLoader{
Common: cmn,
cmd: cmd,
getCurrentBranchName: getCurrentBranchName,
getCurrentBranchInfo: getCurrentBranchInfo,
getRebaseMode: getRebaseMode,
readFile: ioutil.ReadFile,
readFile: os.ReadFile,
walkFiles: filepath.Walk,
dotGitDir: dotGitDir,
}
@@ -372,13 +371,13 @@ func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*mod
}
func (self *CommitLoader) getMergeBase(refName string) (string, error) {
currentBranch, _, err := self.getCurrentBranchName()
info, err := self.getCurrentBranchInfo()
if err != nil {
return "", err
}
baseBranch := "master"
if strings.HasPrefix(currentBranch, "feature/") {
if strings.HasPrefix(info.RefName, "feature/") {
baseBranch = "develop"
}
@@ -427,7 +426,10 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
config := self.UserConfig.Git.Log
orderFlag := "--" + config.Order
orderFlag := ""
if config.Order != "default" {
orderFlag = " --" + config.Order
}
allFlag := ""
if opts.All {
allFlag = " --all"
@@ -435,7 +437,7 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
return self.cmd.New(
fmt.Sprintf(
"git -c log.showSignature=false log %s %s %s --oneline %s%s --abbrev=%d%s",
"git -c log.showSignature=false log %s%s%s --oneline %s%s --abbrev=%d%s",
self.cmd.Quote(opts.RefName),
orderFlag,
allFlag,
@@ -447,14 +449,4 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
).DontLog()
}
var prettyFormat = fmt.Sprintf(
"--pretty=format:\"%%H%s%%at%s%%aN%s%%ae%s%%d%s%%p%s%%s\"",
NULL_CODE,
NULL_CODE,
NULL_CODE,
NULL_CODE,
NULL_CODE,
NULL_CODE,
)
const NULL_CODE = "%x00"
const prettyFormat = `--pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s"`

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"path/filepath"
@@ -27,6 +27,7 @@ func TestGetCommits(t *testing.T) {
runner *oscommands.FakeCmdObjRunner
expectedCommits []*models.Commit
expectedError error
logOrder string
rebaseMode enums.RebaseMode
currentBranchName string
opts GetCommitsOptions
@@ -35,18 +36,20 @@ func TestGetCommits(t *testing.T) {
scenarios := []scenario{
{
testName: "should return no commits if there are none",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git -c log.showSignature=false log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40`, "", nil),
Expect(`git -c log.showSignature=false log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40`, "", nil),
expectedCommits: []*models.Commit{},
expectedError: nil,
},
{
testName: "should return commits if they are present",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
@@ -54,7 +57,7 @@ func TestGetCommits(t *testing.T) {
// here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
Expect(`git -c log.showSignature=false log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40`, commitsOutput, nil).
Expect(`git -c log.showSignature=false log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40`, commitsOutput, nil).
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
Expect(`git merge-base "HEAD" "master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
@@ -174,16 +177,32 @@ func TestGetCommits(t *testing.T) {
},
expectedError: nil,
},
{
testName: "should not specify order if `log.order` is `default`",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git -c log.showSignature=false log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40`, "", nil),
expectedCommits: []*models.Commit{},
expectedError: nil,
},
}
for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.testName, func(t *testing.T) {
common := utils.NewDummyCommon()
common.UserConfig.Git.Log.Order = scenario.logOrder
builder := &CommitLoader{
Common: utils.NewDummyCommon(),
Common: common,
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
getCurrentBranchName: func() (string, string, error) {
return scenario.currentBranchName, scenario.currentBranchName, nil
getCurrentBranchInfo: func() (BranchInfo, error) {
return BranchInfo{RefName: scenario.currentBranchName, DisplayName: scenario.currentBranchName, DetachedHead: false}, nil
},
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
dotGitDir: ".git",

View File

@@ -27,7 +27,7 @@ func TestCommitResetToCommit(t *testing.T) {
runner.CheckForMissingCalls()
}
func TestCommitCommitObj(t *testing.T) {
func TestCommitCommitCmdObj(t *testing.T) {
type scenario struct {
testName string
message string
@@ -89,6 +89,62 @@ func TestCommitCommitObj(t *testing.T) {
}
}
func TestCommitCommitEditorCmdObj(t *testing.T) {
type scenario struct {
testName string
configSignoff bool
configVerbose string
expected string
}
scenarios := []scenario{
{
testName: "Commit using editor",
configSignoff: false,
configVerbose: "default",
expected: `git commit`,
},
{
testName: "Commit with --no-verbose flag",
configSignoff: false,
configVerbose: "never",
expected: `git commit --no-verbose`,
},
{
testName: "Commit with --verbose flag",
configSignoff: false,
configVerbose: "always",
expected: `git commit --verbose`,
},
{
testName: "Commit with --signoff",
configSignoff: true,
configVerbose: "default",
expected: `git commit --signoff`,
},
{
testName: "Commit with --signoff and --no-verbose",
configSignoff: true,
configVerbose: "never",
expected: `git commit --signoff --no-verbose`,
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
userConfig.Git.Commit.SignOff = s.configSignoff
userConfig.Git.Commit.Verbose = s.configVerbose
instance := buildCommitCommands(commonDeps{userConfig: userConfig})
cmdStr := instance.CommitEditorCmdObj().ToString()
assert.Equal(t, s.expected, cmdStr)
})
}
}
func TestCommitCreateFixupCommit(t *testing.T) {
type scenario struct {
testName string

View File

@@ -1,35 +1,37 @@
package git_commands
import (
"sync"
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 {
*common.Common
version *GitVersion
cmd oscommands.ICmdObjBuilder
os *oscommands.OSCommand
dotGitDir string
repo *gogit.Repository
config *ConfigCommands
// mutex for doing things like push/pull/fetch
syncMutex *sync.Mutex
syncMutex *deadlock.Mutex
}
func NewGitCommon(
cmn *common.Common,
version *GitVersion,
cmd oscommands.ICmdObjBuilder,
osCommand *oscommands.OSCommand,
dotGitDir string,
repo *gogit.Repository,
config *ConfigCommands,
syncMutex *sync.Mutex,
syncMutex *deadlock.Mutex,
) *GitCommon {
return &GitCommon{
Common: cmn,
version: version,
cmd: cmd,
os: osCommand,
dotGitDir: dotGitDir,

View File

@@ -1,10 +1,11 @@
package git_commands
import (
"os"
"github.com/go-errors/errors"
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
@@ -14,6 +15,7 @@ import (
type commonDeps struct {
runner *oscommands.FakeCmdObjRunner
userConfig *config.UserConfig
gitVersion *GitVersion
gitConfig *git_config.FakeGitConfig
getenv func(string) string
removeFile func(string) error
@@ -47,6 +49,11 @@ func buildGitCommon(deps commonDeps) *GitCommon {
gitCommon.Common.UserConfig = config.GetDefaultConfig()
}
gitCommon.version = deps.gitVersion
if gitCommon.version == nil {
gitCommon.version = &GitVersion{2, 0, 0, ""}
}
gitConfig := deps.gitConfig
if gitConfig == nil {
gitConfig = git_config.NewFakeGitConfig(nil)
@@ -70,6 +77,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
GetenvFn: getenv,
Cmd: cmd,
RemoveFileFn: removeFile,
TempDir: os.TempDir(),
})
gitCommon.dotGitDir = deps.dotGitDir
@@ -86,8 +94,8 @@ func buildRepo() *gogit.Repository {
return repo
}
func buildFileLoader(gitCommon *GitCommon) *loaders.FileLoader {
return loaders.NewFileLoader(gitCommon.Common, gitCommon.cmd, gitCommon.config)
func buildFileLoader(gitCommon *GitCommon) *FileLoader {
return NewFileLoader(gitCommon.Common, gitCommon.cmd, gitCommon.config)
}
func buildSubmoduleCommands(deps commonDeps) *SubmoduleCommands {

View File

@@ -1,7 +1,7 @@
package git_commands
import (
"io/ioutil"
"os"
"strconv"
"github.com/go-errors/errors"
@@ -20,7 +20,7 @@ func NewFileCommands(gitCommon *GitCommon) *FileCommands {
// Cat obtains the content of a file
func (self *FileCommands) Cat(fileName string) (string, error) {
buf, err := ioutil.ReadFile(fileName)
buf, err := os.ReadFile(fileName)
if err != nil {
return "", nil
}

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"fmt"
@@ -7,7 +7,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/samber/lo"
)
type FileLoaderConfig interface {
@@ -54,28 +53,15 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
self.Log.Warningf("warning when calling git status: %s", status.StatusString)
continue
}
change := status.Change
stagedChange := change[0:1]
unstagedChange := change[1:2]
untracked := lo.Contains([]string{"??", "A ", "AM"}, change)
hasNoStagedChanges := lo.Contains([]string{" ", "U", "?"}, stagedChange)
hasInlineMergeConflicts := lo.Contains([]string{"UU", "AA"}, change)
hasMergeConflicts := hasInlineMergeConflicts || lo.Contains([]string{"DD", "AU", "UA", "UD", "DU"}, change)
file := &models.File{
Name: status.Name,
PreviousName: status.PreviousName,
DisplayString: status.StatusString,
HasStagedChanges: !hasNoStagedChanges,
HasUnstagedChanges: unstagedChange != " ",
Tracked: !untracked,
Deleted: unstagedChange == "D" || stagedChange == "D",
Added: unstagedChange == "A" || untracked,
HasMergeConflicts: hasMergeConflicts,
HasInlineMergeConflicts: hasInlineMergeConflicts,
Type: self.getFileType(status.Name),
ShortStatus: change,
Name: status.Name,
PreviousName: status.PreviousName,
DisplayString: status.StatusString,
Type: self.getFileType(status.Name),
}
models.SetStatusFields(file, status.Change)
files = append(files, file)
}
@@ -101,7 +87,7 @@ func (c *FileLoader) GitStatus(opts GitStatusOptions) ([]FileStatus, error) {
noRenamesFlag = " --no-renames"
}
statusLines, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutput()
statusLines, _, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutputs()
if err != nil {
return []FileStatus{}, err
}

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"testing"

View File

@@ -2,7 +2,7 @@ package git_commands
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -130,7 +130,7 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseSha string, todo
}
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty %s", baseSha)
self.Log.WithField("command", cmdStr).Info("RunCommand")
self.Log.WithField("command", cmdStr).Debug("RunCommand")
cmdObj := self.cmd.New(cmdStr)
@@ -202,7 +202,7 @@ func (self *RebaseCommands) AmendTo(sha string) error {
// EditRebaseTodo sets the action at a given index in the git-rebase-todo file
func (self *RebaseCommands) EditRebaseTodo(index int, action string) error {
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
bytes, err := ioutil.ReadFile(fileName)
bytes, err := os.ReadFile(fileName)
if err != nil {
return err
}
@@ -217,7 +217,7 @@ func (self *RebaseCommands) EditRebaseTodo(index int, action string) error {
content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ")
result := strings.Join(content, "\n")
return ioutil.WriteFile(fileName, []byte(result), 0o644)
return os.WriteFile(fileName, []byte(result), 0o644)
}
func (self *RebaseCommands) getTodoCommitCount(content []string) int {
@@ -234,7 +234,7 @@ func (self *RebaseCommands) getTodoCommitCount(content []string) int {
// MoveTodoDown moves a rebase todo item down by one position
func (self *RebaseCommands) MoveTodoDown(index int) error {
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
bytes, err := ioutil.ReadFile(fileName)
bytes, err := os.ReadFile(fileName)
if err != nil {
return err
}
@@ -247,7 +247,7 @@ func (self *RebaseCommands) MoveTodoDown(index int) error {
rearrangedContent = append(rearrangedContent, content[contentIndex+1:]...)
result := strings.Join(rearrangedContent, "\n")
return ioutil.WriteFile(fileName, []byte(result), 0o644)
return os.WriteFile(fileName, []byte(result), 0o644)
}
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
@@ -255,7 +255,7 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(sha string) error {
return self.runSkipEditorCommand(
self.cmd.New(
fmt.Sprintf(
"git rebase --interactive --autostash --autosquash %s^",
"git rebase --interactive --rebase-merges --autostash --autosquash %s^",
sha,
),
),

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"fmt"

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"errors"

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"fmt"
@@ -45,7 +45,7 @@ func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
remotes := slices.Map(goGitRemotes, func(goGitRemote *gogit.Remote) *models.Remote {
remoteName := goGitRemote.Config().Name
re := regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%s\/([\S]+)`, remoteName))
re := regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%s\/([\S]+)`, regexp.QuoteMeta(remoteName)))
matches := re.FindAllStringSubmatch(remoteBranchesStr, -1)
branches := slices.Map(matches, func(match []string) *models.RemoteBranch {
return &models.RemoteBranch{

View File

@@ -2,20 +2,20 @@ package git_commands
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)
type StashCommands struct {
*GitCommon
fileLoader *loaders.FileLoader
fileLoader *FileLoader
workingTree *WorkingTreeCommands
}
func NewStashCommands(
gitCommon *GitCommon,
fileLoader *loaders.FileLoader,
fileLoader *FileLoader,
workingTree *WorkingTreeCommands,
) *StashCommands {
return &StashCommands{
@@ -46,6 +46,19 @@ func (self *StashCommands) Save(message string) error {
return self.cmd.New("git stash save " + self.cmd.Quote(message)).Run()
}
func (self *StashCommands) Store(sha string, message string) error {
trimmedMessage := strings.Trim(message, " \t")
if len(trimmedMessage) > 0 {
return self.cmd.New(fmt.Sprintf("git stash store %s -m %s", self.cmd.Quote(sha), self.cmd.Quote(trimmedMessage))).Run()
}
return self.cmd.New(fmt.Sprintf("git stash store %s", self.cmd.Quote(sha))).Run()
}
func (self *StashCommands) Sha(index int) (string, error) {
sha, _, err := self.cmd.New(fmt.Sprintf("git rev-parse refs/stash@{%d}", index)).DontLog().RunWithOutputs()
return strings.Trim(sha, "\r\n"), err
}
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
cmdStr := fmt.Sprintf("git stash show -p --stat --color=%s --unified=%d stash@{%d}", self.UserConfig.Git.Paging.ColorArg, self.UserConfig.Git.DiffContextSize, index)
@@ -97,7 +110,7 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
// meaning it's deleted in your working tree but added in your index. Given that it's
// now safely stashed, we need to remove it.
files := self.fileLoader.
GetStatusFiles(loaders.GetStatusFileOptions{})
GetStatusFiles(GetStatusFileOptions{})
for _, file := range files {
if file.ShortStatus == "AD" {
@@ -109,3 +122,25 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
return nil
}
func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
return self.cmd.New(fmt.Sprintf("git stash save %s --include-untracked", self.cmd.Quote(message))).Run()
}
func (self *StashCommands) Rename(index int, message string) error {
sha, err := self.Sha(index)
if err != nil {
return err
}
if err := self.Drop(index); err != nil {
return err
}
err = self.Store(sha, message)
if err != nil {
return err
}
return nil
}

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"regexp"
@@ -65,8 +65,8 @@ outer:
}
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
rawString, _ := self.cmd.New("git stash list --pretty='%gs'").DontLog().RunWithOutput()
return slices.MapWithIndex(utils.SplitLines(rawString), func(line string, index int) *models.StashEntry {
rawString, _ := self.cmd.New("git stash list -z --pretty='%gs'").DontLog().RunWithOutput()
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
return self.stashEntryFromLine(line, index)
})
}

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"testing"
@@ -22,7 +22,7 @@ func TestGetStashEntries(t *testing.T) {
"No stash entries found",
"",
oscommands.NewFakeRunner(t).
Expect(`git stash list --pretty='%gs'`, "", nil),
Expect(`git stash list -z --pretty='%gs'`, "", nil),
[]*models.StashEntry{},
},
{
@@ -30,8 +30,8 @@ func TestGetStashEntries(t *testing.T) {
"",
oscommands.NewFakeRunner(t).
Expect(
`git stash list --pretty='%gs'`,
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template",
`git stash list -z --pretty='%gs'`,
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00WIP on master: bb86a3f update github template\x00",
nil,
),
[]*models.StashEntry{

View File

@@ -10,7 +10,7 @@ import (
func TestStashDrop(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "drop", "stash@{1}"}, "", nil)
ExpectGitArgs([]string{"stash", "drop", "stash@{1}"}, "Dropped refs/stash@{1} (98e9cca532c37c766107093010c72e26f2c24c04)\n", nil)
instance := buildStashCommands(commonDeps{runner: runner})
assert.NoError(t, instance.Drop(1))
@@ -44,6 +44,59 @@ func TestStashSave(t *testing.T) {
runner.CheckForMissingCalls()
}
func TestStashStore(t *testing.T) {
type scenario struct {
testName string
sha string
message string
expected []string
}
scenarios := []scenario{
{
testName: "Non-empty message",
sha: "0123456789abcdef",
message: "New stash name",
expected: []string{"stash", "store", "0123456789abcdef", "-m", "New stash name"},
},
{
testName: "Empty message",
sha: "0123456789abcdef",
message: "",
expected: []string{"stash", "store", "0123456789abcdef"},
},
{
testName: "Space message",
sha: "0123456789abcdef",
message: " ",
expected: []string{"stash", "store", "0123456789abcdef"},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs(s.expected, "", nil)
instance := buildStashCommands(commonDeps{runner: runner})
assert.NoError(t, instance.Store(s.sha, s.message))
runner.CheckForMissingCalls()
})
}
}
func TestStashSha(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"rev-parse", "refs/stash@{5}"}, "14d94495194651adfd5f070590df566c11d28243\n", nil)
instance := buildStashCommands(commonDeps{runner: runner})
sha, err := instance.Sha(5)
assert.NoError(t, err)
assert.Equal(t, "14d94495194651adfd5f070590df566c11d28243", sha)
runner.CheckForMissingCalls()
}
func TestStashStashEntryCmdObj(t *testing.T) {
type scenario struct {
testName string
@@ -79,3 +132,50 @@ func TestStashStashEntryCmdObj(t *testing.T) {
})
}
}
func TestStashRename(t *testing.T) {
type scenario struct {
testName string
index int
message string
expectedShaCmd []string
shaResult string
expectedDropCmd []string
expectedStoreCmd []string
}
scenarios := []scenario{
{
testName: "Default case",
index: 3,
message: "New message",
expectedShaCmd: []string{"rev-parse", "refs/stash@{3}"},
shaResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
expectedDropCmd: []string{"stash", "drop", "stash@{3}"},
expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd", "-m", "New message"},
},
{
testName: "Empty message",
index: 4,
message: "",
expectedShaCmd: []string{"rev-parse", "refs/stash@{4}"},
shaResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
expectedDropCmd: []string{"stash", "drop", "stash@{4}"},
expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs(s.expectedShaCmd, s.shaResult, nil).
ExpectGitArgs(s.expectedDropCmd, "", nil).
ExpectGitArgs(s.expectedStoreCmd, "", nil)
instance := buildStashCommands(commonDeps{runner: runner})
err := instance.Rename(s.index, s.message)
assert.NoError(t, err)
})
}
}

View File

@@ -2,8 +2,10 @@ package git_commands
import (
"path/filepath"
"strconv"
"strings"
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
)
@@ -49,13 +51,21 @@ func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
return enums.REBASE_MODE_NONE
}
func (self *StatusCommands) IsBareRepo() (bool, error) {
return IsBareRepo(self.os)
}
func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
res, err := osCommand.Cmd.New("git rev-parse --is-bare-repository").DontLog().RunWithOutput()
if err != nil {
return false, err
}
// The command returns output with a newline, so we need to strip
return strconv.ParseBool(strings.TrimSpace(res))
}
// IsInMergeState states whether we are still mid-merge
func (self *StatusCommands) IsInMergeState() (bool, error) {
return self.os.FileExists(filepath.Join(self.dotGitDir, "MERGE_HEAD"))
}
func (self *StatusCommands) IsBareRepo() bool {
// note: could use `git rev-parse --is-bare-repository` if we wanna drop go-git
_, err := self.repo.Worktree()
return err == gogit.ErrIsBareRepository
}

View File

@@ -29,7 +29,11 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error)
cmdStr := "git push"
if opts.Force {
cmdStr += " --force-with-lease"
if self.version.IsOlderThan(2, 30, 0) {
cmdStr += " --force-with-lease"
} else {
cmdStr += " --force-with-lease --force-if-includes"
}
}
if opts.SetUpstream {

View File

@@ -10,6 +10,7 @@ import (
func TestSyncPush(t *testing.T) {
type scenario struct {
testName string
version *GitVersion
opts PushOpts
test func(oscommands.ICmdObj, error)
}
@@ -17,6 +18,7 @@ func TestSyncPush(t *testing.T) {
scenarios := []scenario{
{
testName: "Push with force disabled",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{Force: false},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push")
@@ -25,14 +27,25 @@ func TestSyncPush(t *testing.T) {
},
{
testName: "Push with force enabled",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{Force: true},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease")
assert.NoError(t, err)
},
},
{
testName: "Push with force enabled (>= 2.30.0)",
version: &GitVersion{2, 30, 0, ""},
opts: PushOpts{Force: true},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease --force-if-includes")
assert.NoError(t, err)
},
},
{
testName: "Push with force disabled, upstream supplied",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{
Force: false,
UpstreamRemote: "origin",
@@ -45,6 +58,7 @@ func TestSyncPush(t *testing.T) {
},
{
testName: "Push with force disabled, setting upstream",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{
Force: false,
UpstreamRemote: "origin",
@@ -58,6 +72,7 @@ func TestSyncPush(t *testing.T) {
},
{
testName: "Push with force enabled, setting upstream",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{
Force: true,
UpstreamRemote: "origin",
@@ -69,8 +84,23 @@ func TestSyncPush(t *testing.T) {
assert.NoError(t, err)
},
},
{
testName: "Push with force enabled, setting upstream (>= 2.30.0)",
version: &GitVersion{2, 30, 0, ""},
opts: PushOpts{
Force: true,
UpstreamRemote: "origin",
UpstreamBranch: "master",
SetUpstream: true,
},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), `git push --force-with-lease --force-if-includes --set-upstream "origin" "master"`)
assert.NoError(t, err)
},
},
{
testName: "Push with remote branch but no origin",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{
Force: true,
UpstreamRemote: "",
@@ -87,7 +117,7 @@ func TestSyncPush(t *testing.T) {
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{})
instance := buildSyncCommands(commonDeps{gitVersion: s.version})
s.test(instance.PushCmdObj(s.opts))
})
}

View File

@@ -1,6 +1,8 @@
package loaders
package git_commands
import (
"fmt"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
@@ -10,23 +12,31 @@ import (
type TagLoader struct {
*common.Common
cmd oscommands.ICmdObjBuilder
version *GitVersion
cmd oscommands.ICmdObjBuilder
}
func NewTagLoader(
common *common.Common,
version *GitVersion,
cmd oscommands.ICmdObjBuilder,
) *TagLoader {
return &TagLoader{
Common: common,
cmd: cmd,
Common: common,
version: version,
cmd: cmd,
}
}
func (self *TagLoader) GetTags() ([]*models.Tag, error) {
// get remote branches, sorted by creation date (descending)
// see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt
tagsOutput, err := self.cmd.New(`git tag --list --sort=-creatordate`).DontLog().RunWithOutput()
sortKey := "-creatordate"
if self.version.IsOlderThan(2, 7, 0) {
sortKey = "-v:refname"
}
tagsOutput, err := self.cmd.New(fmt.Sprintf(`git tag --list --sort=%s`, sortKey)).DontLog().RunWithOutput()
if err != nil {
return nil, err
}

View File

@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"testing"
@@ -20,6 +20,7 @@ testtag
func TestGetTags(t *testing.T) {
type scenario struct {
testName string
gitVersion *GitVersion
runner *oscommands.FakeCmdObjRunner
expectedTags []*models.Tag
expectedError error
@@ -27,14 +28,24 @@ func TestGetTags(t *testing.T) {
scenarios := []scenario{
{
testName: "should return no tags if there are none",
testName: "should return no tags if there are none",
gitVersion: &GitVersion{2, 7, 0, ""},
runner: oscommands.NewFakeRunner(t).
Expect(`git tag --list --sort=-creatordate`, "", nil),
expectedTags: []*models.Tag{},
expectedError: nil,
},
{
testName: "should return tags if present",
testName: "should return no tags if there are none (< 2.7.0)",
gitVersion: &GitVersion{2, 6, 7, ""},
runner: oscommands.NewFakeRunner(t).
Expect(`git tag --list --sort=-v:refname`, "", nil),
expectedTags: []*models.Tag{},
expectedError: nil,
},
{
testName: "should return tags if present",
gitVersion: &GitVersion{2, 7, 0, ""},
runner: oscommands.NewFakeRunner(t).
Expect(`git tag --list --sort=-creatordate`, tagsOutput, nil),
expectedTags: []*models.Tag{
@@ -47,15 +58,31 @@ func TestGetTags(t *testing.T) {
},
expectedError: nil,
},
{
testName: "should return tags if present (< 2.7.0)",
gitVersion: &GitVersion{2, 6, 7, ""},
runner: oscommands.NewFakeRunner(t).
Expect(`git tag --list --sort=-v:refname`, tagsOutput, nil),
expectedTags: []*models.Tag{
{Name: "v0.34"},
{Name: "v0.33"},
{Name: "v0.32.2"},
{Name: "v0.32.1"},
{Name: "v0.32"},
{Name: "testtag"},
},
expectedError: nil,
},
}
for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.testName, func(t *testing.T) {
loader := &TagLoader{
Common: utils.NewDummyCommon(),
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
}
loader := NewTagLoader(
utils.NewDummyCommon(),
scenario.gitVersion,
oscommands.NewDummyCmdObjBuilder(scenario.runner),
)
tags, err := loader.GetTags()

View File

@@ -0,0 +1,67 @@
package git_commands
import (
"errors"
"regexp"
"strconv"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)
type GitVersion struct {
Major, Minor, Patch int
Additional string
}
func GetGitVersion(osCommand *oscommands.OSCommand) (*GitVersion, error) {
versionStr, _, err := osCommand.Cmd.New("git --version").RunWithOutputs()
if err != nil {
return nil, err
}
version, err := ParseGitVersion(versionStr)
if err != nil {
return nil, err
}
return version, nil
}
func ParseGitVersion(versionStr string) (*GitVersion, error) {
// versionStr should be something like:
// git version 2.39.0
// git version 2.37.1 (Apple Git-137.1)
re := regexp.MustCompile(`[^\d]+(\d+)(\.\d+)?(\.\d+)?(.*)`)
matches := re.FindStringSubmatch(versionStr)
if len(matches) < 5 {
return nil, errors.New("unexpected git version format: " + versionStr)
}
v := &GitVersion{}
var err error
if v.Major, err = strconv.Atoi(matches[1]); err != nil {
return nil, err
}
if len(matches[2]) > 1 {
if v.Minor, err = strconv.Atoi(matches[2][1:]); err != nil {
return nil, err
}
}
if len(matches[3]) > 1 {
if v.Patch, err = strconv.Atoi(matches[3][1:]); err != nil {
return nil, err
}
}
v.Additional = strings.Trim(matches[4], " \r\n")
return v, nil
}
func (v *GitVersion) IsOlderThan(major, minor, patch int) bool {
actual := v.Major*1000*1000 + v.Minor*1000 + v.Patch
required := major*1000*1000 + minor*1000 + patch
return actual < required
}

View File

@@ -0,0 +1,47 @@
package git_commands
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseGitVersion(t *testing.T) {
scenarios := []struct {
input string
expected GitVersion
}{
{
input: "git version 2.39.0",
expected: GitVersion{Major: 2, Minor: 39, Patch: 0, Additional: ""},
},
{
input: "git version 2.37.1 (Apple Git-137.1)",
expected: GitVersion{Major: 2, Minor: 37, Patch: 1, Additional: "(Apple Git-137.1)"},
},
{
input: "git version 2.37 (Apple Git-137.1)",
expected: GitVersion{Major: 2, Minor: 37, Patch: 0, Additional: "(Apple Git-137.1)"},
},
}
for _, s := range scenarios {
actual, err := ParseGitVersion(s.input)
assert.NoError(t, err)
assert.NotNil(t, actual)
assert.Equal(t, s.expected.Major, actual.Major)
assert.Equal(t, s.expected.Minor, actual.Minor)
assert.Equal(t, s.expected.Patch, actual.Patch)
assert.Equal(t, s.expected.Additional, actual.Additional)
}
}
func TestGitVersionIsOlderThan(t *testing.T) {
assert.False(t, (&GitVersion{2, 0, 0, ""}).IsOlderThan(1, 99, 99))
assert.False(t, (&GitVersion{2, 0, 0, ""}).IsOlderThan(2, 0, 0))
assert.False(t, (&GitVersion{2, 1, 0, ""}).IsOlderThan(2, 0, 9))
assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(2, 1, 0))
assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(3, 0, 0))
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/go-errors/errors"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -18,13 +17,13 @@ import (
type WorkingTreeCommands struct {
*GitCommon
submodule *SubmoduleCommands
fileLoader *loaders.FileLoader
fileLoader *FileLoader
}
func NewWorkingTreeCommands(
gitCommon *GitCommon,
submodule *SubmoduleCommands,
fileLoader *loaders.FileLoader,
fileLoader *FileLoader,
) *WorkingTreeCommands {
return &WorkingTreeCommands{
GitCommon: gitCommon,
@@ -90,7 +89,7 @@ func (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File)
// all files, passing the --no-renames flag and then recursively call the function
// again for the before file and after file.
filesWithoutRenames := self.fileLoader.GetStatusFiles(loaders.GetStatusFileOptions{NoRenames: true})
filesWithoutRenames := self.fileLoader.GetStatusFiles(GetStatusFileOptions{NoRenames: true})
var beforeFile *models.File
var afterFile *models.File
@@ -241,7 +240,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
if cached {
cachedArg = " --cached"
}
if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached {
if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() {
trackedArg = "--no-index -- /dev/null"
}
if plain {

View File

@@ -2,7 +2,7 @@ package git_commands
import (
"fmt"
"io/ioutil"
"os"
"regexp"
"testing"
@@ -432,7 +432,7 @@ func TestWorkingTreeApplyPatch(t *testing.T) {
filename := matches[1]
content, err := ioutil.ReadFile(filename)
content, err := os.ReadFile(filename)
assert.NoError(t, err)
assert.Equal(t, "test", string(content))

View File

@@ -3,7 +3,7 @@ package git_config
import (
"bytes"
"fmt"
"io/ioutil"
"io"
"os/exec"
"strings"
"syscall"
@@ -38,7 +38,7 @@ import (
func runGitConfigCmd(cmd *exec.Cmd) (string, error) {
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = ioutil.Discard
cmd.Stderr = io.Discard
err := cmd.Run()
if exitError, ok := err.(*exec.ExitError); ok {

View File

@@ -3,15 +3,16 @@ package commands
import (
"fmt"
"os"
"sync"
"testing"
"time"
"github.com/go-errors/errors"
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sasha-s/go-deadlock"
"github.com/stretchr/testify/assert"
)
@@ -218,9 +219,10 @@ func TestNewGitCommand(t *testing.T) {
s.setup()
s.test(
NewGitCommand(utils.NewDummyCommon(),
&git_commands.GitVersion{},
oscommands.NewDummyOSCommand(),
git_config.NewFakeGitConfig(nil),
&sync.Mutex{},
&deadlock.Mutex{},
))
})
}

View File

@@ -3,7 +3,7 @@ package hosting_service
// if you want to make a custom regex for a given service feel free to test it out
// at regoio.herokuapp.com
var defaultUrlRegexStrings = []string{
`^(?:https?|ssh)://.*/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
`^(?:https?|ssh)://[^/]+/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
`^git@.*:(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
}
var defaultRepoURLTemplate = "https://{{.webDomain}}/{{.owner}}/{{.repo}}"
@@ -24,8 +24,11 @@ var bitbucketServiceDef = ServiceDefinition{
pullRequestURLIntoDefaultBranch: "/pull-requests/new?source={{.From}}&t=1",
pullRequestURLIntoTargetBranch: "/pull-requests/new?source={{.From}}&dest={{.To}}&t=1",
commitURL: "/commits/{{.CommitSha}}",
regexStrings: defaultUrlRegexStrings,
repoURLTemplate: defaultRepoURLTemplate,
regexStrings: []string{
`^(?:https?|ssh)://.*/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
`^.*@.*:(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
},
repoURLTemplate: defaultRepoURLTemplate,
}
var gitLabServiceDef = ServiceDefinition{

View File

@@ -47,6 +47,15 @@ func TestGetPullRequestURL(t *testing.T) {
assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Fsum-operation?expand=1", url)
},
},
{
testName: "Opens a link to new pull request on github with https remote url",
from: "feature/sum-operation",
remoteUrl: "https://github.com/peter/calculator.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Fsum-operation?expand=1", url)
},
},
{
testName: "Opens a link to new pull request on bitbucket with specific target branch",
from: "feature/profile-page/avatar",
@@ -77,6 +86,16 @@ func TestGetPullRequestURL(t *testing.T) {
assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Foperations...feature%2Fsum-operation?expand=1", url)
},
},
{
testName: "Opens a link to new pull request on github with https remote url with specific target branch",
from: "feature/sum-operation",
to: "feature/operations",
remoteUrl: "https://github.com/peter/calculator.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Foperations...feature%2Fsum-operation?expand=1", url)
},
},
{
testName: "Opens a link to new pull request on gitlab",
from: "feature/ui",
@@ -95,6 +114,15 @@ func TestGetPullRequestURL(t *testing.T) {
assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature%2Fui", url)
},
},
{
testName: "Opens a link to new pull request on gitlab with https remote url in nested groups",
from: "feature/ui",
remoteUrl: "https://gitlab.com/peter/public/calculator.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature%2Fui", url)
},
},
{
testName: "Opens a link to new pull request on gitlab with specific target branch",
from: "feature/commit-ui",
@@ -115,6 +143,25 @@ func TestGetPullRequestURL(t *testing.T) {
assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature%2Fcommit-ui&merge_request[target_branch]=epic%2Fui", url)
},
},
{
testName: "Opens a link to new pull request on gitlab with https remote url with specific target branch in nested groups",
from: "feature/commit-ui",
to: "epic/ui",
remoteUrl: "https://gitlab.com/peter/public/calculator.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature%2Fcommit-ui&merge_request[target_branch]=epic%2Fui", url)
},
},
{
testName: "Opens a link to new pull request on bitbucket with a custom SSH username",
from: "feature/profile-page",
remoteUrl: "john@bitbucket.org:johndoe/social_network.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page&t=1", url)
},
},
{
testName: "Opens a link to new pull request on Azure DevOps (SSH)",
from: "feature/new",

View File

@@ -11,6 +11,7 @@ type Branch struct {
Pullables string
UpstreamGone bool
Head bool
DetachedHead bool
// if we have a named remote locally this will be the name of that remote e.g.
// 'origin' or 'tiwood'. If we don't have the remote locally it'll look like
// 'git@github.com:tiwood/lazygit.git'
@@ -19,6 +20,9 @@ type Branch struct {
}
func (b *Branch) FullRefName() string {
if b.DetachedHead {
return b.Name
}
return "refs/heads/" + b.Name
}

View File

@@ -2,6 +2,7 @@ package models
import (
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
// File : A file from git status
@@ -28,6 +29,7 @@ type IFile interface {
GetIsTracked() bool
GetPath() string
GetPreviousPath() string
GetIsFile() bool
}
func (f *File) IsRename() bool {
@@ -43,7 +45,7 @@ func (f *File) Names() []string {
return result
}
// returns true if the file names are the same or if a a file rename includes the filename of the other
// returns true if the file names are the same or if a file rename includes the filename of the other
func (f *File) Matches(f2 *File) bool {
return utils.StringArraysOverlap(f.Names(), f2.Names())
}
@@ -90,3 +92,52 @@ func (f *File) GetPath() string {
func (f *File) GetPreviousPath() string {
return f.PreviousName
}
func (f *File) GetIsFile() bool {
return true
}
type StatusFields struct {
HasStagedChanges bool
HasUnstagedChanges bool
Tracked bool
Deleted bool
Added bool
HasMergeConflicts bool
HasInlineMergeConflicts bool
ShortStatus string
}
func SetStatusFields(file *File, shortStatus string) {
derived := deriveStatusFields(shortStatus)
file.HasStagedChanges = derived.HasStagedChanges
file.HasUnstagedChanges = derived.HasUnstagedChanges
file.Tracked = derived.Tracked
file.Deleted = derived.Deleted
file.Added = derived.Added
file.HasMergeConflicts = derived.HasMergeConflicts
file.HasInlineMergeConflicts = derived.HasInlineMergeConflicts
file.ShortStatus = derived.ShortStatus
}
// shortStatus is something like '??' or 'A '
func deriveStatusFields(shortStatus string) StatusFields {
stagedChange := shortStatus[0:1]
unstagedChange := shortStatus[1:2]
tracked := !lo.Contains([]string{"??", "A ", "AM"}, shortStatus)
hasStagedChanges := !lo.Contains([]string{" ", "U", "?"}, stagedChange)
hasInlineMergeConflicts := lo.Contains([]string{"UU", "AA"}, shortStatus)
hasMergeConflicts := hasInlineMergeConflicts || lo.Contains([]string{"DD", "AU", "UA", "UD", "DU"}, shortStatus)
return StatusFields{
HasStagedChanges: hasStagedChanges,
HasUnstagedChanges: unstagedChange != " ",
Tracked: tracked,
Deleted: unstagedChange == "D" || stagedChange == "D",
Added: unstagedChange == "A" || !tracked,
HasMergeConflicts: hasMergeConflicts,
HasInlineMergeConflicts: hasInlineMergeConflicts,
ShortStatus: shortStatus,
}
}

View File

@@ -2,7 +2,8 @@ package oscommands
import (
"os/exec"
"sync"
"github.com/sasha-s/go-deadlock"
)
// A command object is a general way to represent a command to be run on the
@@ -21,6 +22,8 @@ type ICmdObj interface {
Run() error
// runs the command and returns the output as a string, and an error if any
RunWithOutput() (string, error)
// runs the command and returns stdout and stderr as a string, and an error if any
RunWithOutputs() (string, string, error)
// runs the command and runs a callback function on each line of the output. If the callback returns true for the boolean value, we kill the process and return.
RunAndProcessLines(onLine func(line string) (bool, error)) error
@@ -51,8 +54,8 @@ type ICmdObj interface {
PromptOnCredentialRequest() ICmdObj
FailOnCredentialRequest() ICmdObj
WithMutex(mutex *sync.Mutex) ICmdObj
Mutex() *sync.Mutex
WithMutex(mutex *deadlock.Mutex) ICmdObj
Mutex() *deadlock.Mutex
GetCredentialStrategy() CredentialStrategy
}
@@ -76,7 +79,7 @@ type CmdObj struct {
credentialStrategy CredentialStrategy
// can be set so that we don't run certain commands simultaneously
mutex *sync.Mutex
mutex *deadlock.Mutex
}
type CredentialStrategy int
@@ -139,11 +142,11 @@ func (self *CmdObj) IgnoreEmptyError() ICmdObj {
return self
}
func (self *CmdObj) Mutex() *sync.Mutex {
func (self *CmdObj) Mutex() *deadlock.Mutex {
return self.mutex
}
func (self *CmdObj) WithMutex(mutex *sync.Mutex) ICmdObj {
func (self *CmdObj) WithMutex(mutex *deadlock.Mutex) ICmdObj {
self.mutex = mutex
return self
@@ -161,6 +164,10 @@ func (self *CmdObj) RunWithOutput() (string, error) {
return self.runner.RunWithOutput(self)
}
func (self *CmdObj) RunWithOutputs() (string, string, error) {
return self.runner.RunWithOutputs(self)
}
func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) error {
return self.runner.RunAndProcessLines(self, onLine)
}

View File

@@ -15,6 +15,7 @@ import (
type ICmdObjRunner interface {
Run(cmdObj ICmdObj) error
RunWithOutput(cmdObj ICmdObj) (string, error)
RunWithOutputs(cmdObj ICmdObj) (string, string, error)
RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
}
@@ -24,6 +25,7 @@ const (
Password CredentialType = iota
Username
Passphrase
PIN
)
type cmdObjRunner struct {
@@ -76,6 +78,31 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
return self.RunWithOutputAux(cmdObj)
}
func (self *cmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error) {
if cmdObj.Mutex() != nil {
cmdObj.Mutex().Lock()
defer cmdObj.Mutex().Unlock()
}
if cmdObj.GetCredentialStrategy() != NONE {
err := self.runWithCredentialHandling(cmdObj)
// for now we're not capturing output, just because it would take a little more
// effort and there's currently no use case for it. Some commands call RunWithOutputs
// but ignore the output, hence why we've got this check here.
return "", "", err
}
if cmdObj.ShouldStreamOutput() {
err := self.runAndStream(cmdObj)
// for now we're not capturing output, just because it would take a little more
// effort and there's currently no use case for it. Some commands call RunWithOutputs
// but ignore the output, hence why we've got this check here.
return "", "", err
}
return self.RunWithOutputsAux(cmdObj)
}
func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
@@ -90,6 +117,28 @@ func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
return output, err
}
func (self *cmdObjRunner) RunWithOutputsAux(cmdObj ICmdObj) (string, string, error) {
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
if cmdObj.ShouldLog() {
self.logCmdObj(cmdObj)
}
var outBuffer, errBuffer bytes.Buffer
cmd := cmdObj.GetCmd()
cmd.Stdout = &outBuffer
cmd.Stderr = &errBuffer
err := cmd.Run()
stdout := outBuffer.String()
stderr, err := sanitisedCommandOutput(errBuffer.Bytes(), err)
if err != nil {
self.log.WithField("command", cmdObj.ToString()).Error(stderr)
}
return stdout, stderr, err
}
func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
if cmdObj.Mutex() != nil {
cmdObj.Mutex().Lock()
@@ -147,7 +196,7 @@ func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
promptFn = failPromptFn
case NONE:
// we should never land here
return errors.New("runWithCredentialHandling called but cmdObj does not have a a credential strategy")
return errors.New("runWithCredentialHandling called but cmdObj does not have a credential strategy")
}
return self.runAndDetectCredentialRequest(cmdObj, promptFn)
@@ -215,7 +264,7 @@ func (self *cmdObjRunner) runAndStreamAux(
if cmdObj.ShouldLog() {
self.logCmdObj(cmdObj)
}
self.log.WithField("command", cmdObj.ToString()).Info("RunCommand")
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
cmd := cmdObj.GetCmd()
var stderr bytes.Buffer
@@ -287,6 +336,7 @@ func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (Crede
`Password\s*for\s*'.+':`: Password,
`Username\s*for\s*'.+':`: Username,
`Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase,
`Enter\s*PIN\s*for\s*.+\s*key\s*.+:`: PIN,
}
for pattern, askFor := range prompts {

View File

@@ -7,12 +7,13 @@ import (
"bytes"
"io"
"os/exec"
"sync"
"github.com/sasha-s/go-deadlock"
)
type Buffer struct {
b bytes.Buffer
m sync.Mutex
m deadlock.Mutex
}
func (b *Buffer) Read(p []byte) (n int, err error) {

View File

@@ -19,6 +19,7 @@ type OSCommandDeps struct {
GetenvFn func(string) string
RemoveFileFn func(string) error
Cmd *CmdObjBuilder
TempDir string
}
func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand {
@@ -38,6 +39,7 @@ func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand {
getenvFn: deps.GetenvFn,
removeFileFn: deps.RemoveFileFn,
guiIO: NewNullGuiIO(utils.NewDummyLog()),
tempDir: deps.TempDir,
}
}

View File

@@ -44,6 +44,11 @@ func (self *FakeCmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
return output, err
}
func (self *FakeCmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error) {
output, err := self.RunWithOutput(cmdObj)
return output, "", err
}
func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
output, err := self.RunWithOutput(cmdObj)
if err != nil {

View File

@@ -2,7 +2,6 @@ package oscommands
import (
"io"
"io/ioutil"
"github.com/sirupsen/logrus"
)
@@ -45,7 +44,7 @@ func NewNullGuiIO(log *logrus.Entry) *guiIO {
return &guiIO{
log: log,
logCommandFn: func(string, bool) {},
newCmdWriterFn: func() io.Writer { return ioutil.Discard },
newCmdWriterFn: func() io.Writer { return io.Discard },
promptForCredentialFn: failPromptFn,
}
}

View File

@@ -2,7 +2,7 @@ package oscommands
import (
"fmt"
"io/ioutil"
"io"
"os"
"os/exec"
"path/filepath"
@@ -109,13 +109,34 @@ 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)
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
f, err := os.OpenFile(filename, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0o600)
if err != nil {
return utils.WrapError(err)
}
defer f.Close()
_, err = f.WriteString("\n" + line)
info, err := os.Stat(filename)
if err != nil {
return utils.WrapError(err)
}
if info.Size() > 0 {
// read last char
buf := make([]byte, 1)
if _, err := f.ReadAt(buf, info.Size()-1); err != nil {
return utils.WrapError(err)
}
// if the last byte of the file is not a newline, add it
if []byte("\n")[0] != buf[0] {
_, err = f.WriteString("\n")
}
}
if err == nil {
_, err = f.WriteString(line + "\n")
}
if err != nil {
return utils.WrapError(err)
}
@@ -130,7 +151,7 @@ func (c *OSCommand) CreateFileWithContent(path string, content string) error {
return err
}
if err := ioutil.WriteFile(path, []byte(content), 0o644); err != nil {
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
c.Log.Error(err)
return utils.WrapError(err)
}
@@ -194,7 +215,7 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
c.Log.Error(err)
}
if b, err := ioutil.ReadAll(stderr); err == nil {
if b, err := io.ReadAll(stderr); err == nil {
if len(b) > 0 {
finalErrors = append(finalErrors, string(b))
}

View File

@@ -2,6 +2,7 @@ package oscommands
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -135,3 +136,61 @@ func TestOSCommandFileType(t *testing.T) {
_ = os.RemoveAll(s.path)
}
}
func TestOSCommandAppendLineToFile(t *testing.T) {
type scenario struct {
path string
setup func(string)
test func(string)
}
scenarios := []scenario{
{
filepath.Join(os.TempDir(), "testFile"),
func(path string) {
if err := os.WriteFile(path, []byte("hello"), 0o600); err != nil {
panic(err)
}
},
func(output string) {
assert.EqualValues(t, "hello\nworld\n", output)
},
},
{
filepath.Join(os.TempDir(), "emptyTestFile"),
func(path string) {
if err := os.WriteFile(path, []byte(""), 0o600); err != nil {
panic(err)
}
},
func(output string) {
assert.EqualValues(t, "world\n", output)
},
},
{
filepath.Join(os.TempDir(), "testFileWithNewline"),
func(path string) {
if err := os.WriteFile(path, []byte("hello\n"), 0o600); err != nil {
panic(err)
}
},
func(output string) {
assert.EqualValues(t, "hello\nworld\n", output)
},
},
}
for _, s := range scenarios {
s.setup(s.path)
osCommand := NewDummyOSCommand()
if err := osCommand.AppendLineToFile(s.path, "world"); err != nil {
panic(err)
}
f, err := os.ReadFile(s.path)
if err != nil {
panic(err)
}
s.test(string(f))
_ = os.RemoveAll(s.path)
}
}

View File

@@ -190,7 +190,7 @@ func (p *PatchManager) RenderPatchForFile(filename string, plain bool, reverse b
parser := NewPatchParser(p.Log, patch)
// not passing included lines because we don't want to see them in the secondary panel
return parser.Render(-1, -1, nil)
return parser.Render(false, -1, -1, nil)
}
func (p *PatchManager) renderEachFilePatch(plain bool) []string {

View File

@@ -183,7 +183,7 @@ func parsePatch(patch string) ([]int, []int, []*PatchLine) {
}
// Render returns the coloured string of the diff with any selected lines highlighted
func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndices []int) string {
func (p *PatchParser) Render(isFocused bool, firstLineIndex int, lastLineIndex int, incLineIndices []int) string {
contentToDisplay := slices.Some(p.PatchLines, func(line *PatchLine) bool {
return line.Content != ""
})
@@ -192,7 +192,7 @@ func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndic
}
renderedLines := slices.MapWithIndex(p.PatchLines, func(patchLine *PatchLine, index int) string {
selected := index >= firstLineIndex && index <= lastLineIndex
selected := isFocused && index >= firstLineIndex && index <= lastLineIndex
included := lo.Contains(incLineIndices, index)
return patchLine.render(selected, included)
})
@@ -202,16 +202,18 @@ func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndic
return result
}
// PlainRenderLines returns the non-coloured string of diff part from firstLineIndex to
// RenderLinesPlain returns the non-coloured string of diff part from firstLineIndex to
// lastLineIndex
func (p *PatchParser) PlainRenderLines(firstLineIndex, lastLineIndex int) string {
linesToCopy := p.PatchLines[firstLineIndex : lastLineIndex+1]
func (p *PatchParser) RenderLinesPlain(firstLineIndex, lastLineIndex int) string {
return renderLinesPlain(p.PatchLines[firstLineIndex : lastLineIndex+1])
}
renderedLines := slices.Map(linesToCopy, func(line *PatchLine) string {
return line.Content
func renderLinesPlain(lines []*PatchLine) string {
renderedLines := slices.Map(lines, func(line *PatchLine) string {
return line.Content + "\n"
})
return strings.Join(renderedLines, "\n")
return strings.Join(renderedLines, "")
}
// GetNextStageableLineIndex takes a line index and returns the line index of the next stageable line

View File

@@ -2,7 +2,6 @@ package config
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -15,7 +14,6 @@ import (
type AppConfig struct {
Debug bool `long:"debug" env:"DEBUG" default:"false"`
Version string `long:"version" env:"VERSION" default:"unversioned"`
Commit string `long:"commit" env:"COMMIT"`
BuildDate string `long:"build-date" env:"BUILD_DATE"`
Name string `long:"name" env:"NAME" default:"lazygit"`
BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
@@ -28,15 +26,11 @@ type AppConfig struct {
IsNewRepo bool
}
// AppConfigurer interface allows individual app config structs to inherit Fields
// from AppConfig and still be used by lazygit.
type AppConfigurer interface {
GetDebug() bool
// build info
GetVersion() string
GetCommit() string
GetBuildDate() string
GetName() string
GetBuildSource() string
@@ -80,10 +74,6 @@ func NewAppConfig(
return nil, err
}
if os.Getenv("DEBUG") == "TRUE" {
debuggingFlag = true
}
appState, err := loadAppState()
if err != nil {
return nil, err
@@ -92,7 +82,6 @@ func NewAppConfig(
appConfig := &AppConfig{
Name: name,
Version: version,
Commit: commit,
BuildDate: date,
Debug: debuggingFlag,
BuildSource: buildSource,
@@ -162,7 +151,7 @@ func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error)
file.Close()
}
content, err := ioutil.ReadFile(path)
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
@@ -183,14 +172,6 @@ func (c *AppConfig) GetVersion() string {
return c.Version
}
func (c *AppConfig) GetCommit() string {
return c.Commit
}
func (c *AppConfig) GetBuildDate() string {
return c.BuildDate
}
func (c *AppConfig) GetName() string {
return c.Name
}
@@ -261,7 +242,7 @@ func (c *AppConfig) SaveAppState() error {
return err
}
err = ioutil.WriteFile(filepath, marshalledAppState, 0o644)
err = os.WriteFile(filepath, marshalledAppState, 0o644)
if err != nil && os.IsPermission(err) {
// apparently when people have read-only permissions they prefer us to fail silently
return nil
@@ -281,7 +262,7 @@ func loadAppState() (*AppState, error) {
return nil, err
}
appStateBytes, err := ioutil.ReadFile(filepath)
appStateBytes, err := os.ReadFile(filepath)
if err != nil && !os.IsNotExist(err) {
return nil, err
}

View File

@@ -7,14 +7,11 @@ import (
// NewDummyAppConfig creates a new dummy AppConfig for testing
func NewDummyAppConfig() *AppConfig {
appConfig := &AppConfig{
Name: "lazygit",
Version: "unversioned",
Commit: "",
BuildDate: "",
Debug: false,
BuildSource: "",
UserConfig: GetDefaultConfig(),
AppState: &AppState{},
Name: "lazygit",
Version: "unversioned",
Debug: false,
UserConfig: GetDefaultConfig(),
AppState: &AppState{},
}
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
return appConfig

View File

@@ -9,8 +9,6 @@ type UserConfig struct {
Git GitConfig `yaml:"git"`
Update UpdateConfig `yaml:"update"`
Refresher RefresherConfig `yaml:"refresher"`
Reporting string `yaml:"reporting"`
SplashUpdatesIndex int `yaml:"splashUpdatesIndex"`
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
Keybinding KeybindingConfig `yaml:"keybinding"`
@@ -29,33 +27,34 @@ type RefresherConfig struct {
}
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"`
SkipUnstageLineWarning bool `yaml:"skipUnstageLineWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
SidePanelWidth float64 `yaml:"sidePanelWidth"`
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
Language string `yaml:"language"`
TimeFormat string `yaml:"timeFormat"`
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"`
CommandLogSize int `yaml:"commandLogSize"`
SplitDiff string `yaml:"splitDiff"`
AuthorColors map[string]string `yaml:"authorColors"`
BranchColors map[string]string `yaml:"branchColors"`
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
MouseEvents bool `yaml:"mouseEvents"`
SkipUnstageLineWarning bool `yaml:"skipUnstageLineWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
SidePanelWidth float64 `yaml:"sidePanelWidth"`
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
Language string `yaml:"language"`
TimeFormat string `yaml:"timeFormat"`
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"`
CommandLogSize int `yaml:"commandLogSize"`
SplitDiff string `yaml:"splitDiff"`
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
WindowSize string `yaml:"windowSize"`
}
type ThemeConfig struct {
LightTheme bool `yaml:"lightTheme"`
ActiveBorderColor []string `yaml:"activeBorderColor"`
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
OptionsTextColor []string `yaml:"optionsTextColor"`
@@ -64,6 +63,7 @@ type ThemeConfig struct {
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
DefaultFgColor []string `yaml:"defaultFgColor"`
}
type CommitLengthConfig struct {
@@ -95,7 +95,8 @@ type PagingConfig struct {
}
type CommitConfig struct {
SignOff bool `yaml:"signOff"`
SignOff bool `yaml:"signOff"`
Verbose string `yaml:"verbose"`
}
type MergingConfig struct {
@@ -136,6 +137,7 @@ type KeybindingUniversalConfig struct {
Quit string `yaml:"quit"`
QuitAlt1 string `yaml:"quit-alt1"`
Return string `yaml:"return"`
ReturnAlt1 string `yaml:"return-alt1"`
QuitWithoutChangingDirectory string `yaml:"quitWithoutChangingDirectory"`
TogglePanel string `yaml:"togglePanel"`
PrevItem string `yaml:"prevItem"`
@@ -210,7 +212,7 @@ type KeybindingFilesConfig struct {
CommitChangesWithoutHook string `yaml:"commitChangesWithoutHook"`
AmendLastCommit string `yaml:"amendLastCommit"`
CommitChangesWithEditor string `yaml:"commitChangesWithEditor"`
IgnoreOrExcludeFile string `yaml:"IgnoreOrExcludeFile"`
IgnoreFile string `yaml:"ignoreFile"`
RefreshFiles string `yaml:"refreshFiles"`
StashAllChanges string `yaml:"stashAllChanges"`
ViewStashOptions string `yaml:"viewStashOptions"`
@@ -265,7 +267,8 @@ type KeybindingCommitsConfig struct {
}
type KeybindingStashConfig struct {
PopStash string `yaml:"popStash"`
PopStash string `yaml:"popStash"`
RenameStash string `yaml:"renameStash"`
}
type KeybindingCommitFilesConfig struct {
@@ -310,10 +313,15 @@ type CustomCommand struct {
LoadingText string `yaml:"loadingText"`
Description string `yaml:"description"`
Stream bool `yaml:"stream"`
ShowOutput bool `yaml:"showOutput"`
}
type CustomCommandPrompt struct {
Type string `yaml:"type"` // one of 'input', 'menu', or 'confirm'
Key string `yaml:"key"`
// one of 'input', 'menu', 'confirm', or 'menuFromCommand'
Type string `yaml:"type"`
Title string `yaml:"title"`
// this only apply to input prompts
@@ -352,26 +360,27 @@ func GetDefaultConfig() *UserConfig {
Language: "auto",
TimeFormat: time.RFC822,
Theme: ThemeConfig{
LightTheme: false,
ActiveBorderColor: []string{"green", "bold"},
InactiveBorderColor: []string{"white"},
InactiveBorderColor: []string{"default"},
OptionsTextColor: []string{"blue"},
SelectedLineBgColor: []string{"blue"},
SelectedRangeBgColor: []string{"blue"},
CherryPickedCommitBgColor: []string{"cyan"},
CherryPickedCommitFgColor: []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,
CommandLogSize: 8,
SplitDiff: "auto",
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
},
Git: GitConfig{
Paging: PagingConfig{
@@ -381,6 +390,7 @@ func GetDefaultConfig() *UserConfig {
},
Commit: CommitConfig{
SignOff: false,
Verbose: "default",
},
Merging: MergingConfig{
ManualCommit: false,
@@ -409,8 +419,6 @@ func GetDefaultConfig() *UserConfig {
Method: "prompt",
Days: 14,
},
Reporting: "undetermined",
SplashUpdatesIndex: 0,
ConfirmOnQuit: false,
QuitOnTopLevelReturn: false,
Keybinding: KeybindingConfig{
@@ -418,6 +426,7 @@ func GetDefaultConfig() *UserConfig {
Quit: "q",
QuitAlt1: "<c-c>",
Return: "<esc>",
ReturnAlt1: "",
QuitWithoutChangingDirectory: "Q",
TogglePanel: "<tab>",
PrevItem: "<up>",
@@ -490,7 +499,7 @@ func GetDefaultConfig() *UserConfig {
CommitChangesWithoutHook: "w",
AmendLastCommit: "A",
CommitChangesWithEditor: "C",
IgnoreOrExcludeFile: "i",
IgnoreFile: "i",
RefreshFiles: "r",
StashAllChanges: "s",
ViewStashOptions: "S",
@@ -542,7 +551,8 @@ func GetDefaultConfig() *UserConfig {
ViewBisectOptions: "b",
},
Stash: KeybindingStashConfig{
PopStash: "g",
PopStash: "g",
RenameStash: "r",
},
CommitFiles: KeybindingCommitFilesConfig{
CheckoutCommitFile: "c",

View File

@@ -1,11 +1,11 @@
package gui
import (
"sync"
"time"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sasha-s/go-deadlock"
)
// statusManager's job is to handle rendering of loading states and toast notifications
@@ -13,7 +13,7 @@ import (
type statusManager struct {
statuses []appStatus
nextId int
mutex sync.Mutex
mutex deadlock.Mutex
}
type appStatus struct {
@@ -82,6 +82,10 @@ func (m *statusManager) getStatusString() string {
return topStatus.message
}
func (m *statusManager) showStatus() bool {
return len(m.statuses) > 0
}
func (gui *Gui) toast(message string) {
gui.statusManager.addToastStatus(message)
@@ -94,7 +98,7 @@ func (gui *Gui) renderAppStatus() {
defer ticker.Stop()
for range ticker.C {
appStatus := gui.statusManager.getStatusString()
gui.OnUIThread(func() error {
gui.c.OnUIThread(func() error {
return gui.renderString(gui.Views.AppStatus, appStatus)
})
@@ -117,7 +121,7 @@ func (gui *Gui) withWaitingStatus(message string, f func() error) error {
gui.renderAppStatus()
if err := f(); err != nil {
gui.OnUIThread(func() error {
gui.c.OnUIThread(func() error {
return gui.c.Error(err)
})
}

View File

@@ -1,7 +1,7 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/gui/boxlayout"
"github.com/jesseduffield/lazycore/pkg/boxlayout"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -31,7 +31,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
extrasWindowSize := gui.getExtrasWindowSize(height)
showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || (gui.State.Searching.isSearching || gui.isAnyModeActive())
showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || gui.State.Searching.isSearching || gui.isAnyModeActive() || gui.statusManager.showStatus()
infoSectionSize := 0
if showInfoSection {
infoSectionSize = 1
@@ -105,20 +105,13 @@ func (gui *Gui) mainSectionChildren() []*boxlayout.Box {
}
}
main := "main"
secondary := "secondary"
if gui.secondaryViewFocused() {
// when you think you've focused the secondary view, we've actually just swapped them around in the layout
main, secondary = secondary, main
}
return []*boxlayout.Box{
{
Window: main,
Window: "main",
Weight: 1,
},
{
Window: secondary,
Window: "secondary",
Weight: 1,
},
}
@@ -166,30 +159,26 @@ func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*
}
}
result := []*boxlayout.Box{}
appStatusBox := &boxlayout.Box{Window: "appStatus"}
optionsBox := &boxlayout.Box{Window: "options"}
if len(appStatus) > 0 {
result = append(result,
&boxlayout.Box{
Window: "appStatus",
Size: runewidth.StringWidth(appStatus) + runewidth.StringWidth(INFO_SECTION_PADDING),
},
)
if !gui.c.UserConfig.Gui.ShowBottomLine {
optionsBox.Weight = 0
appStatusBox.Weight = 1
} else {
optionsBox.Weight = 1
appStatusBox.Size = runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(appStatus)
}
result = append(result,
[]*boxlayout.Box{
{
Window: "options",
Weight: 1,
},
{
Window: "information",
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
Size: runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(utils.Decolorise(informationStr)),
},
}...,
)
result := []*boxlayout.Box{appStatusBox, optionsBox}
if gui.c.UserConfig.Gui.ShowBottomLine || gui.isAnyModeActive() {
result = append(result, &boxlayout.Box{
Window: "information",
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
Size: runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(utils.Decolorise(informationStr)),
})
}
return result
}

71
pkg/gui/background.go Normal file
View File

@@ -0,0 +1,71 @@
package gui
import (
"strings"
"time"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) startBackgroundRoutines() {
userConfig := gui.UserConfig
if userConfig.Git.AutoFetch {
fetchInterval := userConfig.Refresher.FetchInterval
if fetchInterval > 0 {
go utils.Safe(gui.startBackgroundFetch)
} else {
gui.c.Log.Errorf(
"Value of config option 'refresher.fetchInterval' (%d) is invalid, disabling auto-fetch",
fetchInterval)
}
}
if userConfig.Git.AutoRefresh {
refreshInterval := userConfig.Refresher.RefreshInterval
if refreshInterval > 0 {
gui.goEvery(time.Second*time.Duration(refreshInterval), gui.stopChan, gui.refreshFilesAndSubmodules)
} else {
gui.c.Log.Errorf(
"Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh",
refreshInterval)
}
}
}
func (gui *Gui) startBackgroundFetch() {
gui.waitForIntro.Wait()
isNew := gui.IsNewRepo
userConfig := gui.UserConfig
if !isNew {
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
}
err := gui.backgroundFetch()
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
_ = gui.c.Alert(gui.c.Tr.NoAutomaticGitFetchTitle, gui.c.Tr.NoAutomaticGitFetchBody)
} else {
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {
err := gui.backgroundFetch()
gui.render()
return err
})
}
}
func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
go utils.Safe(func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if gui.PauseBackgroundThreads {
continue
}
_ = function()
case <-stop:
return
}
}
})
}

View File

@@ -1,380 +0,0 @@
package boxlayout
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestArrangeWindows(t *testing.T) {
type scenario struct {
testName string
root *Box
x0 int
y0 int
width int
height int
test func(result map[string]Dimensions)
}
scenarios := []scenario{
{
testName: "Empty box",
root: &Box{},
x0: 0,
y0: 0,
width: 10,
height: 10,
test: func(result map[string]Dimensions) {
assert.EqualValues(t, result, map[string]Dimensions{})
},
},
{
testName: "Box with static and dynamic panel",
root: &Box{Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic"}}},
x0: 0,
y0: 0,
width: 10,
height: 10,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"dynamic": {X0: 0, X1: 9, Y0: 1, Y1: 9},
"static": {X0: 0, X1: 9, Y0: 0, Y1: 0},
},
)
},
},
{
testName: "Box with static and two dynamic panels",
root: &Box{Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
x0: 0,
y0: 0,
width: 10,
height: 10,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"static": {X0: 0, X1: 9, Y0: 0, Y1: 0},
"dynamic1": {X0: 0, X1: 9, Y0: 1, Y1: 3},
"dynamic2": {X0: 0, X1: 9, Y0: 4, Y1: 9},
},
)
},
},
{
testName: "Box with COLUMN direction",
root: &Box{Direction: COLUMN, Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
x0: 0,
y0: 0,
width: 10,
height: 10,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"static": {X0: 0, X1: 0, Y0: 0, Y1: 9},
"dynamic1": {X0: 1, X1: 3, Y0: 0, Y1: 9},
"dynamic2": {X0: 4, X1: 9, Y0: 0, Y1: 9},
},
)
},
},
{
testName: "Box with COLUMN direction only on wide boxes with narrow box",
root: &Box{ConditionalDirection: func(width int, height int) Direction {
if width > 4 {
return COLUMN
} else {
return ROW
}
}, Children: []*Box{{Weight: 1, Window: "dynamic1"}, {Weight: 1, Window: "dynamic2"}}},
x0: 0,
y0: 0,
width: 4,
height: 4,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 1},
"dynamic2": {X0: 0, X1: 3, Y0: 2, Y1: 3},
},
)
},
},
{
testName: "Box with COLUMN direction only on wide boxes with wide box",
root: &Box{ConditionalDirection: func(width int, height int) Direction {
if width > 4 {
return COLUMN
} else {
return ROW
}
}, Children: []*Box{{Weight: 1, Window: "dynamic1"}, {Weight: 1, Window: "dynamic2"}}},
// 5 / 2 = 2 remainder 1. That remainder goes to the first box.
x0: 0,
y0: 0,
width: 5,
height: 5,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"dynamic1": {X0: 0, X1: 2, Y0: 0, Y1: 4},
"dynamic2": {X0: 3, X1: 4, Y0: 0, Y1: 4},
},
)
},
},
{
testName: "Box with conditional children where box is wide",
root: &Box{ConditionalChildren: func(width int, height int) []*Box {
if width > 4 {
return []*Box{{Window: "wide", Weight: 1}}
} else {
return []*Box{{Window: "narrow", Weight: 1}}
}
}},
x0: 0,
y0: 0,
width: 5,
height: 5,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"wide": {X0: 0, X1: 4, Y0: 0, Y1: 4},
},
)
},
},
{
testName: "Box with conditional children where box is narrow",
root: &Box{ConditionalChildren: func(width int, height int) []*Box {
if width > 4 {
return []*Box{{Window: "wide", Weight: 1}}
} else {
return []*Box{{Window: "narrow", Weight: 1}}
}
}},
x0: 0,
y0: 0,
width: 4,
height: 4,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"narrow": {X0: 0, X1: 3, Y0: 0, Y1: 3},
},
)
},
},
{
testName: "Box with static child with size too large",
root: &Box{Direction: COLUMN, Children: []*Box{{Size: 11, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
x0: 0,
y0: 0,
width: 10,
height: 10,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"static": {X0: 0, X1: 9, Y0: 0, Y1: 9},
// not sure if X0: 10, X1: 9 makes any sense, but testing this in the
// actual GUI it seems harmless
"dynamic1": {X0: 10, X1: 9, Y0: 0, Y1: 9},
"dynamic2": {X0: 10, X1: 9, Y0: 0, Y1: 9},
},
)
},
},
{
// 10 total space minus 2 from the status box leaves us with 8.
// Total weight is 3, 8 / 3 = 2 with 2 remainder.
// We want to end up with 2, 3, 5 (one unit from remainder to each dynamic box)
testName: "Distributing remainder across weighted boxes",
root: &Box{Direction: COLUMN, Children: []*Box{{Size: 2, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
x0: 0,
y0: 0,
width: 10,
height: 10,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"static": {X0: 0, X1: 1, Y0: 0, Y1: 9}, // 2
"dynamic1": {X0: 2, X1: 4, Y0: 0, Y1: 9}, // 3
"dynamic2": {X0: 5, X1: 9, Y0: 0, Y1: 9}, // 5
},
)
},
},
{
// 9 total space.
// total weight is 5, 9 / 5 = 1 with 4 remainder
// we want to give 2 of that remainder to the first, 1 to the second, and 1 to the last.
// Reason being that we just give units to each box evenly and consider weight in subsequent passes.
testName: "Distributing remainder across weighted boxes 2",
root: &Box{Direction: COLUMN, Children: []*Box{{Weight: 2, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}, {Weight: 1, Window: "dynamic3"}}},
x0: 0,
y0: 0,
width: 9,
height: 10,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 9}, // 4
"dynamic2": {X0: 4, X1: 6, Y0: 0, Y1: 9}, // 3
"dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2
},
)
},
},
{
// 9 total space.
// total weight is 5, 9 / 5 = 1 with 4 remainder
// we want to give 2 of that remainder to the first, 1 to the second, and 1 to the last.
// Reason being that we just give units to each box evenly and consider weight in subsequent passes.
testName: "Distributing remainder across weighted boxes with unnormalized weights",
root: &Box{Direction: COLUMN, Children: []*Box{{Weight: 4, Window: "dynamic1"}, {Weight: 4, Window: "dynamic2"}, {Weight: 2, Window: "dynamic3"}}},
x0: 0,
y0: 0,
width: 9,
height: 10,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 9}, // 4
"dynamic2": {X0: 4, X1: 6, Y0: 0, Y1: 9}, // 3
"dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2
},
)
},
},
{
testName: "Another distribution test",
root: &Box{Direction: COLUMN, Children: []*Box{
{Weight: 3, Window: "dynamic1"},
{Weight: 1, Window: "dynamic2"},
{Weight: 1, Window: "dynamic3"},
}},
x0: 0,
y0: 0,
width: 9,
height: 10,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"dynamic1": {X0: 0, X1: 4, Y0: 0, Y1: 9}, // 5
"dynamic2": {X0: 5, X1: 6, Y0: 0, Y1: 9}, // 2
"dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2
},
)
},
},
{
testName: "Box with zero weight",
root: &Box{Direction: COLUMN, Children: []*Box{
{Weight: 1, Window: "dynamic1"},
{Weight: 0, Window: "dynamic2"},
}},
x0: 0,
y0: 0,
width: 10,
height: 10,
test: func(result map[string]Dimensions) {
assert.EqualValues(
t,
result,
map[string]Dimensions{
"dynamic1": {X0: 0, X1: 9, Y0: 0, Y1: 9},
"dynamic2": {X0: 10, X1: 9, Y0: 0, Y1: 9}, // when X0 > X1, we will hide the window
},
)
},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
s.test(ArrangeWindows(s.root, s.x0, s.y0, s.width, s.height))
})
}
}
func TestNormalizeWeights(t *testing.T) {
scenarios := []struct {
testName string
input []int
expected []int
}{
{
testName: "empty",
input: []int{},
expected: []int{},
},
{
testName: "one item of value 1",
input: []int{1},
expected: []int{1},
},
{
testName: "one item of value greater than 1",
input: []int{2},
expected: []int{1},
},
{
testName: "slice contains 1",
input: []int{2, 1},
expected: []int{2, 1},
},
{
testName: "slice contains 2 and 2",
input: []int{2, 2},
expected: []int{1, 1},
},
{
testName: "no common multiple",
input: []int{2, 3},
expected: []int{2, 3},
},
{
testName: "complex case",
input: []int{10, 10, 20},
expected: []int{1, 1, 2},
},
{
testName: "when a zero weight is included it is ignored",
input: []int{10, 10, 20, 0},
expected: []int{1, 1, 2, 0},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
assert.EqualValues(t, s.expected, normalizeWeights(s.input))
})
}
}

View File

@@ -1,20 +1,23 @@
package gui
import "github.com/jesseduffield/lazygit/pkg/gui/types"
func (gui *Gui) branchesRenderToMain() error {
var task updateTask
var task types.UpdateTask
branch := gui.State.Contexts.Branches.GetSelected()
if branch == nil {
task = NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
task = types.NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
} else {
cmdObj := gui.git.Branch.GetGraphCmdObj(branch.FullRefName())
task = NewRunPtyTask(cmdObj.GetCmd())
task = types.NewRunPtyTask(cmdObj.GetCmd())
}
return gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{
title: gui.c.Tr.LogTitle,
task: task,
return gui.c.RenderToMainViews(types.RefreshMainOpts{
Pair: gui.c.MainViewPairs().Normal,
Main: &types.ViewUpdateOpts{
Title: gui.c.Tr.LogTitle,
Task: task,
},
})
}

View File

@@ -7,15 +7,16 @@ import (
"time"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/theme"
)
// our UI command log looks like this:
// Stage File
// git add -- 'filename'
// Unstage File
// git reset HEAD 'filename'
// Stage File:
// git add -- 'filename'
// Unstage File:
// git reset HEAD 'filename'
//
// The 'Stage File' and 'Unstage File' lines are actions i.e they group up a set
// of command logs (typically there's only one command under an action but there may be more).
@@ -53,7 +54,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
func (gui *Gui) printCommandLogHeader() {
introStr := fmt.Sprintf(
gui.c.Tr.CommandLogHeader,
gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
keybindings.Label(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
)
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
@@ -71,7 +72,7 @@ func (gui *Gui) getRandomTip() string {
config := gui.c.UserConfig.Keybinding
formattedKey := func(key string) string {
return gui.getKeyDisplay(key)
return keybindings.Label(key)
}
tips := []string{

View File

@@ -2,14 +2,9 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// TODO: do we need this?
func (gui *Gui) onCommitFileFocus() error {
gui.escapeLineByLinePanel()
return nil
}
func (gui *Gui) commitFilesRenderToMain() error {
node := gui.State.Contexts.CommitFiles.GetSelected()
if node == nil {
@@ -21,20 +16,20 @@ func (gui *Gui) commitFilesRenderToMain() error {
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
task := NewRunPtyTask(cmdObj.GetCmd())
task := types.NewRunPtyTask(cmdObj.GetCmd())
mainContext := gui.State.Contexts.Normal
pair := gui.c.MainViewPairs().Normal
if node.File != nil {
mainContext = gui.State.Contexts.PatchBuilding
pair = gui.c.MainViewPairs().PatchBuilding
}
return gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{
title: "Patch",
task: task,
context: mainContext,
return gui.c.RenderToMainViews(types.RefreshMainOpts{
Pair: pair,
Main: &types.ViewUpdateOpts{
Title: gui.Tr.Patch,
Task: task,
},
secondary: gui.secondaryPatchPanelUpdateOpts(),
Secondary: gui.secondaryPatchPanelUpdateOpts(),
})
}
@@ -46,33 +41,11 @@ func (gui *Gui) SwitchToCommitFilesContext(opts controllers.SwitchToCommitFilesC
gui.State.Contexts.CommitFiles.SetParentContext(opts.Context)
gui.State.Contexts.CommitFiles.SetWindowName(opts.Context.GetWindowName())
if err := gui.refreshCommitFilesContext(); err != nil {
if err := gui.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.COMMIT_FILES},
}); err != nil {
return err
}
return gui.c.PushContext(gui.State.Contexts.CommitFiles)
}
func (gui *Gui) refreshCommitFilesContext() error {
ref := gui.State.Contexts.CommitFiles.GetRef()
to := ref.RefName()
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
files, err := gui.git.Loaders.CommitFiles.GetFilesInDiff(from, to, reverse)
if err != nil {
return gui.c.Error(err)
}
gui.State.Model.CommitFiles = files
gui.State.Contexts.CommitFiles.CommitFileTreeViewModel.SetTree()
return gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles)
}
func (gui *Gui) getSelectedCommitFileName() string {
node := gui.State.Contexts.CommitFiles.GetSelected()
if node == nil {
return ""
}
return node.Path
}

View File

@@ -5,6 +5,7 @@ import (
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -12,9 +13,9 @@ func (gui *Gui) handleCommitMessageFocused() error {
message := utils.ResolvePlaceholderString(
gui.c.Tr.CommitMessageConfirm,
map[string]string{
"keyBindClose": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Return),
"keyBindConfirm": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Confirm),
"keyBindNewLine": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
"keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return),
"keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm),
"keyBindNewLine": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
},
)

View File

@@ -2,6 +2,7 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -25,30 +26,42 @@ func (gui *Gui) onCommitFocus() error {
})
}
gui.escapeLineByLinePanel()
return nil
}
func (gui *Gui) branchCommitsRenderToMain() error {
var task updateTask
var task types.UpdateTask
commit := gui.State.Contexts.LocalCommits.GetSelected()
if commit == nil {
task = NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
task = types.NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
} else {
cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
task = NewRunPtyTask(cmdObj.GetCmd())
task = types.NewRunPtyTask(cmdObj.GetCmd())
}
return gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{
title: "Patch",
task: task,
return gui.c.RenderToMainViews(types.RefreshMainOpts{
Pair: gui.c.MainViewPairs().Normal,
Main: &types.ViewUpdateOpts{
Title: "Patch",
Task: task,
},
secondary: gui.secondaryPatchPanelUpdateOpts(),
Secondary: gui.secondaryPatchPanelUpdateOpts(),
})
}
func (gui *Gui) secondaryPatchPanelUpdateOpts() *types.ViewUpdateOpts {
if gui.git.Patch.PatchManager.Active() {
patch := gui.git.Patch.PatchManager.RenderAggregatedPatchColored(false)
return &types.ViewUpdateOpts{
Task: types.NewRenderStringWithoutScrollTask(patch),
Title: gui.Tr.CustomPatch,
}
}
return nil
}
func (gui *Gui) refForLog() string {
bisectInfo := gui.git.Bisect.GetInfo()
gui.State.Model.BisectInfo = bisectInfo

View File

@@ -1,11 +1,12 @@
package gui
import (
"context"
"fmt"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
@@ -16,9 +17,11 @@ import (
// This file is for the rendering of confirmation panels along with setting and handling associated
// keybindings.
func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function func() error) func() error {
func (gui *Gui) wrappedConfirmationFunction(cancel context.CancelFunc, function func() error) func() error {
return func() error {
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
cancel()
if err := gui.c.PopContext(); err != nil {
return err
}
@@ -32,9 +35,11 @@ func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function f
}
}
func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, function func(string) error, getResponse func() string) func() error {
func (gui *Gui) wrappedPromptConfirmationFunction(cancel context.CancelFunc, function func(string) error, getResponse func() string) func() error {
return func() error {
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
cancel()
if err := gui.c.PopContext(); err != nil {
return err
}
@@ -48,27 +53,15 @@ func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, func
}
}
func (gui *Gui) closeConfirmationPrompt(handlersManageFocus bool) error {
func (gui *Gui) deactivateConfirmationPrompt() {
gui.Mutexes.PopupMutex.Lock()
gui.State.CurrentPopupOpts = nil
gui.Mutexes.PopupMutex.Unlock()
// we've already closed it so we can just return
if !gui.Views.Confirmation.Visible {
return nil
}
if !handlersManageFocus {
if err := gui.c.PopContext(); err != nil {
return err
}
}
gui.clearConfirmationViewKeyBindings()
gui.Views.Confirmation.Visible = false
gui.Views.Suggestions.Visible = false
return nil
gui.clearConfirmationViewKeyBindings()
}
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
@@ -123,33 +116,31 @@ func (gui *Gui) getConfirmationPanelWidth() int {
}
func (gui *Gui) prepareConfirmationPanel(
title,
prompt string,
hasLoader bool,
findSuggestionsFunc func(string) []*types.Suggestion,
editable bool,
mask bool,
ctx context.Context,
opts types.ConfirmOpts,
) error {
gui.Views.Confirmation.HasLoader = hasLoader
if hasLoader {
gui.g.StartTicking()
gui.Views.Confirmation.HasLoader = opts.HasLoader
if opts.HasLoader {
gui.g.StartTicking(ctx)
}
gui.Views.Confirmation.Title = title
gui.Views.Confirmation.Title = opts.Title
// for now we do not support wrapping in our editor
gui.Views.Confirmation.Wrap = !editable
gui.Views.Confirmation.Wrap = !opts.Editable
gui.Views.Confirmation.FgColor = theme.GocuiDefaultTextColor
gui.Views.Confirmation.Mask = runeForMask(mask)
gui.Views.Confirmation.Mask = runeForMask(opts.Mask)
_ = gui.Views.Confirmation.SetOrigin(0, 0)
gui.findSuggestions = findSuggestionsFunc
if findSuggestionsFunc != nil {
gui.findSuggestions = opts.FindSuggestionsFunc
if opts.FindSuggestionsFunc != nil {
suggestionsView := gui.Views.Suggestions
suggestionsView.Wrap = false
suggestionsView.FgColor = theme.GocuiDefaultTextColor
gui.setSuggestions(findSuggestionsFunc(""))
gui.setSuggestions(opts.FindSuggestionsFunc(""))
suggestionsView.Visible = true
suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel)
}
gui.resizeConfirmationPanel()
return nil
}
@@ -160,16 +151,19 @@ func runeForMask(mask bool) rune {
return 0
}
func (gui *Gui) createPopupPanel(opts types.CreatePopupPanelOpts) error {
func (gui *Gui) createPopupPanel(ctx context.Context, opts types.CreatePopupPanelOpts) error {
gui.Mutexes.PopupMutex.Lock()
defer gui.Mutexes.PopupMutex.Unlock()
ctx, cancel := context.WithCancel(ctx)
// we don't allow interruptions of non-loader popups in case we get stuck somehow
// e.g. a credentials popup never gets its required user input so a process hangs
// forever.
// The proper solution is to have a queue of popup options
if gui.State.CurrentPopupOpts != nil && !gui.State.CurrentPopupOpts.HasLoader {
gui.Log.Error("ignoring create popup panel because a popup panel is already open")
cancel()
return nil
}
@@ -177,14 +171,17 @@ func (gui *Gui) createPopupPanel(opts types.CreatePopupPanelOpts) error {
gui.clearConfirmationViewKeyBindings()
err := gui.prepareConfirmationPanel(
opts.Title,
opts.Prompt,
opts.HasLoader,
opts.FindSuggestionsFunc,
opts.Editable,
opts.Mask,
)
ctx,
types.ConfirmOpts{
Title: opts.Title,
Prompt: opts.Prompt,
HasLoader: opts.HasLoader,
FindSuggestionsFunc: opts.FindSuggestionsFunc,
Editable: opts.Editable,
Mask: opts.Mask,
})
if err != nil {
cancel()
return err
}
confirmationView := gui.Views.Confirmation
@@ -195,14 +192,17 @@ func (gui *Gui) createPopupPanel(opts types.CreatePopupPanelOpts) error {
textArea := confirmationView.TextArea
textArea.Clear()
textArea.TypeString(opts.Prompt)
gui.resizeConfirmationPanel()
confirmationView.RenderTextArea()
} else {
if err := gui.renderString(confirmationView, style.AttrBold.Sprint(opts.Prompt)); err != nil {
cancel()
return err
}
}
if err := gui.setKeyBindings(opts); err != nil {
if err := gui.setKeyBindings(cancel, opts); err != nil {
cancel()
return err
}
@@ -211,7 +211,7 @@ func (gui *Gui) createPopupPanel(opts types.CreatePopupPanelOpts) error {
return gui.c.PushContext(gui.State.Contexts.Confirmation)
}
func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
func (gui *Gui) setKeyBindings(cancel context.CancelFunc, opts types.CreatePopupPanelOpts) error {
actions := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
map[string]string{
@@ -223,14 +223,14 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
_ = gui.renderString(gui.Views.Options, actions)
var onConfirm func() error
if opts.HandleConfirmPrompt != nil {
onConfirm = gui.wrappedPromptConfirmationFunction(opts.HandlersManageFocus, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
onConfirm = gui.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
} else {
onConfirm = gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleConfirm)
onConfirm = gui.wrappedConfirmationFunction(cancel, opts.HandleConfirm)
}
keybindingConfig := gui.c.UserConfig.Keybinding
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
opts.HandlersManageFocus,
cancel,
opts.HandleConfirmPrompt,
gui.getSelectedSuggestionValue,
)
@@ -238,26 +238,22 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
bindings := []*types.Binding{
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Confirm),
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
Handler: onConfirm,
},
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onConfirm,
},
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleClose),
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
},
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error {
if len(gui.State.Suggestions) > 0 {
return gui.replaceContext(gui.State.Contexts.Suggestions)
@@ -267,26 +263,22 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Confirm),
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleClose),
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
},
}
@@ -302,18 +294,23 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
func (gui *Gui) clearConfirmationViewKeyBindings() {
keybindingConfig := gui.c.UserConfig.Keybinding
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
}
func (gui *Gui) refreshSuggestions() {
gui.suggestionsAsyncHandler.Do(func() func() {
suggestions := gui.findSuggestions(gui.c.GetPromptInput())
return func() { gui.setSuggestions(suggestions) }
findSuggestionsFn := gui.findSuggestions
if findSuggestionsFn != nil {
suggestions := gui.findSuggestions(gui.c.GetPromptInput())
return func() { gui.setSuggestions(suggestions) }
} else {
return func() {}
}
})
}
@@ -323,8 +320,8 @@ func (gui *Gui) handleAskFocused() error {
message := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
map[string]string{
"keyBindClose": gui.getKeyDisplay(keybindingConfig.Universal.Return),
"keyBindConfirm": gui.getKeyDisplay(keybindingConfig.Universal.Confirm),
"keyBindClose": keybindings.Label(keybindingConfig.Universal.Return),
"keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm),
},
)

View File

@@ -1,8 +1,6 @@
package gui
import (
"errors"
"fmt"
"sort"
"strings"
@@ -28,24 +26,6 @@ func (gui *Gui) popupViewNames() []string {
})
}
func (gui *Gui) currentContextKeyIgnoringPopups() types.ContextKey {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
stack := gui.State.ContextManager.ContextStack
for i := range stack {
reversedIndex := len(stack) - 1 - i
context := stack[reversedIndex]
kind := stack[reversedIndex].GetKind()
if kind != types.TEMPORARY_POPUP && kind != types.PERSISTENT_POPUP {
return context.GetKey()
}
}
return ""
}
// use replaceContext when you don't want to return to the original context upon
// hitting escape: you want to go that context's parent instead.
func (gui *Gui) replaceContext(c types.Context) error {
@@ -64,54 +44,71 @@ func (gui *Gui) replaceContext(c types.Context) error {
defer gui.State.ContextManager.Unlock()
return gui.activateContext(c)
return gui.activateContext(c, types.OnFocusOpts{})
}
func (gui *Gui) pushContext(c types.Context, opts ...types.OnFocusOpts) error {
// using triple dot but you should only ever pass one of these opt structs
if len(opts) > 1 {
return errors.New("cannot pass multiple opts to pushContext")
}
func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
if !c.IsFocusable() {
return nil
}
gui.State.ContextManager.Lock()
contextsToDeactivate := gui.pushToContextStack(c)
// push onto stack
// if we are switching to a side context, remove all other contexts in the stack
if c.GetKind() == types.SIDE_CONTEXT {
for _, contextToDeactivate := range contextsToDeactivate {
if err := gui.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
return err
}
}
return gui.activateContext(c, opts)
}
// Adjusts the context stack based on the context that's being pushed and returns contexts to deactivate
func (gui *Gui) pushToContextStack(c types.Context) []types.Context {
contextsToDeactivate := []types.Context{}
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
if len(gui.State.ContextManager.ContextStack) == 0 {
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
} else if c.GetKind() == types.SIDE_CONTEXT {
// if we are switching to a side context, remove all other contexts in the stack
contextsToDeactivate = gui.State.ContextManager.ContextStack
gui.State.ContextManager.ContextStack = []types.Context{c}
} else if c.GetKind() == types.MAIN_CONTEXT {
// if we're switching to a main context, remove all other main contexts in the stack
for _, stackContext := range gui.State.ContextManager.ContextStack {
if stackContext.GetKey() != c.GetKey() {
if err := gui.deactivateContext(stackContext); err != nil {
gui.State.ContextManager.Unlock()
return err
}
if stackContext.GetKind() == types.MAIN_CONTEXT {
contextsToDeactivate = append(contextsToDeactivate, stackContext)
}
}
gui.State.ContextManager.ContextStack = []types.Context{c}
} else if len(gui.State.ContextManager.ContextStack) == 0 || gui.currentContextWithoutLock().GetKey() != c.GetKey() {
// Do not append if the one at the end is the same context (e.g. opening a menu from a menu)
// In that case we'll just close the menu entirely when the user hits escape.
} else {
topContext := gui.currentContextWithoutLock()
// TODO: think about other exceptional cases
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
// if we're pushing the same context on, we do nothing.
if topContext.GetKey() != c.GetKey() {
// if top one is a temporary popup, we remove it. Ideally you'd be able to
// escape back to previous temporary popups, but because we're currently reusing
// views for this, you might not be able to get back to where you previously were.
// The exception is when going to the search context e.g. for searching a menu.
if (topContext.GetKind() == types.TEMPORARY_POPUP && c.GetKey() != context.SEARCH_CONTEXT_KEY) ||
// we only ever want one main context on the stack at a time.
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
contextsToDeactivate = append(contextsToDeactivate, topContext)
_, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
}
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
}
}
gui.State.ContextManager.Unlock()
return gui.activateContext(c, opts...)
return contextsToDeactivate
}
// pushContextWithView is to be used when you don't know which context you
// want to switch to: you only know the view that you want to switch to. It will
// look up the context currently active for that view and switch to that context
func (gui *Gui) pushContextWithView(viewName string) error {
return gui.c.PushContext(gui.State.ViewContextMap.Get(viewName))
}
func (gui *Gui) returnFromContext() error {
func (gui *Gui) popContext() error {
gui.State.ContextManager.Lock()
if len(gui.State.ContextManager.ContextStack) == 1 {
@@ -120,25 +117,21 @@ func (gui *Gui) returnFromContext() error {
return nil
}
n := len(gui.State.ContextManager.ContextStack) - 1
var currentContext types.Context
currentContext, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
currentContext := gui.State.ContextManager.ContextStack[n]
newContext := gui.State.ContextManager.ContextStack[n-1]
gui.State.ContextManager.ContextStack = gui.State.ContextManager.ContextStack[:n]
gui.g.SetCurrentContext(string(newContext.GetKey()))
newContext := gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
gui.State.ContextManager.Unlock()
if err := gui.deactivateContext(currentContext); err != nil {
if err := gui.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
return err
}
return gui.activateContext(newContext)
return gui.activateContext(newContext, types.OnFocusOpts{})
}
func (gui *Gui) deactivateContext(c types.Context) error {
func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
view, _ := gui.g.View(c.GetViewName())
if view != nil && view.IsSearching() {
@@ -154,7 +147,7 @@ func (gui *Gui) deactivateContext(c types.Context) error {
view.Visible = false
}
if err := c.HandleFocusLost(); err != nil {
if err := c.HandleFocusLost(opts); err != nil {
return err
}
@@ -165,16 +158,12 @@ func (gui *Gui) deactivateContext(c types.Context) error {
// if the context's view is set to another context we do nothing.
// if the context's view is the current view we trigger a focus; re-selecting the current item.
func (gui *Gui) postRefreshUpdate(c types.Context) error {
if gui.State.ViewContextMap.Get(c.GetViewName()).GetKey() != c.GetKey() {
return nil
}
if err := c.HandleRender(); err != nil {
return err
}
if gui.currentViewName() == c.GetViewName() {
if err := c.HandleFocus(); err != nil {
if err := c.HandleFocus(types.OnFocusOpts{}); err != nil {
return err
}
}
@@ -182,29 +171,16 @@ func (gui *Gui) postRefreshUpdate(c types.Context) error {
return nil
}
func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) error {
func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
viewName := c.GetViewName()
v, err := gui.g.View(viewName)
if err != nil {
return err
}
originalViewContext := gui.State.ViewContextMap.Get(viewName)
var originalViewContextKey types.ContextKey = ""
if originalViewContext != nil {
originalViewContextKey = originalViewContext.GetKey()
}
gui.setWindowContext(c)
gui.setViewTabForContext(c)
if viewName == "main" {
gui.changeMainViewsContext(c)
} else {
gui.changeMainViewsContext(gui.State.Contexts.Normal)
}
gui.g.SetCurrentContext(string(c.GetKey()))
gui.moveToTopOfWindow(c)
if _, err := gui.g.SetCurrentView(viewName); err != nil {
return err
}
@@ -216,15 +192,6 @@ func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) erro
v.Visible = true
// if the new context's view was previously displaying another context, render the new context
if originalViewContextKey != c.GetKey() {
if err := c.HandleRender(); err != nil {
return err
}
}
gui.ViewContextMapSet(viewName, c)
gui.g.Cursor = v.Editable
// render the options available for the current context at the bottom of the screen
@@ -234,7 +201,7 @@ func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) erro
}
gui.renderOptionsMap(optionsMap)
if err := c.HandleFocus(opts...); err != nil {
if err := c.HandleFocus(opts); err != nil {
return err
}
@@ -253,16 +220,6 @@ func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
}
// also setting context on view for now. We'll need to pick one of these two approaches to stick with.
func (gui *Gui) ViewContextMapSet(viewName string, c types.Context) {
gui.State.ViewContextMap.Set(viewName, c)
view, err := gui.g.View(viewName)
if err != nil {
panic(err)
}
view.Context = string(c.GetKey())
}
// // currently unused
// func (gui *Gui) renderContextStack() string {
// result := ""
@@ -326,6 +283,10 @@ func (gui *Gui) currentStaticContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
return gui.currentStaticContextWithoutLock()
}
func (gui *Gui) currentStaticContextWithoutLock() types.Context {
stack := gui.State.ContextManager.ContextStack
if len(stack) == 0 {
@@ -359,7 +320,7 @@ func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
newView := gui.g.CurrentView()
// for now we don't consider losing focus to a popup panel as actually losing focus
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
if err := gui.onViewFocusLost(previousView, newView); err != nil {
if err := gui.onViewFocusLost(previousView); err != nil {
return err
}
@@ -369,7 +330,7 @@ func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
}
}
func (gui *Gui) onViewFocusLost(oldView *gocui.View, newView *gocui.View) error {
func (gui *Gui) onViewFocusLost(oldView *gocui.View) error {
if oldView == nil {
return nil
}
@@ -387,57 +348,14 @@ func (gui *Gui) TransientContexts() []types.Context {
})
}
// changeContext is a helper function for when we want to change a 'main' context
// which currently just means a context that affects both the main and secondary views
// other views can have their context changed directly but this function helps
// keep the main and secondary views in sync
func (gui *Gui) changeMainViewsContext(c types.Context) {
if gui.State.MainContext == c.GetKey() {
return
}
switch c.GetKey() {
case context.MAIN_NORMAL_CONTEXT_KEY, context.MAIN_PATCH_BUILDING_CONTEXT_KEY, context.MAIN_STAGING_CONTEXT_KEY, context.MAIN_MERGING_CONTEXT_KEY:
gui.ViewContextMapSet(gui.Views.Main.Name(), c)
gui.ViewContextMapSet(gui.Views.Secondary.Name(), c)
default:
panic(fmt.Sprintf("unknown context for main: %s", c.GetKey()))
}
gui.State.MainContext = c.GetKey()
}
func (gui *Gui) viewTabNames(viewName string) []string {
tabContexts := gui.State.ViewTabContextMap[viewName]
return slices.Map(tabContexts, func(tabContext context.TabContext) string {
return tabContext.Tab
})
}
func (gui *Gui) setViewTabForContext(c types.Context) {
// search for the context in our map and if we find it, set the tab for the corresponding view
tabContexts, ok := gui.State.ViewTabContextMap[c.GetViewName()]
if !ok {
return
}
for tabIndex, tabContext := range tabContexts {
if tabContext.Context.GetKey() == c.GetKey() {
// get the view, set the tab
v, err := gui.g.View(c.GetViewName())
if err != nil {
gui.c.Log.Error(err)
return
}
v.TabIndex = tabIndex
return
}
}
}
func (gui *Gui) rerenderView(view *gocui.View) error {
return gui.State.ViewContextMap.Get(view.Name()).HandleRender()
context, ok := gui.contextForView(view.Name())
if !ok {
gui.Log.Errorf("no context found for view %s", view.Name())
return nil
}
return context.HandleRender()
}
func (gui *Gui) getSideContextSelectedItemId() string {
@@ -449,11 +367,6 @@ func (gui *Gui) getSideContextSelectedItemId() string {
return currentSideContext.GetSelectedItemId()
}
func (gui *Gui) isContextVisible(c types.Context) bool {
return gui.State.WindowViewNameMap[c.GetWindowName()] == c.GetViewName() &&
gui.State.ViewContextMap.Get(c.GetViewName()).GetKey() == c.GetKey()
}
// currently unused
// func (gui *Gui) getCurrentSideView() *gocui.View {
// currentSideContext := gui.currentSideContext()

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