Compare commits

...
166 Commits
Author SHA1 Message Date
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
RyogaandJesse Duffield 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
lkearn 6422b399e7 Feat: Add emacs character navigation, because I'm weird like that :) 2022-08-09 17:58:43 +10:00
1070 changed files with 22982 additions and 3879 deletions
+13
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>
+69
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"
}
+2 -1
View File
@@ -73,8 +73,9 @@ jobs:
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: |
PARALLEL_TOTAL=${{ matrix.parallelism }} PARALLEL_INDEX=${{ matrix.index }} go test pkg/integration/deprecated/*.go
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"
+1
View File
@@ -27,6 +27,7 @@ lazygit.exe
!.circleci/
!.github/
!.vscode/
!.devcontainer/
# these are for our integration tests
!.git_keep
+1 -5
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.
+53
View File
@@ -0,0 +1,53 @@
{
// 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
}
},
{
"label": "Run current file integration test (slow)",
"type": "shell",
"command": "go run cmd/integration_test/main.go cli --slow ${relativeFile}",
"problemMatcher": [],
"group": {
"kind": "test",
}
},
{
"label": "Run current file integration test (sandbox)",
"type": "shell",
"command": "go run cmd/integration_test/main.go cli --sandbox ${relativeFile}",
"problemMatcher": [],
"group": {
"kind": "test",
}
}
],
}
+66 -1
View File
@@ -23,6 +23,26 @@ 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:
![image](https://user-images.githubusercontent.com/8456633/201500566-ffe9105d-6030-4cc7-a525-6570b0b413a2.png)
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].
@@ -132,6 +152,34 @@ If you want to trigger a debug session from VSCode, you can use the following sn
}
```
## 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. For integration tests, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
@@ -140,7 +188,7 @@ Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go
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:
@@ -151,6 +199,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.
+4 -4
View File
File diff suppressed because one or more lines are too long
+12 -5
View File
@@ -13,7 +13,7 @@ Usage:
See https://github.com/jesseduffield/lazygit/tree/master/pkg/integration/README.md
CLI mode:
> go run cmd/integration_test/main.go cli [--slow] <test1> <test2> ...
> 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
@@ -42,12 +42,19 @@ func main() {
case "cli":
testNames := os.Args[2:]
slow := false
sandbox := false
// get the next arg if it's --slow
if len(os.Args) > 2 && (os.Args[2] == "--slow" || os.Args[2] == "-slow") {
testNames = os.Args[3:]
slow = true
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)
clients.RunCLI(testNames, slow, sandbox)
case "tui":
clients.RunTUI()
default:
+13 -18
View File
@@ -12,6 +12,10 @@ 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
@@ -25,7 +29,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
@@ -219,6 +222,7 @@ keybinding:
viewBisectOptions: 'b'
stash:
popStash: 'g'
renameStash: 'r'
commitFiles:
checkoutCommitFile: 'c'
main:
@@ -339,23 +343,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 +418,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
+1 -1
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'
+1
View File
@@ -238,6 +238,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>
+1
View File
@@ -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>
+1
View File
@@ -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>
+1
View File
@@ -238,6 +238,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>
+1
View File
@@ -231,6 +231,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>
+1
View File
@@ -264,6 +264,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>
+12 -11
View File
@@ -11,33 +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.2
github.com/gdamore/tcell/v2 v2.5.3
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.20220815095708-156fda5e0419
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
github.com/jesseduffield/gocui v0.3.1-0.20221016041636-16c24668f71c
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.0-20210107192922-496545a6307b
gopkg.in/yaml.v3 v3.0.1
)
require (
@@ -61,14 +62,14 @@ require (
github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/rivo/uniseg v0.3.4 // indirect
github.com/rivo/uniseg v0.4.2 // 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/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
golang.org/x/text v0.3.8 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
+27 -20
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.2 h1:tKzG29kO9p2V++3oBY2W9zUjYu7IK1MENFeY/BzJSVY=
github.com/gdamore/tcell/v2 v2.5.2/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
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.20220815095708-156fda5e0419 h1:p3Ix7RUcy4X16Lk5jTSfTxecJT7ryqYHclfRbo/Svzs=
github.com/jesseduffield/gocui v0.3.1-0.20220815095708-156fda5e0419/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.20221016041636-16c24668f71c h1:ttqCvM86hyp4V3DtRPxLo9imD1s+n6ry/zshPx0tt98=
github.com/jesseduffield/gocui v0.3.1-0.20221016041636-16c24668f71c/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,9 @@ 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=
@@ -134,12 +137,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
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/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
github.com/rivo/uniseg v0.4.2/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=
@@ -151,14 +154,17 @@ 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=
@@ -194,15 +200,16 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
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-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w=
golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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=
@@ -222,5 +229,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=
+1 -2
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
+15 -1
View File
@@ -17,6 +17,7 @@ import (
"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"
@@ -57,6 +58,11 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
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")
}
@@ -78,7 +84,8 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
}
if cliArgs.PrintVersionInfo {
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s\n", buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, buildInfo.Version, runtime.GOOS, runtime.GOARCH)
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)
}
@@ -263,3 +270,10 @@ func mergeBuildInfo(buildInfo *BuildInfo) {
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
}
+2 -2
View File
@@ -1,7 +1,7 @@
package app
import (
"io/ioutil"
"io"
"log"
"os"
@@ -26,7 +26,7 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
func newProductionLogger() *logrus.Logger {
log := logrus.New()
log.Out = ioutil.Discard
log.Out = io.Discard
log.SetLevel(logrus.ErrorLevel)
return log
}
+2 -3
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)
}
+4 -4
View File
@@ -15,12 +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/utils"
"github.com/samber/lo"
)
@@ -44,8 +44,8 @@ func CommandToRun() string {
return "go run scripts/cheatsheet/main.go generate"
}
func GetDir() string {
return utils.GetLazygitRootDirectory() + "/docs/keybindings"
func GetKeybindingsDir() string {
return utils.GetLazyRootDirectory() + "/docs/keybindings"
}
func generateAtDir(cheatsheetDir string) {
@@ -75,7 +75,7 @@ func generateAtDir(cheatsheetDir string) {
}
func Generate() {
generateAtDir(GetDir())
generateAtDir(GetKeybindingsDir())
}
func writeString(file *os.File, str string) {
+28 -21
View File
@@ -1,7 +1,6 @@
package commands
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -12,7 +11,6 @@ import (
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,14 +41,14 @@ 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(
@@ -68,7 +66,7 @@ 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
}
@@ -99,10 +97,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, 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 +119,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, cmd)
return &GitCommand{
Branch: branchCommands,
Commit: commitCommands,
@@ -138,14 +145,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,
},
}
}
+4
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)
}
+18 -8
View File
@@ -30,18 +30,20 @@ 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()
if err != nil {
return "", "", err
return BranchInfo{}, err
}
for _, line := range utils.SplitLines(output) {
re := regexp.MustCompile(CurrentBranchNameRegex)
@@ -49,10 +51,18 @@ func (self *BranchCommands) CurrentBranchName() (string, string, error) {
if len(match) > 0 {
branchName = match[1]
displayBranchName := match[0][2:]
return branchName, displayBranchName, nil
return BranchInfo{
RefName: branchName,
DisplayName: displayBranchName,
DetachedHead: true,
}, nil
}
}
return "HEAD", "HEAD", nil
return BranchInfo{
RefName: "HEAD",
DisplayName: "HEAD",
DetachedHead: true,
}, nil
}
// Delete delete branch
@@ -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()
@@ -1,4 +1,4 @@
package loaders
package git_commands
// "*|feat/detect-purge|origin/feat/detect-purge|[ahead 1]"
import (
+22 -18
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,32 +162,34 @@ 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",
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 --contains`, "* (HEAD detached at 8982166a)", nil),
func(info BranchInfo, err error) {
assert.NoError(t, err)
assert.EqualValues(t, "master", name)
assert.EqualValues(t, "master", displayname)
assert.EqualValues(t, "8982166a", info.RefName)
assert.EqualValues(t, "(HEAD detached at 8982166a)", info.DisplayName)
assert.True(t, info.DetachedHead)
},
},
{
@@ -195,10 +197,11 @@ func TestBranchCurrentBranchName(t *testing.T) {
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) {
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, "123abcd", info.RefName)
assert.EqualValues(t, "(HEAD detached at 123abcd)", info.DisplayName)
assert.True(t, info.DetachedHead)
},
},
{
@@ -206,10 +209,11 @@ func TestBranchCurrentBranchName(t *testing.T) {
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) {
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 +222,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()
})
}
@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"fmt"
@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"testing"
@@ -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"
}
@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"path/filepath"
@@ -182,8 +182,8 @@ func TestGetCommits(t *testing.T) {
builder := &CommitLoader{
Common: utils.NewDummyCommon(),
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",
+5 -3
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"
@@ -70,6 +71,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
GetenvFn: getenv,
Cmd: cmd,
RemoveFileFn: removeFile,
TempDir: os.TempDir(),
})
gitCommon.dotGitDir = deps.dotGitDir
@@ -86,8 +88,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 {
+2 -2
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
}
@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"fmt"
@@ -87,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
}
@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"testing"
+6 -6
View File
@@ -2,7 +2,7 @@ package git_commands
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -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,
),
),
@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"fmt"
@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"errors"
@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"fmt"
+39 -4
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
}
@@ -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)
})
}
@@ -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{
+101 -1
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)
})
}
}
@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"github.com/jesseduffield/generics/slices"
@@ -1,4 +1,4 @@
package loaders
package git_commands
import (
"testing"
+4 -5
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 {
@@ -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))
+2 -2
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 {
+4
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
}
+5
View File
@@ -29,6 +29,7 @@ type IFile interface {
GetIsTracked() bool
GetPath() string
GetPreviousPath() string
GetIsFile() bool
}
func (f *File) IsRename() bool {
@@ -92,6 +93,10 @@ func (f *File) GetPreviousPath() string {
return f.PreviousName
}
func (f *File) GetIsFile() bool {
return true
}
type StatusFields struct {
HasStagedChanges bool
HasUnstagedChanges bool
+6
View File
@@ -22,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
@@ -162,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)
}
+48
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
}
@@ -76,6 +77,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 +116,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()
+2
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,
}
}
@@ -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 {
+1 -2
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,
}
}
+3 -3
View File
@@ -2,7 +2,7 @@ package oscommands
import (
"fmt"
"io/ioutil"
"io"
"os"
"os/exec"
"path/filepath"
@@ -151,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)
}
@@ -215,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))
}
+4 -5
View File
@@ -1,7 +1,6 @@
package oscommands
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
@@ -149,7 +148,7 @@ func TestOSCommandAppendLineToFile(t *testing.T) {
{
filepath.Join(os.TempDir(), "testFile"),
func(path string) {
if err := ioutil.WriteFile(path, []byte("hello"), 0o600); err != nil {
if err := os.WriteFile(path, []byte("hello"), 0o600); err != nil {
panic(err)
}
},
@@ -160,7 +159,7 @@ func TestOSCommandAppendLineToFile(t *testing.T) {
{
filepath.Join(os.TempDir(), "emptyTestFile"),
func(path string) {
if err := ioutil.WriteFile(path, []byte(""), 0o600); err != nil {
if err := os.WriteFile(path, []byte(""), 0o600); err != nil {
panic(err)
}
},
@@ -171,7 +170,7 @@ func TestOSCommandAppendLineToFile(t *testing.T) {
{
filepath.Join(os.TempDir(), "testFileWithNewline"),
func(path string) {
if err := ioutil.WriteFile(path, []byte("hello\n"), 0o600); err != nil {
if err := os.WriteFile(path, []byte("hello\n"), 0o600); err != nil {
panic(err)
}
},
@@ -187,7 +186,7 @@ func TestOSCommandAppendLineToFile(t *testing.T) {
if err := osCommand.AppendLineToFile(s.path, "world"); err != nil {
panic(err)
}
f, err := ioutil.ReadFile(s.path)
f, err := os.ReadFile(s.path)
if err != nil {
panic(err)
}
+3 -4
View File
@@ -2,7 +2,6 @@ package config
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -152,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
}
@@ -243,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
@@ -263,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
}
+7 -5
View File
@@ -55,7 +55,6 @@ type GuiConfig struct {
}
type ThemeConfig struct {
LightTheme bool `yaml:"lightTheme"`
ActiveBorderColor []string `yaml:"activeBorderColor"`
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
OptionsTextColor []string `yaml:"optionsTextColor"`
@@ -265,7 +264,8 @@ type KeybindingCommitsConfig struct {
}
type KeybindingStashConfig struct {
PopStash string `yaml:"popStash"`
PopStash string `yaml:"popStash"`
RenameStash string `yaml:"renameStash"`
}
type KeybindingCommitFilesConfig struct {
@@ -314,6 +314,8 @@ type CustomCommand struct {
}
type CustomCommandPrompt struct {
Key string `yaml:"key"`
// one of 'input', 'menu', 'confirm', or 'menuFromCommand'
Type string `yaml:"type"`
@@ -355,9 +357,8 @@ 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"},
@@ -545,7 +546,8 @@ func GetDefaultConfig() *UserConfig {
ViewBisectOptions: "b",
},
Stash: KeybindingStashConfig{
PopStash: "g",
PopStash: "g",
RenameStash: "r",
},
CommitFiles: KeybindingCommitFilesConfig{
CheckoutCommitFile: "c",
+4
View File
@@ -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)
+19 -23
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
@@ -159,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
}
-380
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))
})
}
}
+1
View File
@@ -181,6 +181,7 @@ 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 {
+2 -2
View File
@@ -255,7 +255,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
return nil
}
if err := self.git.Branch.New(newBranchName, branch.Name); err != nil {
if err := self.git.Branch.New(newBranchName, branch.FullRefName()); err != nil {
return self.c.Error(err)
}
@@ -411,7 +411,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
}
func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
return self.helpers.Refs.NewBranch(selectedBranch.RefName(), selectedBranch.RefName(), "")
return self.helpers.Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
}
func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error {
+23 -11
View File
@@ -758,18 +758,31 @@ func (self *FilesController) createStashMenu() error {
{
Label: self.c.Tr.LcStashAllChanges,
OnPress: func() error {
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges, self.c.Tr.NoFilesToStash)
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
}
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
},
Key: 'a',
},
{
Label: self.c.Tr.LcStashAllChangesKeepIndex,
OnPress: func() error {
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
}
// if there are no staged files it behaves the same as Stash.Save
return self.handleStashSave(self.git.Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex, self.c.Tr.NoFilesToStash)
return self.handleStashSave(self.git.Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
},
Key: 'i',
},
{
Label: self.c.Tr.LcStashIncludeUntrackedChanges,
OnPress: func() error {
return self.handleStashSave(self.git.Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
},
Key: 'U',
},
{
Label: self.c.Tr.LcStashStagedChanges,
OnPress: func() error {
@@ -777,18 +790,21 @@ func (self *FilesController) createStashMenu() error {
if !self.helpers.WorkingTree.AnyStagedFiles() {
return self.c.ErrorMsg(self.c.Tr.NoTrackedStagedFilesStash)
}
return self.handleStashSave(self.git.Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges, self.c.Tr.NoTrackedStagedFilesStash)
return self.handleStashSave(self.git.Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
},
Key: 's',
},
{
Label: self.c.Tr.LcStashUnstagedChanges,
OnPress: func() error {
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
}
if self.helpers.WorkingTree.AnyStagedFiles() {
return self.handleStashSave(self.git.Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges, self.c.Tr.NoFilesToStash)
return self.handleStashSave(self.git.Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
}
// ordinary stash
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashUnstagedChanges, self.c.Tr.NoFilesToStash)
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
},
Key: 'u',
},
@@ -797,7 +813,7 @@ func (self *FilesController) createStashMenu() error {
}
func (self *FilesController) stash() error {
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges, self.c.Tr.NoTrackedStagedFilesStash)
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
}
func (self *FilesController) createResetToUpstreamMenu() error {
@@ -825,11 +841,7 @@ func (self *FilesController) toggleTreeView() error {
return self.c.PostRefreshUpdate(self.context())
}
func (self *FilesController) handleStashSave(stashFunc func(message string) error, action string, errorMsg string) error {
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
return self.c.ErrorMsg(errorMsg)
}
func (self *FilesController) handleStashSave(stashFunc func(message string) error, action string) error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.StashChanges,
HandleConfirm: func(stashComment string) error {
@@ -122,10 +122,6 @@ func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string
// loading a heap of commits is slow so we limit them whenever doing a reset
self.contexts.LocalCommits.SetLimitCommits(true)
if err := self.c.PushContext(self.contexts.LocalCommits); err != nil {
return err
}
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}}); err != nil {
return err
}
@@ -1,7 +1,7 @@
package controllers
import (
"io/ioutil"
"os"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
@@ -192,7 +192,7 @@ func (self *MergeConflictsController) HandleUndo() error {
self.c.LogAction("Restoring file to previous state")
self.c.LogCommand("Undoing last conflict resolution", false)
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
if err := os.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
return err
}
@@ -280,7 +280,7 @@ func (self *MergeConflictsController) resolveConflict(selection mergeconflicts.S
self.c.LogAction("Resolve merge conflict")
self.c.LogCommand(logStr, false)
state.PushContent(content)
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
return true, os.WriteFile(state.GetPath(), []byte(content), 0o644)
}
func (self *MergeConflictsController) onLastConflictResolved() error {
+30
View File
@@ -4,6 +4,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type StashController struct {
@@ -44,6 +45,11 @@ func (self *StashController) GetKeybindings(opts types.KeybindingsOpts) []*types
Handler: self.checkSelected(self.handleNewBranchOffStashEntry),
Description: self.c.Tr.LcNewBranch,
},
{
Key: opts.GetKey(opts.Config.Stash.RenameStash),
Handler: self.checkSelected(self.handleRenameStashEntry),
Description: self.c.Tr.LcRenameStash,
},
}
return bindings
@@ -139,3 +145,27 @@ func (self *StashController) postStashRefresh() error {
func (self *StashController) handleNewBranchOffStashEntry(stashEntry *models.StashEntry) error {
return self.helpers.Refs.NewBranch(stashEntry.RefName(), stashEntry.Description(), "")
}
func (self *StashController) handleRenameStashEntry(stashEntry *models.StashEntry) error {
message := utils.ResolvePlaceholderString(
self.c.Tr.RenameStashPrompt,
map[string]string{
"stashName": stashEntry.RefName(),
},
)
return self.c.Prompt(types.PromptOpts{
Title: message,
InitialContent: stashEntry.Name,
HandleConfirm: func(response string) error {
self.c.LogAction(self.c.Tr.Actions.RenameStash)
err := self.git.Stash.Rename(stashEntry.Index, response)
_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
if err != nil {
return err
}
self.context().SetSelectedLineIdx(0) // Select the renamed stash
return nil
},
})
}
@@ -1,7 +1,7 @@
package controllers
import (
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -57,8 +57,8 @@ func (self *SwitchToSubCommitsController) viewCommits() error {
}
// need to populate my sub commits
commits, err := self.git.Loaders.Commits.GetCommits(
loaders.GetCommitsOptions{
commits, err := self.git.Loaders.CommitLoader.GetCommits(
git_commands.GetCommitsOptions{
Limit: true,
FilterPath: self.modes.Filtering.GetPath(),
IncludeRebaseCommits: false,
+12 -2
View File
@@ -22,9 +22,13 @@ func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch
textArea.MoveCursorDown()
case key == gocui.KeyArrowUp:
textArea.MoveCursorUp()
case key == gocui.KeyArrowLeft:
case key == gocui.KeyArrowLeft && (mod&gocui.ModAlt) != 0:
textArea.MoveLeftWord()
case key == gocui.KeyArrowLeft || key == gocui.KeyCtrlB:
textArea.MoveCursorLeft()
case key == gocui.KeyArrowRight:
case key == gocui.KeyArrowRight && (mod&gocui.ModAlt) != 0:
textArea.MoveRightWord()
case key == gocui.KeyArrowRight || key == gocui.KeyCtrlF:
textArea.MoveCursorRight()
case key == newlineKey:
if allowMultiline {
@@ -38,10 +42,16 @@ func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch
textArea.ToggleOverwrite()
case key == gocui.KeyCtrlU:
textArea.DeleteToStartOfLine()
case key == gocui.KeyCtrlK:
textArea.DeleteToEndOfLine()
case key == gocui.KeyCtrlA || key == gocui.KeyHome:
textArea.GoToStartOfLine()
case key == gocui.KeyCtrlE || key == gocui.KeyEnd:
textArea.GoToEndOfLine()
case key == gocui.KeyCtrlW:
textArea.BackSpaceWord()
case key == gocui.KeyCtrlY:
textArea.Yank()
// TODO: see if we need all three of these conditions: maybe the final one is sufficient
case ch != 0 && mod == 0 && unicode.IsPrint(ch):
+1
View File
@@ -89,6 +89,7 @@ func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
view := getView()
view.ClearTextArea()
view.TextArea.TypeString(text)
_ = gui.resizePopupPanel(view, view.TextArea.GetContent())
view.RenderTextArea()
}
}
+4
View File
@@ -42,6 +42,10 @@ func (self *FileNode) GetIsTracked() bool {
return self.SomeFile(func(file *models.File) bool { return file.Tracked })
}
func (self *FileNode) GetIsFile() bool {
return self.IsFile()
}
func (self *FileNode) GetPreviousPath() string {
if self.File == nil {
return ""
+10 -7
View File
@@ -2,7 +2,7 @@ package gui
import (
"fmt"
"io/ioutil"
"io"
"log"
"os"
"strings"
@@ -185,7 +185,7 @@ type GuiRepoState struct {
// WindowViewNameMap is a mapping of windows to the current view of that window.
// Some views move between windows for example the commitFiles view and when cycling through
// side windows we need to know which view to give focus to for a given window
WindowViewNameMap map[string]string
WindowViewNameMap *utils.ThreadSafeMap[string, string]
// tells us whether we've set up our views for the current repo. We'll need to
// do this whenever we switch back and forth between repos to get the views
@@ -494,8 +494,6 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
})
deadlock.Opts.Disable = !gui.Debug
gui.handleTestMode(startArgs.IntegrationTest)
gui.g.OnSearchEscape = gui.onSearchEscape
if err := gui.Config.ReloadUserConfig(); err != nil {
return nil
@@ -552,6 +550,8 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
gui.c.Log.Info("starting main loop")
gui.handleTestMode(startArgs.IntegrationTest)
return gui.g.MainLoop()
}
@@ -656,13 +656,16 @@ func (gui *Gui) runSubprocess(cmdObj oscommands.ICmdObj) error { //nolint:unpara
err := subprocess.Run()
subprocess.Stdout = ioutil.Discard
subprocess.Stderr = ioutil.Discard
subprocess.Stdout = io.Discard
subprocess.Stderr = io.Discard
subprocess.Stdin = nil
if gui.Config.GetUserConfig().PromptToReturnFromSubprocess {
fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint(gui.Tr.PressEnterToReturn))
fmt.Scanln() // wait for enter press
// scan to buffer to prevent run unintentional operations when TUI resumes.
var buffer string
fmt.Scanln(&buffer) // wait for enter press
}
return err
+8
View File
@@ -79,3 +79,11 @@ func (self *GuiDriver) MainView() *gocui.View {
func (self *GuiDriver) SecondaryView() *gocui.View {
return self.gui.secondaryView()
}
func (self *GuiDriver) View(viewName string) *gocui.View {
view, err := self.gui.g.View(viewName)
if err != nil {
panic(err)
}
return view
}
-1
View File
@@ -179,6 +179,5 @@ func GetKey(key string) types.Key {
} else if runeCount == 1 {
return []rune(key)[0]
}
log.Fatal("Key empty for keybinding: " + strings.ToLower(key))
return nil
}
+28 -4
View File
@@ -27,11 +27,35 @@ func (gui *Gui) runTaskForView(view *gocui.View, task types.UpdateTask) error {
}
func (gui *Gui) moveMainContextPairToTop(pair types.MainContextPair) {
gui.setWindowContext(pair.Main)
gui.moveToTopOfWindow(pair.Main)
gui.moveMainContextToTop(pair.Main)
if pair.Secondary != nil {
gui.setWindowContext(pair.Secondary)
gui.moveToTopOfWindow(pair.Secondary)
gui.moveMainContextToTop(pair.Secondary)
}
}
func (gui *Gui) moveMainContextToTop(context types.Context) {
gui.setWindowContext(context)
view := context.GetView()
topView := gui.topViewInWindow(context.GetWindowName())
if topView == nil {
gui.Log.Error("unexpected: topView is nil")
return
}
if topView != view {
// We need to copy the content to avoid a flicker effect: If we're flicking
// through files in the files panel, we use a different view to render the
// files vs the directories, and if you select dir A, then file B, then dir
// C, you'll briefly see dir A's contents again before the view is updated.
// So here we're copying the content from the top window to avoid that
// flicker effect.
gui.g.CopyContent(topView, view)
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
gui.Log.Error(err)
}
}
}
+9 -2
View File
@@ -53,10 +53,17 @@ func (gui *Gui) modeStatuses() []modeStatus {
{
isActive: gui.State.Modes.CherryPicking.Active,
description: func() string {
copiedCount := len(gui.State.Modes.CherryPicking.CherryPickedCommits)
text := gui.c.Tr.LcCommitsCopied
if copiedCount == 1 {
text = gui.c.Tr.LcCommitCopied
}
return gui.withResetButton(
fmt.Sprintf(
"%d commits copied",
len(gui.State.Modes.CherryPicking.CherryPickedCommits),
"%d %s",
copiedCount,
text,
),
style.FgCyan,
)
+1 -1
View File
@@ -58,7 +58,7 @@ func (gui *Gui) handleCreateOptionsMenu() error {
OpensMenu: binding.OpensMenu,
Label: binding.Description,
OnPress: func() error {
if binding.Key == nil {
if binding.Handler == nil {
return nil
}
+6 -1
View File
@@ -13,6 +13,7 @@ const (
COMMIT_ICON = "\ufc16" // ﰖ
MERGE_COMMIT_ICON = "\ufb2c" // שּׁ
DEFAULT_REMOTE_ICON = "\uf7a1" // 
STASH_ICON = "\uf01c" // 
)
type remoteIcon struct {
@@ -28,7 +29,7 @@ var remoteIcons = []remoteIcon{
}
func IconForBranch(branch *models.Branch) string {
if branch.DisplayName != "" {
if branch.DetachedHead {
return DETACHED_HEAD_ICON
}
return BRANCH_ICON
@@ -59,3 +60,7 @@ func IconForRemote(remote *models.Remote) string {
}
return DEFAULT_REMOTE_ICON
}
func IconForStash(stash *models.StashEntry) string {
return STASH_ICON
}
+8 -1
View File
@@ -3,6 +3,7 @@ package presentation
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
"github.com/jesseduffield/lazygit/pkg/theme"
)
@@ -19,5 +20,11 @@ func getStashEntryDisplayStrings(s *models.StashEntry, diffed bool) []string {
if diffed {
textStyle = theme.DiffTerminalColor
}
return []string{textStyle.Sprint(s.Name)}
res := make([]string, 0, 2)
if icons.IsIconEnabled() {
res = append(res, textStyle.Sprint(icons.IconForStash(s)))
}
res = append(res, textStyle.Sprint(s.Name))
return res
}
+2 -3
View File
@@ -2,7 +2,6 @@ package gui
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -20,7 +19,7 @@ import (
func (gui *Gui) getCurrentBranch(path string) string {
readHeadFile := func(path string) (string, error) {
headFile, err := ioutil.ReadFile(filepath.Join(path, "HEAD"))
headFile, err := os.ReadFile(filepath.Join(path, "HEAD"))
if err == nil {
content := strings.TrimSpace(string(headFile))
refsPrefix := "ref: refs/heads/"
@@ -47,7 +46,7 @@ func (gui *Gui) getCurrentBranch(path string) string {
}
} else {
// worktree
if worktreeGitDir, err := ioutil.ReadFile(gitDirPath); err == nil {
if worktreeGitDir, err := os.ReadFile(gitDirPath); err == nil {
content := strings.TrimSpace(string(worktreeGitDir))
worktreePath := strings.TrimPrefix(content, "gitdir: ")
if branch, err := readHeadFile(worktreePath); err == nil {
+13 -13
View File
@@ -7,7 +7,7 @@ import (
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/context"
@@ -223,8 +223,8 @@ func (gui *Gui) refreshCommitsWithLimit() error {
gui.Mutexes.LocalCommitsMutex.Lock()
defer gui.Mutexes.LocalCommitsMutex.Unlock()
commits, err := gui.git.Loaders.Commits.GetCommits(
loaders.GetCommitsOptions{
commits, err := gui.git.Loaders.CommitLoader.GetCommits(
git_commands.GetCommitsOptions{
Limit: gui.State.Contexts.LocalCommits.GetLimitCommits(),
FilterPath: gui.State.Modes.Filtering.GetPath(),
IncludeRebaseCommits: true,
@@ -245,7 +245,7 @@ func (gui *Gui) refreshCommitFilesContext() error {
to := ref.RefName()
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
files, err := gui.git.Loaders.CommitFiles.GetFilesInDiff(from, to, reverse)
files, err := gui.git.Loaders.CommitFileLoader.GetFilesInDiff(from, to, reverse)
if err != nil {
return gui.c.Error(err)
}
@@ -259,7 +259,7 @@ func (gui *Gui) refreshRebaseCommits() error {
gui.Mutexes.LocalCommitsMutex.Lock()
defer gui.Mutexes.LocalCommitsMutex.Unlock()
updatedCommits, err := gui.git.Loaders.Commits.MergeRebasingCommits(gui.State.Model.Commits)
updatedCommits, err := gui.git.Loaders.CommitLoader.MergeRebasingCommits(gui.State.Model.Commits)
if err != nil {
return err
}
@@ -269,7 +269,7 @@ func (gui *Gui) refreshRebaseCommits() error {
}
func (self *Gui) refreshTags() error {
tags, err := self.git.Loaders.Tags.GetTags()
tags, err := self.git.Loaders.TagLoader.GetTags()
if err != nil {
return self.c.Error(err)
}
@@ -300,13 +300,13 @@ func (gui *Gui) refreshBranches() {
// which allows us to order them correctly. So if we're filtering we'll just
// manually load all the reflog commits here
var err error
reflogCommits, _, err = gui.git.Loaders.ReflogCommits.GetReflogCommits(nil, "")
reflogCommits, _, err = gui.git.Loaders.ReflogCommitLoader.GetReflogCommits(nil, "")
if err != nil {
gui.c.Log.Error(err)
}
}
branches, err := gui.git.Loaders.Branches.Load(reflogCommits)
branches, err := gui.git.Loaders.BranchLoader.Load(reflogCommits)
if err != nil {
_ = gui.c.Error(err)
}
@@ -406,8 +406,8 @@ func (gui *Gui) refreshStateFiles() error {
}
}
files := gui.git.Loaders.Files.
GetStatusFiles(loaders.GetStatusFileOptions{})
files := gui.git.Loaders.FileLoader.
GetStatusFiles(git_commands.GetStatusFileOptions{})
conflictFileCount := 0
for _, file := range files {
@@ -463,7 +463,7 @@ func (gui *Gui) refreshReflogCommits() error {
}
refresh := func(stateCommits *[]*models.Commit, filterPath string) error {
commits, onlyObtainedNewReflogCommits, err := gui.git.Loaders.ReflogCommits.
commits, onlyObtainedNewReflogCommits, err := gui.git.Loaders.ReflogCommitLoader.
GetReflogCommits(lastReflogCommit, filterPath)
if err != nil {
return gui.c.Error(err)
@@ -495,7 +495,7 @@ func (gui *Gui) refreshReflogCommits() error {
func (gui *Gui) refreshRemotes() error {
prevSelectedRemote := gui.State.Contexts.Remotes.GetSelected()
remotes, err := gui.git.Loaders.Remotes.GetRemotes()
remotes, err := gui.git.Loaders.RemoteLoader.GetRemotes()
if err != nil {
return gui.c.Error(err)
}
@@ -525,7 +525,7 @@ func (gui *Gui) refreshRemotes() error {
}
func (gui *Gui) refreshStashEntries() error {
gui.State.Model.StashEntries = gui.git.Loaders.Stash.
gui.State.Model.StashEntries = gui.git.Loaders.StashLoader.
GetStashEntries(gui.State.Modes.Filtering.GetPath())
return gui.postRefreshUpdate(gui.State.Contexts.Stash)
@@ -2,6 +2,7 @@ package custom_commands
import (
"strings"
"text/template"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands"
@@ -45,8 +46,9 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
return func() error {
sessionState := self.sessionStateLoader.call()
promptResponses := make([]string, len(customCommand.Prompts))
form := make(map[string]string)
f := func() error { return self.finalHandler(customCommand, sessionState, promptResponses) }
f := func() error { return self.finalHandler(customCommand, sessionState, promptResponses, form) }
// if we have prompts we'll recursively wrap our confirm handlers with more prompts
// until we reach the actual command
@@ -60,10 +62,11 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
wrappedF := func(response string) error {
promptResponses[idx] = response
form[prompt.Key] = response
return g()
}
resolveTemplate := self.getResolveTemplateFn(promptResponses, sessionState)
resolveTemplate := self.getResolveTemplateFn(form, promptResponses, sessionState)
resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate)
if err != nil {
return self.c.Error(err)
@@ -154,19 +157,25 @@ func (self *HandlerCreator) menuPromptFromCommand(prompt *config.CustomCommandPr
type CustomCommandObjects struct {
*SessionState
PromptResponses []string
Form map[string]string
}
func (self *HandlerCreator) getResolveTemplateFn(promptResponses []string, sessionState *SessionState) func(string) (string, error) {
func (self *HandlerCreator) getResolveTemplateFn(form map[string]string, promptResponses []string, sessionState *SessionState) func(string) (string, error) {
objects := CustomCommandObjects{
SessionState: sessionState,
PromptResponses: promptResponses,
Form: form,
}
return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects) }
funcs := template.FuncMap{
"quote": self.os.Quote,
}
return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects, funcs) }
}
func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, sessionState *SessionState, promptResponses []string) error {
resolveTemplate := self.getResolveTemplateFn(promptResponses, sessionState)
func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, sessionState *SessionState, promptResponses []string, form map[string]string) error {
resolveTemplate := self.getResolveTemplateFn(form, promptResponses, sessionState)
cmdStr, err := resolveTemplate(customCommand.Command)
if err != nil {
return self.c.Error(err)
@@ -1,9 +1,6 @@
package custom_commands
import (
"bytes"
"text/template"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
)
@@ -110,17 +107,3 @@ type CustomCommandObject struct {
PromptResponses []string
Form map[string]string
}
func ResolveTemplate(templateStr string, object interface{}) (string, error) {
tmpl, err := template.New("template").Parse(templateStr)
if err != nil {
return "", err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, object); err != nil {
return "", err
}
return buf.String(), nil
}
+1
View File
@@ -27,6 +27,7 @@ var (
BgBlue = FromBasicBg(color.BgBlue)
BgMagenta = FromBasicBg(color.BgMagenta)
BgCyan = FromBasicBg(color.BgCyan)
BgDefault = FromBasicBg(color.BgDefault)
// will not print any colour escape codes, including the reset escape code
Nothing = New()
+23
View File
@@ -1,6 +1,8 @@
package style
import (
"os"
"github.com/gookit/color"
)
@@ -25,6 +27,23 @@ import (
// a string, we derive it when a new TextStyle is created and store it in the
// `style` field.
// See https://github.com/xtermjs/xterm.js/issues/4238
// VSCode is soon to fix this in an upcoming update.
// Once that's done, we can scrap the HIDE_UNDERSCORES variable
var (
underscoreEnvChecked bool
hideUnderscores bool
)
func hideUnderScores() bool {
if !underscoreEnvChecked {
hideUnderscores = os.Getenv("TERM_PROGRAM") == "vscode"
underscoreEnvChecked = true
}
return hideUnderscores
}
type TextStyle struct {
fg *Color
bg *Color
@@ -64,6 +83,10 @@ func (b TextStyle) SetBold() TextStyle {
}
func (b TextStyle) SetUnderline() TextStyle {
if hideUnderScores() {
return b
}
b.decoration.SetUnderline()
b.Style = b.deriveStyle()
return b
+3 -4
View File
@@ -2,7 +2,6 @@ package gui
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"strconv"
@@ -42,7 +41,7 @@ func (gui *Gui) handleTestMode(test integrationTypes.IntegrationTest) {
if Replaying() {
gui.g.RecordingConfig = gocui.RecordingConfig{
Speed: GetRecordingSpeed(),
Leeway: 100,
Leeway: 1000,
}
var err error
@@ -93,7 +92,7 @@ func GetRecordingSpeed() float64 {
func LoadRecording() (*gocui.Recording, error) {
path := os.Getenv("REPLAY_EVENTS_FROM")
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
@@ -120,5 +119,5 @@ func SaveRecording(recording *gocui.Recording) error {
path := recordEventsTo()
return ioutil.WriteFile(path, jsonEvents, 0o600)
return os.WriteFile(path, jsonEvents, 0o600)
}
+5 -1
View File
@@ -87,7 +87,11 @@ func (gui *Gui) resizeConfirmationPanel() {
}
panelWidth := gui.getConfirmationPanelWidth()
prompt := gui.Views.Confirmation.Buffer()
wrap := !gui.Views.Confirmation.Editable
wrap := true
if gui.Views.Confirmation.Editable {
prompt = gui.Views.Confirmation.TextArea.GetContent()
wrap = false
}
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
confirmationViewBottom := y1 - suggestionsViewHeight
+34 -14
View File
@@ -3,8 +3,10 @@ package gui
import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -14,18 +16,18 @@ import (
// space. Right now most windows are 1:1 with views, except for commitFiles which
// is a view that moves between windows
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) map[string]string {
result := map[string]string{}
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSafeMap[string, string] {
result := utils.NewThreadSafeMap[string, string]()
for _, context := range contextTree.Flatten() {
result[context.GetWindowName()] = context.GetViewName()
result.Set(context.GetWindowName(), context.GetViewName())
}
return result
}
func (gui *Gui) getViewNameForWindow(window string) string {
viewName, ok := gui.State.WindowViewNameMap[window]
viewName, ok := gui.State.WindowViewNameMap.Get(window)
if !ok {
panic(fmt.Sprintf("Viewname not found for window: %s", window))
}
@@ -50,7 +52,7 @@ func (gui *Gui) setWindowContext(c types.Context) {
gui.resetWindowContext(c)
}
gui.State.WindowViewNameMap[c.GetWindowName()] = c.GetViewName()
gui.State.WindowViewNameMap.Set(c.GetWindowName(), c.GetViewName())
}
func (gui *Gui) currentWindow() string {
@@ -59,39 +61,57 @@ func (gui *Gui) currentWindow() string {
// assumes the context's windowName has been set to the new window if necessary
func (gui *Gui) resetWindowContext(c types.Context) {
for windowName, viewName := range gui.State.WindowViewNameMap {
for _, windowName := range gui.State.WindowViewNameMap.Keys() {
viewName, ok := gui.State.WindowViewNameMap.Get(windowName)
if !ok {
continue
}
if viewName == c.GetViewName() && windowName != c.GetWindowName() {
for _, context := range gui.State.Contexts.Flatten() {
if context.GetKey() != c.GetKey() && context.GetWindowName() == windowName {
gui.State.WindowViewNameMap[windowName] = context.GetViewName()
gui.State.WindowViewNameMap.Set(windowName, context.GetViewName())
}
}
}
}
}
func (gui *Gui) moveToTopOfWindow(context types.Context) {
// moves given context's view to the top of the window and returns
// true if the view was not already on top.
func (gui *Gui) moveToTopOfWindow(context types.Context) bool {
view := context.GetView()
if view == nil {
return
return false
}
window := context.GetWindowName()
topView := gui.topViewInWindow(window)
if view.Name() == topView.Name() {
return false
} else {
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
gui.Log.Error(err)
}
return true
}
}
func (gui *Gui) topViewInWindow(windowName string) *gocui.View {
// now I need to find all views in that same window, via contexts. And I guess then I need to find the index of the highest view in that list.
viewNamesInWindow := gui.viewNamesInWindow(window)
viewNamesInWindow := gui.viewNamesInWindow(windowName)
// The views list is ordered highest-last, so we're grabbing the last view of the window
topView := view
var topView *gocui.View
for _, currentView := range gui.g.Views() {
if lo.Contains(viewNamesInWindow, currentView.Name()) {
topView = currentView
}
}
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
gui.Log.Error(err)
}
return topView
}
func (gui *Gui) viewNamesInWindow(windowName string) []string {
+3
View File
@@ -139,6 +139,8 @@ func chineseTranslationSet() TranslationSet {
SureApplyStashEntry: "您确定要应用此贮藏条目?",
NoTrackedStagedFilesStash: "没有可以贮藏的已跟踪/暂存文件",
StashChanges: "贮藏更改",
LcRenameStash: "rename stash",
RenameStashPrompt: "Rename stash: {{.stashName}}",
OpenConfig: "打开配置文件",
EditConfig: "编辑配置文件",
ForcePush: "强制推送",
@@ -530,6 +532,7 @@ func chineseTranslationSet() TranslationSet {
UpdateRemote: "更新远程",
ApplyPatch: "应用补丁",
Stash: "贮藏 (Stash)",
RenameStash: "Rename stash",
RemoveSubmodule: "删除子模块",
ResetSubmodule: "重置子模块",
AddSubmodule: "添加子模块",
+2
View File
@@ -105,6 +105,8 @@ func dutchTranslationSet() TranslationSet {
SureApplyStashEntry: "Weet je zeker dat je deze stash entry wil toepassen?",
NoTrackedStagedFilesStash: "Je hebt geen tracked/staged bestanden om te laten stashen",
StashChanges: "Stash veranderingen",
LcRenameStash: "rename stash",
RenameStashPrompt: "Rename stash: {{.stashName}}",
NoChangedFiles: "Geen veranderde bestanden",
OpenConfig: "open config bestand",
EditConfig: "verander config bestand",
+14
View File
@@ -128,6 +128,8 @@ type TranslationSet struct {
NoTrackedStagedFilesStash string
NoFilesToStash string
StashChanges string
LcRenameStash string
RenameStashPrompt string
OpenConfig string
EditConfig string
ForcePush string
@@ -293,6 +295,7 @@ type TranslationSet struct {
LcStashStagedChanges string
LcStashAllChangesKeepIndex string
LcStashUnstagedChanges string
LcStashIncludeUntrackedChanges string
LcStashOptions string
NotARepository string
LcJump string
@@ -507,6 +510,8 @@ type TranslationSet struct {
EmptyOutput string
Patch string
CustomPatch string
LcCommitsCopied string
LcCommitCopied string
Actions Actions
Bisect Bisect
}
@@ -584,6 +589,7 @@ type Actions struct {
StashAllChangesKeepIndex string
StashStagedChanges string
StashUnstagedChanges string
StashIncludeUntrackedChanges string
GitFlowFinish string
GitFlowStart string
CopyToClipboard string
@@ -599,6 +605,7 @@ type Actions struct {
UpdateRemote string
ApplyPatch string
Stash string
RenameStash string
RemoveSubmodule string
ResetSubmodule string
AddSubmodule string
@@ -767,6 +774,8 @@ func EnglishTranslationSet() TranslationSet {
NoTrackedStagedFilesStash: "You have no tracked/staged files to stash",
NoFilesToStash: "You have no files to stash",
StashChanges: "Stash changes",
LcRenameStash: "rename stash",
RenameStashPrompt: "Rename stash: {{.stashName}}",
OpenConfig: "open config file",
EditConfig: "edit config file",
ForcePush: "Force push",
@@ -933,6 +942,7 @@ func EnglishTranslationSet() TranslationSet {
LcStashStagedChanges: "stash staged changes",
LcStashAllChangesKeepIndex: "stash all changes and keep index",
LcStashUnstagedChanges: "stash unstaged changes",
LcStashIncludeUntrackedChanges: "stash all changes including untracked files",
LcStashOptions: "Stash options",
NotARepository: "Error: must be run inside a git repository",
LcJump: "jump to panel",
@@ -1147,6 +1157,8 @@ func EnglishTranslationSet() TranslationSet {
EmptyOutput: "<empty output>",
Patch: "Patch",
CustomPatch: "Custom patch",
LcCommitsCopied: "commits copied",
LcCommitCopied: "commit copied",
Actions: Actions{
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
CheckoutCommit: "Checkout commit",
@@ -1207,6 +1219,7 @@ func EnglishTranslationSet() TranslationSet {
StashAllChangesKeepIndex: "Stash all changes and keep index",
StashStagedChanges: "Stash staged changes",
StashUnstagedChanges: "Stash unstaged changes",
StashIncludeUntrackedChanges: "Stash all changes including untracked files",
GitFlowFinish: "Git flow finish",
GitFlowStart: "Git Flow start",
CopyToClipboard: "Copy to clipboard",
@@ -1222,6 +1235,7 @@ func EnglishTranslationSet() TranslationSet {
UpdateRemote: "Update remote",
ApplyPatch: "Apply patch",
Stash: "Stash",
RenameStash: "Rename stash",
RemoveSubmodule: "Remove submodule",
ResetSubmodule: "Reset submodule",
AddSubmodule: "Add submodule",
+3
View File
@@ -130,6 +130,8 @@ func japaneseTranslationSet() TranslationSet {
SureApplyStashEntry: "Stashを適用します。よろしいですか?",
// NoTrackedStagedFilesStash: "You have no tracked/staged files to stash",
StashChanges: "変更をStash",
LcRenameStash: "Stashを変更",
RenameStashPrompt: "Stash名を変更: {{.stashName}}",
OpenConfig: "設定ファイルを開く",
EditConfig: "設定ファイルを編集",
ForcePush: "Force push",
@@ -556,6 +558,7 @@ func japaneseTranslationSet() TranslationSet {
UpdateRemote: "リモートを更新",
ApplyPatch: "パッチを適用",
Stash: "Stash",
RenameStash: "Stash名を変更",
RemoveSubmodule: "サブモジュールを削除",
ResetSubmodule: "サブモジュールをリセット",
AddSubmodule: "サブモジュールを追加",
+3
View File
@@ -131,6 +131,8 @@ func koreanTranslationSet() TranslationSet {
SureApplyStashEntry: "정말로 Stash를 적용하시겠습니까?",
NoTrackedStagedFilesStash: "You have no tracked/staged files to stash",
StashChanges: "변경을 Stash",
LcRenameStash: "rename stash",
RenameStashPrompt: "Rename stash: {{.stashName}}",
OpenConfig: "설정 파일 열기",
EditConfig: "설정 파일 수정",
ForcePush: "강제 푸시",
@@ -561,6 +563,7 @@ func koreanTranslationSet() TranslationSet {
UpdateRemote: "Update remote",
ApplyPatch: "Apply patch",
Stash: "Stash",
RenameStash: "Rename stash",
RemoveSubmodule: "서브모듈 삭제",
ResetSubmodule: "서브모듈 Reset",
AddSubmodule: "서브모듈 추가",
+2
View File
@@ -83,6 +83,8 @@ func polishTranslationSet() TranslationSet {
SureDropStashEntry: "Jesteś pewny, że chcesz porzucić tę pozycję w schowku?",
NoTrackedStagedFilesStash: "Nie masz śledzonych/zatwierdzonych plików do przechowania",
StashChanges: "Przechowaj zmiany",
LcRenameStash: "rename stash",
RenameStashPrompt: "Rename stash: {{.stashName}}",
OpenConfig: "otwórz konfigurację",
EditConfig: "edytuj konfigurację",
ForcePush: "Wymuś wysłanie",
+14 -3
View File
@@ -11,7 +11,7 @@ go run cmd/integration_test/main.go tui
or
```sh
go run cmd/integration_test/main.go cli [--slow] [testname or testpath...]
go run cmd/integration_test/main.go cli [--slow or --sandbox] [testname or testpath...]
```
## Writing tests
@@ -49,7 +49,7 @@ If you find yourself doing something frequently in a test, consider making it a
There are three ways to invoke a test:
1. go run cmd/integration_test/main.go cli [--slow] [testname or testpath...]
1. go run cmd/integration_test/main.go cli [--slow or --sandbox] [testname or testpath...]
2. go run cmd/integration_test/main.go tui
3. go test pkg/integration/clients/go_test.go
@@ -61,6 +61,15 @@ The name of a test is based on its path, so the name of the test at `pkg/integra
You can pass the KEY_PRESS_DELAY env var to the test runner in order to set a delay in milliseconds between keypresses, which helps for watching a test at a realistic speed to understand what it's doing. Or you can pass the '--slow' flag which sets a pre-set 'slow' key delay. In the tui you can press 't' to run the test in slow mode.
### Running tests in VSCode
If you've opened an integration test file in your editor you can run that file by bringing up the command panel with `cmd+shift+p` and typing 'run task', then selecting the test task you want to run
![image](https://user-images.githubusercontent.com/8456633/201500427-b86e129f-5f35-4d55-b7bd-fff5d8e4a04e.png)
![image](https://user-images.githubusercontent.com/8456633/201500431-903deb8c-c210-4054-8514-ab7088c7a839.png)
The test will run in a VSCode terminal:
![image](https://user-images.githubusercontent.com/8456633/201500446-b87abf11-9653-438f-8a9a-e0bf8abdb7ee.png)
### Snapshots
At the moment (this is subject to change) each test has a snapshot repo created after running for the first time. These snapshots live in `test/integration_new`, in folders named 'expected' (alongside the 'actual' folders which contain the resulting repo from the last test run). Whenever you run a test, the resultant repo will be compared against the snapshot repo and if they're different, you'll be asked whether you want to update the snapshot. If you want to update a snapshot without being prompted you can pass MODE=update to the test runner.
@@ -69,7 +78,7 @@ At the moment (this is subject to change) each test has a snapshot repo created
Say you want to do a manual test of how lazygit handles merge-conflicts, but you can't be bothered actually finding a way to create merge conflicts in a repo. To make your life easier, you can simply run a merge-conflicts test in sandbox mode, meaning the setup step is run for you, and then instead of the test driving the lazygit session, you're allowed to drive it yourself.
To run a test in sandbox mode you can press 's' on a test in the test TUI or pass the env var MODE=sandbox to the test runner.
To run a test in sandbox mode you can press 's' on a test in the test TUI or in the test runner pass MODE=sandbox or the --sandbox argument.
## Migration process
@@ -84,3 +93,5 @@ go run pkg/integration/deprecated/cmd/tui/main.go
```
The tests in the old format live in test/integration. In the old format, test definitions are co-located with the snapshots. The setup step is done in a `setup.sh` shell script and the `recording.json` file contains the recorded keypresses to be replayed during the test.
If you have rewritten an integration test under the new pattern, be sure to delete the old integration test directory.
+12 -4
View File
@@ -23,26 +23,34 @@ import (
// If invoked directly, you can specify tests to run by passing their names as positional arguments
func RunCLI(testNames []string, slow bool) {
func RunCLI(testNames []string, slow bool, sandbox bool) {
keyPressDelay := tryConvert(os.Getenv("KEY_PRESS_DELAY"), 0)
if slow {
keyPressDelay = SLOW_KEY_PRESS_DELAY
}
var mode components.Mode
if sandbox {
mode = components.SANDBOX
} else {
mode = getModeFromEnv()
}
err := components.RunTests(
getTestsToRun(testNames),
log.Printf,
runCmdInTerminal,
runAndPrintError,
getModeFromEnv(),
runAndPrintFatalError,
mode,
keyPressDelay,
1,
)
if err != nil {
log.Print(err.Error())
}
}
func runAndPrintError(test *components.IntegrationTest, f func() error) {
func runAndPrintFatalError(test *components.IntegrationTest, f func() error) {
if err := f(); err != nil {
log.Fatalf(err.Error())
}

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