Compare commits

..
Author SHA1 Message Date
Jesse Duffield 954a4d2fbb My commit summary
My commit description
2023-09-29 10:46:02 +10:00
75 changed files with 503 additions and 1125 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
- **Please check if the PR fulfills these requirements**
* [ ] Cheatsheets are up-to-date (run `go generate ./...`)
* [ ] Cheatsheets are up-to-date (run `go run scripts/cheatsheet/main.go generate`)
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
+6 -3
View File
@@ -127,14 +127,17 @@ jobs:
uses: actions/setup-go@v4
with:
go-version: 1.20.x
- name: Check Cheatsheet
run: |
go run scripts/cheatsheet/main.go check
- name: Check Vendor Directory
# ensure our vendor directory matches up with our go modules
run: |
go mod vendor && git diff --exit-code || (echo "Unexpected change to vendor directory. Run 'go mod vendor' locally and commit the changes" && exit 1)
- name: Check All Auto-Generated Files
# ensure all our auto-generated files are up to date
- name: Check Integration Test List
# ensure our integration test list is up to date
run: |
go generate ./... && git diff --quiet || (git status -s; echo "Auto-generated files not up to date. Run 'go generate ./...' locally and commit the changes" && exit 1)
go generate pkg/integration/tests/tests.go && git diff --exit-code || (echo "Integration test list not up to date. Run 'go generate pkg/integration/tests/tests.go' locally and commit the changes" && exit 1)
shell: bash # needed so that we get "-o pipefail"
- name: Check Filenames
run: scripts/check_filenames.sh
+1 -5
View File
@@ -20,11 +20,7 @@ linters:
linters-settings:
exhaustive:
default-signifies-exhaustive: true
staticcheck:
# SA1019 is for checking that we're not using fields marked as deprecated
# in a comment. It decides this in a loose way so I'm silencing it. Also because
# it's tripping on our own structs.
checks: ["all", "-SA1019"]
nakedret:
# the gods will judge me but I just don't like naked returns at all
max-func-lines: 0
-5
View File
@@ -1,5 +0,0 @@
{
"gopls": {
"formatting.gofumpt": true,
},
}
+5 -2
View File
@@ -3,7 +3,7 @@ all: build
.PHONY: build
build:
go build -gcflags='all=-N -l'
go build
.PHONY: install
install:
@@ -29,7 +29,6 @@ unit-test:
.PHONY: test
test: unit-test integration-test-all
# Generate all our auto-generated files (test list, cheatsheets, maybe other things in the future)
.PHONY: generate
generate:
go generate ./...
@@ -38,6 +37,10 @@ generate:
format:
gofumpt -l -w .
.PHONY: update-cheatsheet
update-cheatsheet:
go run scripts/cheatsheet/main.go generate
# For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md.
.PHONY: integration-test-tui
integration-test-tui:
+4 -8
View File
File diff suppressed because one or more lines are too long
+4 -5
View File
@@ -85,7 +85,6 @@ gui:
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
border: 'rounded' # one of 'single' | 'double' | 'rounded' | 'hidden'
animateExplosion: true # shows an explosion animation when nuking the working tree
portraitMode: 'auto' # one of 'auto' | 'never' | 'always'
git:
paging:
colorArg: always
@@ -163,7 +162,7 @@ keybinding:
jumpToBlock: ['1', '2', '3', '4', '5'] # goto the Nth block / panel
nextMatch: 'n'
prevMatch: 'N'
optionMenu: <disabled> # show help menu
optionMenu: null # show help menu
optionMenu-alt1: '?' # show help menu
select: '<space>'
goInto: '<enter>'
@@ -319,7 +318,7 @@ os:
editPreset: 'vscode'
```
Supported presets are `vim`, `nvim`, `nvim-remote`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
Supported presets are `vim`, `nvim`, `nvim-remote`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
`nvim-remote` is an experimental preset for when you have invoked lazygit from within a neovim process, allowing lazygit to open the file from within the parent process rather than spawning a new one.
@@ -462,12 +461,12 @@ Supported versions are "2" and "3". The deprecated config `showIcons` sets the v
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 `<disabled>`.
You can disable certain key bindings by specifying `null`.
```yaml
keybinding:
universal:
edit: <disabled> # disable 'edit file'
edit: null # disable 'edit file'
```
### Example Keybindings For Colemak Users
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Keybindings
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit キーバインド
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 키 바인딩
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Sneltoetsen
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Keybindings
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Связки клавиш
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 按键绑定
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 鍵盤快捷鍵
+2 -2
View File
@@ -180,10 +180,10 @@ func parseCliArgsAndEnvVars() *cliArgs {
useConfigDir := ""
flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory")
workTree := os.Getenv("GIT_WORK_TREE")
workTree := ""
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
gitDir := os.Getenv("GIT_DIR")
gitDir := ""
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
customConfigFile := ""
+77
View File
@@ -0,0 +1,77 @@
package cheatsheet
import (
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"regexp"
"github.com/pmezard/go-difflib/difflib"
)
func Check() {
dir := GetKeybindingsDir()
tmpDir := filepath.Join(os.TempDir(), "lazygit_cheatsheet")
err := os.RemoveAll(tmpDir)
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
err = os.Mkdir(tmpDir, 0o700)
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
generateAtDir(tmpDir)
defer os.RemoveAll(tmpDir)
actualContent := obtainContent(dir)
expectedContent := obtainContent(tmpDir)
if expectedContent == "" {
log.Fatal("empty expected content")
}
if actualContent != expectedContent {
err := difflib.WriteUnifiedDiff(os.Stdout, difflib.UnifiedDiff{
A: difflib.SplitLines(expectedContent),
B: difflib.SplitLines(actualContent),
FromFile: "Expected",
FromDate: "",
ToFile: "Actual",
ToDate: "",
Context: 1,
})
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
fmt.Printf("\nCheatsheets are out of date. Please run `%s` at the project root and commit the changes. If you run the script and no keybindings files are updated as a result, try rebasing onto master and trying again.\n", CommandToRun())
os.Exit(1)
}
fmt.Println("\nCheatsheets are up to date")
}
func obtainContent(dir string) string {
re := regexp.MustCompile(`Keybindings_\w+\.md$`)
content := ""
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if re.MatchString(path) {
bytes, err := os.ReadFile(path)
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
content += fmt.Sprintf("\n%s\n\n", filepath.Base(path))
content += string(bytes)
}
return nil
})
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
return content
}
+6 -8
View File
@@ -1,12 +1,10 @@
//go:generate go run generator.go
// This "script" generates files called Keybindings_{{.LANG}}.md
// in the docs/keybindings directory.
// This "script" generates a file called Keybindings_{{.LANG}}.md
// in current working directory.
//
// The content of these generated files is a keybindings cheatsheet.
// The content of this generated file is a keybindings cheatsheet.
//
// To generate the cheatsheets, run:
// go generate pkg/cheatsheet/generate.go
// To generate cheatsheet in english run:
// go run scripts/generate_cheatsheet.go
package cheatsheet
@@ -44,7 +42,7 @@ type headerWithBindings struct {
}
func CommandToRun() string {
return "go generate ./..."
return "go run scripts/cheatsheet/main.go generate"
}
func GetKeybindingsDir() string {
-14
View File
@@ -1,14 +0,0 @@
//go:build ignore
package main
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/cheatsheet"
)
func main() {
fmt.Printf("Generating cheatsheets in %s...\n", cheatsheet.GetKeybindingsDir())
cheatsheet.Generate()
}
+5 -4
View File
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/go-errors/errors"
"github.com/sasha-s/go-deadlock"
"github.com/spf13/afero"
gogit "github.com/jesseduffield/go-git/v5"
@@ -47,7 +48,6 @@ type GitCommand struct {
type Loaders struct {
BranchLoader *git_commands.BranchLoader
MergedBranchLoader *git_commands.MergedBranchLoader
CommitFileLoader *git_commands.CommitFileLoader
CommitLoader *git_commands.CommitLoader
FileLoader *git_commands.FileLoader
@@ -63,6 +63,7 @@ func NewGitCommand(
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
syncMutex *deadlock.Mutex,
) (*GitCommand, error) {
currentPath, err := os.Getwd()
if err != nil {
@@ -117,6 +118,7 @@ func NewGitCommand(
gitConfig,
repoPaths,
repository,
syncMutex,
), nil
}
@@ -127,6 +129,7 @@ func NewGitCommandAux(
gitConfig git_config.IGitConfig,
repoPaths *git_commands.RepoPaths,
repo *gogit.Repository,
syncMutex *deadlock.Mutex,
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
@@ -137,7 +140,7 @@ func NewGitCommandAux(
// common ones are: cmn, osCommand, dotGitDir, configCommands
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands, syncMutex)
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
statusCommands := git_commands.NewStatusCommands(gitCommon)
@@ -163,7 +166,6 @@ func NewGitCommandAux(
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
mergedBranchLoader := git_commands.NewMergedBranchLoader(gitCommon)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
@@ -202,7 +204,6 @@ func NewGitCommandAux(
Worktrees: worktreeLoader,
StashLoader: stashLoader,
TagLoader: tagLoader,
MergedBranchLoader: mergedBranchLoader,
},
RepoPaths: repoPaths,
}
+40 -16
View File
@@ -568,26 +568,50 @@ func (self *CommitLoader) getMergeBase(refName string) string {
}
func (self *CommitLoader) getExistingMainBranches() []string {
var existingBranches []string
var wg sync.WaitGroup
mainBranches := self.UserConfig.Git.MainBranches
existingBranches = make([]string, len(mainBranches))
existingBranches := utils.ConcurrentMap(mainBranches, func(mainBranch string) string {
// Try to determine upstream of local main branch
if ref, err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--symbolic-full-name", mainBranch+"@{u}").ToArgv(),
).DontLog().RunWithOutput(); err == nil {
return strings.TrimSpace(ref)
}
for i, branchName := range mainBranches {
wg.Add(1)
i := i
branchName := branchName
go utils.Safe(func() {
defer wg.Done()
// If this failed, fallback to the local branch
ref := "refs/heads/" + mainBranch
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
return ref
}
// Try to determine upstream of local main branch
if ref, err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
).DontLog().RunWithOutput(); err == nil {
existingBranches[i] = strings.TrimSpace(ref)
return
}
return ""
})
// If this failed, a local branch for this main branch doesn't exist or it
// has no upstream configured. Try looking for one in the "origin" remote.
ref := "refs/remotes/origin/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
return
}
// If this failed as well, try if we have the main branch as a local
// branch. This covers the case where somebody is using git locally
// for something, but never pushing anywhere.
ref = "refs/heads/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
}
})
}
wg.Wait()
existingBranches = lo.Filter(existingBranches, func(branch string, _ int) bool {
return branch != ""
@@ -74,10 +74,12 @@ func TestGetCommits(t *testing.T) {
// here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
// here it's testing which of the configured main branches have an upstream
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "refs/remotes/origin/main", nil). // this one does too
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so check for a local branch
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", errors.New("error")). // no local branch either
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", nil). // yep, origin/main exists
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/develop"}, "", errors.New("error")). // doesn't exist there, either, so it checks for a local branch
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", errors.New("error")). // no local branch either
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
ExpectGitArgs([]string{"merge-base", "HEAD", "refs/remotes/origin/master", "refs/remotes/origin/main"}, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
@@ -210,8 +212,10 @@ func TestGetCommits(t *testing.T) {
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist; neither does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")),
expectedCommits: []*models.Commit{
@@ -246,6 +250,7 @@ func TestGetCommits(t *testing.T) {
// here it's testing which of the configured main branches exist
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "refs/remotes/origin/develop", nil).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "1.0-hotfixes@{u}"}, "refs/remotes/origin/1.0-hotfixes", nil).
+5
View File
@@ -4,6 +4,7 @@ import (
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/sasha-s/go-deadlock"
)
type GitCommon struct {
@@ -14,6 +15,8 @@ type GitCommon struct {
repoPaths *RepoPaths
repo *gogit.Repository
config *ConfigCommands
// mutex for doing things like push/pull/fetch
syncMutex *deadlock.Mutex
}
func NewGitCommon(
@@ -24,6 +27,7 @@ func NewGitCommon(
repoPaths *RepoPaths,
repo *gogit.Repository,
config *ConfigCommands,
syncMutex *deadlock.Mutex,
) *GitCommon {
return &GitCommon{
Common: cmn,
@@ -33,5 +37,6 @@ func NewGitCommon(
repoPaths: repoPaths,
repo: repo,
config: config,
syncMutex: syncMutex,
}
}
@@ -1,59 +0,0 @@
package git_commands
import (
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// Returns a set of branch names which are merged into a main branch.
// This
type MergedBranchLoader struct {
c *GitCommon
}
func NewMergedBranchLoader(c *GitCommon) *MergedBranchLoader {
return &MergedBranchLoader{c: c}
}
// TODO: check against upstreams, and share code with commit loader that determines main branches
func (self *MergedBranchLoader) Load() *set.Set[string] {
set := set.New[string]()
mainBranches := self.c.UserConfig.Git.MainBranches
results := utils.ConcurrentMap(mainBranches, func(mainBranch string) []string {
mergedBranches, err := self.GetMergedBranches(mainBranch)
if err != nil {
self.c.Log.Warnf("Failed to get merged branches for %s: %s", mainBranch, err)
return nil
}
return mergedBranches
})
for i := 0; i < len(mainBranches); i++ {
for _, mergedBranch := range results[i] {
set.Add(mergedBranch)
}
}
return set
}
func (self *MergedBranchLoader) GetMergedBranches(mainBranch string) ([]string, error) {
// git for-each-ref --merged master --format '%(refname)' refs/heads/
cmdArgs := NewGitCmd("for-each-ref").Arg(
"--merged",
mainBranch,
"--format",
"%(refname)",
"refs/heads/",
).ToArgv()
output, err := self.c.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return nil, err
}
branches := utils.SplitLines(output)
return branches, nil
}
+2 -2
View File
@@ -53,7 +53,7 @@ func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName strin
Arg(remoteName, "--delete", branchName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string, tagName string) error {
@@ -61,7 +61,7 @@ func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string,
Arg(remoteName, "--delete", tagName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
// CheckRemoteBranchExists Returns remote branch
+5 -4
View File
@@ -36,7 +36,7 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
ToArgv()
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task)
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex)
return cmdObj, nil
}
@@ -70,6 +70,7 @@ func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
cmdObj := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest()
cmdObj.WithMutex(self.syncMutex)
return cmdObj
}
@@ -95,7 +96,7 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
// has 'pull.rebase = interactive' configured.
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).Run()
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
func (self *SyncCommands) FastForward(
@@ -109,7 +110,7 @@ func (self *SyncCommands) FastForward(
Arg(remoteBranchName + ":" + branchName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error {
@@ -117,5 +118,5 @@ func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error
Arg(remoteName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
+1 -1
View File
@@ -52,5 +52,5 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string
cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
+22 -20
View File
@@ -99,30 +99,32 @@ func (self *HostingServiceMgr) getCandidateServiceDomains() []ServiceDomain {
serviceDomains := slices.Clone(defaultServiceDomains)
for gitDomain, typeAndDomain := range self.configServiceDomains {
provider, webDomain, success := strings.Cut(typeAndDomain, ":")
if len(self.configServiceDomains) > 0 {
for gitDomain, typeAndDomain := range self.configServiceDomains {
provider, webDomain, success := strings.Cut(typeAndDomain, ":")
// we allow for one ':' for specifying the TCP port
if !success || strings.Count(webDomain, ":") > 1 {
self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain)
continue
}
// we allow for one ':' for specifying the TCP port
if !success || strings.Count(webDomain, ":") > 1 {
self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain)
continue
}
serviceDefinition, ok := serviceDefinitionByProvider[provider]
if !ok {
providerNames := lo.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition, _ int) string {
return serviceDefinition.provider
serviceDefinition, ok := serviceDefinitionByProvider[provider]
if !ok {
providerNames := lo.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition, _ int) string {
return serviceDefinition.provider
})
self.log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", "))
continue
}
serviceDomains = append(serviceDomains, ServiceDomain{
gitDomain: gitDomain,
webDomain: webDomain,
serviceDefinition: serviceDefinition,
})
self.log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", "))
continue
}
serviceDomains = append(serviceDomains, ServiceDomain{
gitDomain: gitDomain,
webDomain: webDomain,
serviceDefinition: serviceDefinition,
})
}
return serviceDomains
-4
View File
@@ -65,10 +65,6 @@ func (b *Branch) ID() string {
return b.RefName()
}
func (b *Branch) URN() string {
return "branch-" + b.ID()
}
func (b *Branch) Description() string {
return b.RefName()
}
-4
View File
@@ -24,10 +24,6 @@ func (t *Tag) ID() string {
return t.RefName()
}
func (t *Tag) URN() string {
return "tag-" + t.ID()
}
func (t *Tag) Description() string {
return t.Message
}
+5
View File
@@ -74,6 +74,11 @@ func CopyFile(src, dst string) (err error) {
return //nolint: nakedret
}
// If I was less lazy
// I would actually think
// of a way to add some real code
// for the sake of an example
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist. If destination already exists we'll clobber it.
// Symlinks are ignored and skipped.
-1
View File
@@ -60,7 +60,6 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
suspend: false,
},
"emacs": standardTerminalEditorPreset("emacs"),
"micro": standardTerminalEditorPreset("micro"),
"nano": standardTerminalEditorPreset("nano"),
"kakoune": standardTerminalEditorPreset("kak"),
"helix": {
+110 -265
View File
@@ -5,255 +5,131 @@ import (
)
type UserConfig struct {
// Config relating to the Lazygit UI
Gui GuiConfig `yaml:"gui"`
// Config relating to git
Git GitConfig `yaml:"git"`
// Periodic update checks
Update UpdateConfig `yaml:"update"`
// Background refreshes
Refresher RefresherConfig `yaml:"refresher"`
// If true, show a confirmation popup before quitting Lazygit
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
// If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
// Keybindings
Keybinding KeybindingConfig `yaml:"keybinding"`
// Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc
OS OSConfig `yaml:"os,omitempty"`
// If true, don't display introductory popups upon opening Lazygit.
// Lazygit sets this to true upon first runninng the program so that you don't see introductory popups every time you open the program.
DisableStartupPopups bool `yaml:"disableStartupPopups"`
// User-configured commands that can be invoked from within Lazygit
CustomCommands []CustomCommand `yaml:"customCommands"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-pull-request-urls
Services map[string]string `yaml:"services"`
// What to do when opening Lazygit outside of a git repo.
// - 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo
// - 'create': initialize a new repo
// - 'skip': open most recent repo
// - 'quit': exit Lazygit
NotARepository string `yaml:"notARepository"`
// If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
Gui GuiConfig `yaml:"gui"`
Git GitConfig `yaml:"git"`
Update UpdateConfig `yaml:"update"`
Refresher RefresherConfig `yaml:"refresher"`
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
Keybinding KeybindingConfig `yaml:"keybinding"`
// OS determines what defaults are set for opening files and links
OS OSConfig `yaml:"os,omitempty"`
DisableStartupPopups bool `yaml:"disableStartupPopups"`
CustomCommands []CustomCommand `yaml:"customCommands"`
Services map[string]string `yaml:"services"`
NotARepository string `yaml:"notARepository"`
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
}
type RefresherConfig struct {
// File/submodule refresh interval in seconds.
// Auto-refresh can be disabled via option 'git.autoRefresh'.
RefreshInterval int `yaml:"refreshInterval"`
// Re-fetch interval in seconds.
// Auto-fetch can be disabled via option 'git.autoFetch'.
FetchInterval int `yaml:"fetchInterval"`
FetchInterval int `yaml:"fetchInterval"`
}
type GuiConfig struct {
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color
AuthorColors map[string]string `yaml:"authorColors"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
BranchColors map[string]string `yaml:"branchColors"`
// The number of lines you scroll by when scrolling the main window
ScrollHeight int `yaml:"scrollHeight"`
// If true, allow scrolling past the bottom of the content in the main window
ScrollPastBottom bool `yaml:"scrollPastBottom"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin
ScrollOffMargin int `yaml:"scrollOffMargin"`
// One of: 'margin' (default) | 'jump'
ScrollOffBehavior string `yaml:"scrollOffBehavior"`
// If true, capture mouse events.
// When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
MouseEvents bool `yaml:"mouseEvents"`
// If true, do not show a warning when discarding changes in the staging view.
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
// If true, do not show warning when applying/popping the stash
SkipStashWarning bool `yaml:"skipStashWarning"`
// If true, do not show a warning when attempting to commit without any staged files; instead stage all unstaged files.
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
// If true, do not show a warning when rewording a commit via an external editor
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
// Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section.
// Number from 0 to 1.0.
SidePanelWidth float64 `yaml:"sidePanelWidth"`
// If true, increase the height of the focused side window; creating an accordion effect.
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
// Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split.
// Options are:
// - 'horizontal': split the window horizontally
// - 'vertical': split the window vertically
// - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
// One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
Language string `yaml:"language"`
// Format used when displaying time e.g. commit time.
// Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
TimeFormat string `yaml:"timeFormat"`
// Format used when displaying time if the time is less than 24 hours ago.
// Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
ShortTimeFormat string `yaml:"shortTimeFormat"`
// Config relating to colors and styles.
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes
Theme ThemeConfig `yaml:"theme"`
// Config relating to the commit length indicator
CommitLength CommitLengthConfig `yaml:"commitLength"`
// If true, show the '5 of 20' footer at the bottom of list views
ShowListFooter bool `yaml:"showListFooter"`
// If true, display the files in the file views as a tree. If false, display the files as a flat list.
// This can be toggled from within Lazygit with the '~' key, but that will not change the default.
ShowFileTree bool `yaml:"showFileTree"`
// If true, show a random tip in the command log when Lazygit starts
ShowRandomTip bool `yaml:"showRandomTip"`
// If true, show the command log
ShowCommandLog bool `yaml:"showCommandLog"`
// If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action.
ShowBottomLine bool `yaml:"showBottomLine"`
// If true, show jump-to-window keybindings in window titles.
ShowPanelJumps bool `yaml:"showPanelJumps"`
// Deprecated: use nerdFontsVersion instead
ShowIcons bool `yaml:"showIcons"`
// Nerd fonts version to use.
// One of: '2' | '3' | empty string (default)
// If empty, do not show icons.
NerdFontsVersion string `yaml:"nerdFontsVersion"`
// If true, show commit hashes alongside branch names in the branches view.
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
// Height of the command log view
CommandLogSize int `yaml:"commandLogSize"`
// Whether to split the main window when viewing file changes.
// One of: 'auto' | 'always'
// If 'auto', only split the main window when a file has both staged and unstaged changes
SplitDiff string `yaml:"splitDiff"`
// Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default).
// One of: 'normal' (default) | 'half' | 'full'
WindowSize string `yaml:"windowSize"`
// Window border style.
// One of 'rounded' (default) | 'single' | 'double' | 'hidden'
Border string `yaml:"border"`
// If true, show a seriously epic explosion animation when nuking the working tree.
AnimateExplosion bool `yaml:"animateExplosion"`
// Whether to stack UI components on top of each other.
// One of 'auto' (default) | 'always' | 'never'
PortraitMode string `yaml:"portraitMode"`
AuthorColors map[string]string `yaml:"authorColors"`
BranchColors map[string]string `yaml:"branchColors"`
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
ScrollOffMargin int `yaml:"scrollOffMargin"`
ScrollOffBehavior string `yaml:"scrollOffBehavior"`
MouseEvents bool `yaml:"mouseEvents"`
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
SidePanelWidth float64 `yaml:"sidePanelWidth"`
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
Language string `yaml:"language"`
TimeFormat string `yaml:"timeFormat"`
ShortTimeFormat string `yaml:"shortTimeFormat"`
Theme ThemeConfig `yaml:"theme"`
CommitLength CommitLengthConfig `yaml:"commitLength"`
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
ShowListFooter bool `yaml:"showListFooter"`
ShowFileTree bool `yaml:"showFileTree"`
ShowRandomTip bool `yaml:"showRandomTip"`
ShowCommandLog bool `yaml:"showCommandLog"`
ShowBottomLine bool `yaml:"showBottomLine"`
ShowPanelJumps bool `yaml:"showPanelJumps"`
ShowIcons bool `yaml:"showIcons"`
NerdFontsVersion string `yaml:"nerdFontsVersion"`
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
CommandLogSize int `yaml:"commandLogSize"`
SplitDiff string `yaml:"splitDiff"`
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
WindowSize string `yaml:"windowSize"`
Border string `yaml:"border"`
AnimateExplosion bool `yaml:"animateExplosion"`
}
type ThemeConfig struct {
// Border color of focused window
ActiveBorderColor []string `yaml:"activeBorderColor"`
// Border color of non-focused windows
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
// Border color of focused window when searching in that window
ActiveBorderColor []string `yaml:"activeBorderColor"`
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
SearchingActiveBorderColor []string `yaml:"searchingActiveBorderColor"`
// Color of keybindings help text in the bottom line
OptionsTextColor []string `yaml:"optionsTextColor"`
// Background color of selected line.
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
// Background color of selected range
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
// Foreground color of copied commit
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
// Background color of copied commit
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
// Foreground color of marked base commit (for rebase)
MarkedBaseCommitFgColor []string `yaml:"markedBaseCommitFgColor"`
// Background color of marked base commit (for rebase)
MarkedBaseCommitBgColor []string `yaml:"markedBaseCommitBgColor"`
// Color for file with unstaged changes
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
// Default text color
DefaultFgColor []string `yaml:"defaultFgColor"`
OptionsTextColor []string `yaml:"optionsTextColor"`
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
MarkedBaseCommitBgColor []string `yaml:"markedBaseCommitBgColor"`
MarkedBaseCommitFgColor []string `yaml:"markedBaseCommitFgColor"`
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
DefaultFgColor []string `yaml:"defaultFgColor"`
}
type CommitLengthConfig struct {
// If true, show an indicator of commit message length
Show bool `yaml:"show"`
}
type GitConfig struct {
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
Paging PagingConfig `yaml:"paging"`
// Config relating to committing
Commit CommitConfig `yaml:"commit"`
// Config relating to merging
Merging MergingConfig `yaml:"merging"`
// list of branches that are considered 'main' branches, used when displaying commits
MainBranches []string `yaml:"mainBranches"`
// Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP'
SkipHookPrefix string `yaml:"skipHookPrefix"`
// If true, periodically fetch from remote
AutoFetch bool `yaml:"autoFetch"`
// If true, periodically refresh files and submodules
AutoRefresh bool `yaml:"autoRefresh"`
// If true, pass the --all arg to git fetch
FetchAll bool `yaml:"fetchAll"`
// Command used when displaying the current branch git log in the main window
BranchLogCmd string `yaml:"branchLogCmd"`
// Command used to display git log of all branches in the main window
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
// If true, do not spawn a separate process when using GPG
OverrideGpg bool `yaml:"overrideGpg"`
// If true, do not allow force pushes
DisableForcePushing bool `yaml:"disableForcePushing"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
// If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀
// (This should really be under 'gui', not 'git')
ParseEmoji bool `yaml:"parseEmoji"`
// Config for showing the log in the commits view
Log LogConfig `yaml:"log"`
Paging PagingConfig `yaml:"paging"`
Commit CommitConfig `yaml:"commit"`
Merging MergingConfig `yaml:"merging"`
MainBranches []string `yaml:"mainBranches"`
SkipHookPrefix string `yaml:"skipHookPrefix"`
AutoFetch bool `yaml:"autoFetch"`
AutoRefresh bool `yaml:"autoRefresh"`
FetchAll bool `yaml:"fetchAll"`
BranchLogCmd string `yaml:"branchLogCmd"`
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
OverrideGpg bool `yaml:"overrideGpg"`
DisableForcePushing bool `yaml:"disableForcePushing"`
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
// this should really be under 'gui', not 'git'
ParseEmoji bool `yaml:"parseEmoji"`
Log LogConfig `yaml:"log"`
}
type PagingConfig struct {
// Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never'
ColorArg string `yaml:"colorArg"`
// e.g.
// diff-so-fancy
// delta --dark --paging=never
// ydiff -p cat -s --wrap --width={{columnWidth}}
Pager string `yaml:"pager"`
// If true, Lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
UseConfig bool `yaml:"useConfig"`
// e.g. 'difft --color=always'
ColorArg string `yaml:"colorArg"`
Pager string `yaml:"pager"`
UseConfig bool `yaml:"useConfig"`
ExternalDiffCommand string `yaml:"externalDiffCommand"`
}
type CommitConfig struct {
// If true, pass '--signoff' flag when committing
SignOff bool `yaml:"signOff"`
}
type MergingConfig struct {
// If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang
// Only applicable to unix users.
ManualCommit bool `yaml:"manualCommit"`
// Extra args passed to `git merge`, e.g. --no-ff
Args string `yaml:"args"`
ManualCommit bool `yaml:"manualCommit"`
Args string `yaml:"args"`
}
type LogConfig struct {
// One of: 'date-order' | 'author-date-order' | 'topo-order'
// 'topo-order' makes it easier to read the git log graph, but commits may not
// appear chronologically. See https://git-scm.com/docs/
Order string `yaml:"order"`
// This determines whether the git graph is rendered in the commits panel
// One of 'always' | 'never' 'when-maximised'
ShowGraph string `yaml:"showGraph"`
// displays the whole git graph by default in the commits view (equivalent to passing the `--all` argument to `git log`)
ShowWholeGraph bool `yaml:"showWholeGraph"`
Order string `yaml:"order"` // one of date-order, author-date-order, topo-order
ShowGraph string `yaml:"showGraph"` // one of always, never, when-maximised
ShowWholeGraph bool `yaml:"showWholeGraph"`
}
type CommitPrefixConfig struct {
// pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*"
Pattern string `yaml:"pattern"`
// Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] "
Replace string `yaml:"replace"`
}
type UpdateConfig struct {
// One of: 'prompt' (default) | 'background' | 'never'
Method string `yaml:"method"`
// Period in days between update checks
Days int64 `yaml:"days"`
Days int64 `yaml:"days"`
}
type KeybindingConfig struct {
@@ -491,8 +367,7 @@ type OSConfig struct {
// Deprecated: use OpenLink instead.
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
// CopyToClipboardCmd is the command for copying to clipboard.
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-clipboard
// CopyToClipboardCmd is the command for copying to clipboard
CopyToClipboardCmd string `yaml:"copyToClipboardCmd,omitempty"`
}
@@ -501,79 +376,50 @@ type CustomCommandAfterHook struct {
}
type CustomCommand struct {
// The key to trigger the command. Use a single letter or one of the values from https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md
Key string `yaml:"key"`
// The context in which to listen for the key
Context string `yaml:"context"`
// The command to run (using Go template syntax for placeholder values)
Command string `yaml:"command"`
// If true, run the command in a subprocess (e.g. if the command requires user input)
Subprocess bool `yaml:"subprocess"`
// A list of prompts that will request user input before running the final command
Prompts []CustomCommandPrompt `yaml:"prompts"`
// Text to display while waiting for command to finish
LoadingText string `yaml:"loadingText"`
// Label for the custom command when displayed in the keybindings menu
Description string `yaml:"description"`
// If true, stream the command's output to the Command Log panel
Stream bool `yaml:"stream"`
// If true, show the command's output in a popup within Lazygit
ShowOutput bool `yaml:"showOutput"`
// Actions to take after the command has completed
After CustomCommandAfterHook `yaml:"after"`
Key string `yaml:"key"`
Context string `yaml:"context"`
Command string `yaml:"command"`
Subprocess bool `yaml:"subprocess"`
Prompts []CustomCommandPrompt `yaml:"prompts"`
LoadingText string `yaml:"loadingText"`
Description string `yaml:"description"`
Stream bool `yaml:"stream"`
ShowOutput bool `yaml:"showOutput"`
After CustomCommandAfterHook `yaml:"after"`
}
type CustomCommandPrompt struct {
// One of: 'input' | 'menu' | 'confirm' | 'menuFromCommand'
Type string `yaml:"type"`
// Used to reference the entered value from within the custom command. E.g. a prompt with `key: 'Branch'` can be referred to as `{{.Form.Branch}}` in the command
Key string `yaml:"key"`
// The title to display in the popup panel
// one of 'input', 'menu', 'confirm', or 'menuFromCommand'
Type string `yaml:"type"`
Key string `yaml:"key"`
Title string `yaml:"title"`
// The initial value to appear in the text box.
// Only for input prompts.
InitialValue string `yaml:"initialValue"`
// Shows suggestions as the input is entered
// Only for input prompts.
Suggestions CustomCommandSuggestions `yaml:"suggestions"`
// these only apply to input prompts
InitialValue string `yaml:"initialValue"`
Suggestions CustomCommandSuggestions `yaml:"suggestions"`
// The message of the confirmation prompt.
// Only for confirm prompts.
// this only applies to confirm prompts
Body string `yaml:"body"`
// Menu options.
// Only for menu prompts.
// this only applies to menus
Options []CustomCommandMenuOption
// The command to run to generate menu options
// Only for menuFromCommand prompts.
Command string `yaml:"command"`
// The regexp to run specifying groups which are going to be kept from the command's output.
// Only for menuFromCommand prompts.
Filter string `yaml:"filter"`
// How to format matched groups from the filter to construct a menu item's value.
// Only for menuFromCommand prompts.
// this only applies to menuFromCommand
Command string `yaml:"command"`
Filter string `yaml:"filter"`
ValueFormat string `yaml:"valueFormat"`
// Like valueFormat but for the labels. If `labelFormat` is not specified, `valueFormat` is shown instead.
// Only for menuFromCommand prompts.
LabelFormat string `yaml:"labelFormat"`
}
type CustomCommandSuggestions struct {
// Uses built-in logic to obtain the suggestions. One of 'authors' | 'branches' | 'files' | 'refs' | 'remotes' | 'remoteBranches' | 'tags'
Preset string `yaml:"preset"`
// Command to run such that each line in the output becomes a suggestion. Mutually exclusive with 'preset' field.
Preset string `yaml:"preset"`
Command string `yaml:"command"`
}
type CustomCommandMenuOption struct {
// The first part of the label
Name string `yaml:"name"`
// The second part of the label
Name string `yaml:"name"`
Description string `yaml:"description"`
// The value that will be used in the command
Value string `yaml:"value"`
Value string `yaml:"value"`
}
func GetDefaultConfig() *UserConfig {
@@ -622,7 +468,6 @@ func GetDefaultConfig() *UserConfig {
SkipRewordInEditorWarning: false,
Border: "rounded",
AnimateExplosion: true,
PortraitMode: "auto",
},
Git: GitConfig{
Paging: PagingConfig{
@@ -691,7 +536,7 @@ func GetDefaultConfig() *UserConfig {
NextMatch: "n",
PrevMatch: "N",
StartSearch: "/",
OptionMenu: "<disabled>",
OptionMenu: "",
OptionMenuAlt1: "?",
Select: "<space>",
GoInto: "<enter>",
-2
View File
@@ -27,13 +27,11 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
getDisplayStrings := func(_ int, _ int) [][]string {
return presentation.GetBranchListDisplayStrings(
viewModel.GetItems(),
c.State().GetItemOperation,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().Diffing.Ref,
c.Tr,
c.UserConfig,
c.Model().Worktrees,
c.Model().MergedBranches,
)
}
-4
View File
@@ -41,10 +41,6 @@ func (self *FilteredList[T]) ClearFilter() {
self.SetFilter("")
}
func (self *FilteredList[T]) ReApplyFilter() {
self.applyFilter()
}
func (self *FilteredList[T]) IsFiltering() bool {
return self.filter != ""
}
+1
View File
@@ -9,6 +9,7 @@ import (
type RemotesContext struct {
*FilteredListViewModel[*models.Remote]
*ListContextTrait
*SearchHistory
}
var (
+1 -4
View File
@@ -27,10 +27,7 @@ func NewTagsContext(
)
getDisplayStrings := func(_ int, _ int) [][]string {
return presentation.GetTagListDisplayStrings(
viewModel.GetItems(),
c.State().GetItemOperation,
c.Modes().Diffing.Ref, c.Tr)
return presentation.GetTagListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
}
return &TagsContext{
+1 -4
View File
@@ -47,7 +47,6 @@ func (gui *Gui) resetHelpersAndControllers() {
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon)
stagingHelper := helpers.NewStagingHelper(helperCommon)
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon)
searchHelper := helpers.NewSearchHelper(helperCommon)
refreshHelper := helpers.NewRefreshHelper(
helperCommon,
@@ -57,7 +56,6 @@ func (gui *Gui) resetHelpersAndControllers() {
stagingHelper,
mergeConflictsHelper,
worktreeHelper,
searchHelper,
)
diffHelper := helpers.NewDiffHelper(helperCommon)
cherryPickHelper := helpers.NewCherryPickHelper(
@@ -114,14 +112,13 @@ func (gui *Gui) resetHelpersAndControllers() {
Confirmation: helpers.NewConfirmationHelper(helperCommon),
Mode: modeHelper,
AppStatus: appStatusHelper,
InlineStatus: helpers.NewInlineStatusHelper(helperCommon),
WindowArrangement: helpers.NewWindowArrangementHelper(
gui.c,
windowHelper,
modeHelper,
appStatusHelper,
),
Search: searchHelper,
Search: helpers.NewSearchHelper(helperCommon),
Worktree: worktreeHelper,
SubCommits: helpers.NewSubCommitsHelper(helperCommon, refreshHelper, setSubCommits),
}
+21 -20
View File
@@ -34,10 +34,9 @@ func NewBranchesController(
func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.press),
GetDisabledReason: self.getDisabledReasonForPress,
Description: self.c.Tr.Checkout,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.press),
Description: self.c.Tr.Checkout,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
@@ -300,18 +299,6 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
}
func (self *BranchesController) getDisabledReasonForPress() string {
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
if currentBranch != nil {
op := self.c.State().GetItemOperation(currentBranch)
if op == types.ItemOperationFastForwarding || op == types.ItemOperationPulling {
return self.c.Tr.CantCheckoutBranchWhilePulling
}
}
return ""
}
func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) {
return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees)
}
@@ -576,7 +563,14 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
action := self.c.Tr.Actions.FastForwardBranch
return self.c.WithInlineStatus(branch, types.ItemOperationFastForwarding, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
message := utils.ResolvePlaceholderString(
self.c.Tr.FastForwarding,
map[string]string{
"branch": branch.Name,
},
)
return self.c.WithWaitingStatus(message, func(task gocui.Task) error {
worktree, ok := self.worktreeForBranch(branch)
if ok {
self.c.LogAction(action)
@@ -596,17 +590,24 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
WorktreeGitDir: worktreeGitDir,
},
)
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return err
if err != nil {
_ = self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
} else {
self.c.LogAction(action)
err := self.c.Git().Sync.FastForward(
task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch,
)
if err != nil {
_ = self.c.Error(err)
}
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
return err
}
return nil
})
}
-2
View File
@@ -46,7 +46,6 @@ type Helpers struct {
Confirmation *ConfirmationHelper
Mode *ModeHelper
AppStatus *AppStatusHelper
InlineStatus *InlineStatusHelper
WindowArrangement *WindowArrangementHelper
Search *SearchHelper
Worktree *WorktreeHelper
@@ -82,7 +81,6 @@ func NewStubHelpers() *Helpers {
Confirmation: &ConfirmationHelper{},
Mode: &ModeHelper{},
AppStatus: &AppStatusHelper{},
InlineStatus: &InlineStatusHelper{},
WindowArrangement: &WindowArrangementHelper{},
Search: &SearchHelper{},
Worktree: &WorktreeHelper{},
@@ -1,129 +0,0 @@
package helpers
import (
"time"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sasha-s/go-deadlock"
)
type InlineStatusHelper struct {
c *HelperCommon
contextsWithInlineStatus map[types.ContextKey]*inlineStatusInfo
mutex *deadlock.Mutex
}
func NewInlineStatusHelper(c *HelperCommon) *InlineStatusHelper {
return &InlineStatusHelper{
c: c,
contextsWithInlineStatus: make(map[types.ContextKey]*inlineStatusInfo),
mutex: &deadlock.Mutex{},
}
}
type InlineStatusOpts struct {
Item types.HasUrn
Operation types.ItemOperation
ContextKey types.ContextKey
}
type inlineStatusInfo struct {
refCount int
stop chan struct{}
}
// A custom task for WithInlineStatus calls; it wraps the original one and
// hides the status whenever the task is paused, and shows it again when
// continued.
type inlineStatusHelperTask struct {
gocui.Task
inlineStatusHelper *InlineStatusHelper
opts InlineStatusOpts
}
// poor man's version of explicitly saying that struct X implements interface Y
var _ gocui.Task = inlineStatusHelperTask{}
func (self inlineStatusHelperTask) Pause() {
self.inlineStatusHelper.stop(self.opts)
self.Task.Pause()
self.inlineStatusHelper.renderContext(self.opts.ContextKey)
}
func (self inlineStatusHelperTask) Continue() {
self.Task.Continue()
self.inlineStatusHelper.start(self.opts)
}
func (self *InlineStatusHelper) WithInlineStatus(opts InlineStatusOpts, f func(gocui.Task) error) {
self.c.OnWorker(func(task gocui.Task) {
self.start(opts)
err := f(inlineStatusHelperTask{task, self, opts})
if err != nil {
self.c.OnUIThread(func() error {
return self.c.Error(err)
})
}
self.stop(opts)
})
}
func (self *InlineStatusHelper) start(opts InlineStatusOpts) {
self.c.State().SetItemOperation(opts.Item, opts.Operation)
self.mutex.Lock()
defer self.mutex.Unlock()
info := self.contextsWithInlineStatus[opts.ContextKey]
if info == nil {
info = &inlineStatusInfo{refCount: 0, stop: make(chan struct{})}
self.contextsWithInlineStatus[opts.ContextKey] = info
go utils.Safe(func() {
ticker := time.NewTicker(time.Millisecond * utils.LoaderAnimationInterval)
defer ticker.Stop()
outer:
for {
select {
case <-ticker.C:
self.renderContext(opts.ContextKey)
case <-info.stop:
break outer
}
}
})
}
info.refCount++
}
func (self *InlineStatusHelper) stop(opts InlineStatusOpts) {
self.mutex.Lock()
if info := self.contextsWithInlineStatus[opts.ContextKey]; info != nil {
info.refCount--
if info.refCount <= 0 {
info.stop <- struct{}{}
delete(self.contextsWithInlineStatus, opts.ContextKey)
}
}
self.mutex.Unlock()
self.c.State().ClearItemOperation(opts.Item)
}
func (self *InlineStatusHelper) renderContext(contextKey types.ContextKey) {
self.c.OnUIThread(func() error {
_ = self.c.ContextForKey(contextKey).HandleRender()
return nil
})
}
+38 -79
View File
@@ -28,7 +28,6 @@ type RefreshHelper struct {
stagingHelper *StagingHelper
mergeConflictsHelper *MergeConflictsHelper
worktreeHelper *WorktreeHelper
searchHelper *SearchHelper
}
func NewRefreshHelper(
@@ -39,7 +38,6 @@ func NewRefreshHelper(
stagingHelper *StagingHelper,
mergeConflictsHelper *MergeConflictsHelper,
worktreeHelper *WorktreeHelper,
searchHelper *SearchHelper,
) *RefreshHelper {
return &RefreshHelper{
c: c,
@@ -49,7 +47,6 @@ func NewRefreshHelper(
stagingHelper: stagingHelper,
mergeConflictsHelper: mergeConflictsHelper,
worktreeHelper: worktreeHelper,
searchHelper: searchHelper,
}
}
@@ -118,15 +115,12 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
}
}
includeWorktreesWithBranches := false
if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) {
// whenever we change commits, we should update branches because the upstream/downstream
// counts can change. Whenever we change branches we should also change commits
// e.g. in the case of switching branches.
refresh("commits and commit files", self.refreshCommitsAndCommitFiles)
includeWorktreesWithBranches = scopeSet.Includes(types.WORKTREES)
refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches) })
refresh("reflog and branches", self.refreshReflogAndBranches)
} else if scopeSet.Includes(types.REBASE_COMMITS) {
// the above block handles rebase commits so we only need to call this one
// if we've asked specifically for rebase commits and not those other things
@@ -163,7 +157,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
refresh("remotes", func() { _ = self.refreshRemotes() })
}
if scopeSet.Includes(types.WORKTREES) && !includeWorktreesWithBranches {
if scopeSet.Includes(types.WORKTREES) {
refresh("worktrees", func() { _ = self.refreshWorktrees() })
}
@@ -248,7 +242,7 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
case types.INITIAL:
self.c.OnWorker(func(_ gocui.Task) {
_ = self.refreshReflogCommits()
self.refreshBranches(false)
self.refreshBranches()
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
})
@@ -257,10 +251,10 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
}
}
func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool) {
func (self *RefreshHelper) refreshReflogAndBranches() {
self.refreshReflogCommitsConsideringStartup()
self.refreshBranches(refreshWorktrees)
self.refreshBranches()
}
func (self *RefreshHelper) refreshCommitsAndCommitFiles() {
@@ -331,7 +325,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
self.c.Model().CheckedOutBranch = checkedOutBranchName
return self.refreshView(self.c.Contexts().LocalCommits)
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
}
func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
@@ -354,7 +348,7 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
self.c.Model().SubCommits = commits
self.RefreshAuthors(commits)
return self.refreshView(self.c.Contexts().SubCommits)
return self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
}
func (self *RefreshHelper) RefreshAuthors(commits []*models.Commit) {
@@ -384,7 +378,7 @@ func (self *RefreshHelper) refreshCommitFilesContext() error {
self.c.Model().CommitFiles = files
self.c.Contexts().CommitFiles.CommitFileTreeViewModel.SetTree()
return self.refreshView(self.c.Contexts().CommitFiles)
return self.c.PostRefreshUpdate(self.c.Contexts().CommitFiles)
}
func (self *RefreshHelper) refreshRebaseCommits() error {
@@ -398,7 +392,7 @@ func (self *RefreshHelper) refreshRebaseCommits() error {
self.c.Model().Commits = updatedCommits
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
return self.refreshView(self.c.Contexts().LocalCommits)
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
}
func (self *RefreshHelper) refreshTags() error {
@@ -409,7 +403,7 @@ func (self *RefreshHelper) refreshTags() error {
self.c.Model().Tags = tags
return self.refreshView(self.c.Contexts().Tags)
return self.c.PostRefreshUpdate(self.c.Contexts().Tags)
}
func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
@@ -425,66 +419,39 @@ func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
// self.refreshStatus is called at the end of this because that's when we can
// be sure there is a State.Model.Branches array to pick the current branch from
func (self *RefreshHelper) refreshBranches(refreshWorktrees bool) {
func (self *RefreshHelper) refreshBranches() {
self.c.Mutexes().RefreshingBranchesMutex.Lock()
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
reflogCommits := self.c.Model().FilteredReflogCommits
if self.c.Modes().Filtering.Active() {
// in filter mode we filter our reflog commits to just those containing the path
// however we need all the reflog entries to populate the recencies of our branches
// 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 = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(nil, "")
if err != nil {
self.c.Log.Error(err)
}
}
branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits)
reflogCommits := self.c.Model().FilteredReflogCommits
if self.c.Modes().Filtering.Active() {
// in filter mode we filter our reflog commits to just those containing the path
// however we need all the reflog entries to populate the recencies of our branches
// 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 = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(nil, "")
if err != nil {
_ = self.c.Error(err)
}
self.c.Model().Branches = branches
}()
go func() {
defer wg.Done()
mergedBranches := self.c.Git().Loaders.MergedBranchLoader.Load()
self.c.Model().MergedBranches = mergedBranches
}()
wg.Wait()
if refreshWorktrees {
self.loadWorktrees()
if err := self.refreshView(self.c.Contexts().Worktrees); err != nil {
self.c.Log.Error(err)
}
}
if err := self.refreshView(self.c.Contexts().Branches); err != nil {
branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits)
if err != nil {
_ = self.c.Error(err)
}
self.c.Model().Branches = branches
if err := self.c.PostRefreshUpdate(self.c.Contexts().Branches); err != nil {
self.c.Log.Error(err)
}
// Need to re-render the commits view because the visualization of local
// branch heads might have changed
self.c.Mutexes().LocalCommitsMutex.Lock()
if err := self.c.Contexts().LocalCommits.HandleRender(); err != nil {
self.c.Log.Error(err)
}
self.c.Mutexes().LocalCommitsMutex.Unlock()
self.refreshStatus()
}
@@ -506,11 +473,11 @@ func (self *RefreshHelper) refreshFilesAndSubmodules() error {
}
self.c.OnUIThread(func() error {
if err := self.refreshView(self.c.Contexts().Submodules); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Submodules); err != nil {
self.c.Log.Error(err)
}
if err := self.refreshView(self.c.Contexts().Files); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
self.c.Log.Error(err)
}
@@ -632,7 +599,7 @@ func (self *RefreshHelper) refreshReflogCommits() error {
model.FilteredReflogCommits = model.ReflogCommits
}
return self.refreshView(self.c.Contexts().ReflogCommits)
return self.c.PostRefreshUpdate(self.c.Contexts().ReflogCommits)
}
func (self *RefreshHelper) refreshRemotes() error {
@@ -656,44 +623,41 @@ func (self *RefreshHelper) refreshRemotes() error {
}
}
if err := self.refreshView(self.c.Contexts().Remotes); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Remotes); err != nil {
return err
}
if err := self.refreshView(self.c.Contexts().RemoteBranches); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().RemoteBranches); err != nil {
return err
}
return nil
}
func (self *RefreshHelper) loadWorktrees() {
func (self *RefreshHelper) refreshWorktrees() error {
worktrees, err := self.c.Git().Loaders.Worktrees.GetWorktrees()
if err != nil {
self.c.Log.Error(err)
self.c.Model().Worktrees = []*models.Worktree{}
return nil
}
self.c.Model().Worktrees = worktrees
}
func (self *RefreshHelper) refreshWorktrees() error {
self.loadWorktrees()
// need to refresh branches because the branches view shows worktrees against
// branches
if err := self.refreshView(self.c.Contexts().Branches); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Branches); err != nil {
return err
}
return self.refreshView(self.c.Contexts().Worktrees)
return self.c.PostRefreshUpdate(self.c.Contexts().Worktrees)
}
func (self *RefreshHelper) refreshStashEntries() error {
self.c.Model().StashEntries = self.c.Git().Loaders.StashLoader.
GetStashEntries(self.c.Modes().Filtering.GetPath())
return self.refreshView(self.c.Contexts().Stash)
return self.c.PostRefreshUpdate(self.c.Contexts().Stash)
}
// never call this on its own, it should only be called from within refreshCommits()
@@ -712,7 +676,7 @@ func (self *RefreshHelper) refreshStatus() {
repoName := self.c.Git().RepoPaths.RepoName()
status := presentation.FormatStatus(repoName, currentBranch, types.ItemOperationNone, linkedWorktreeName, workingTreeState, self.c.Tr)
status := presentation.FormatStatus(repoName, currentBranch, linkedWorktreeName, workingTreeState, self.c.Tr)
self.c.SetViewContent(self.c.Views().Status, status)
}
@@ -732,8 +696,3 @@ func (self *RefreshHelper) refForLog() string {
return bisectInfo.GetStartSha()
}
func (self *RefreshHelper) refreshView(context types.Context) error {
self.searchHelper.ReApplyFilter(context)
return self.c.PostRefreshUpdate(context)
}
@@ -173,6 +173,11 @@ func (self *ReposHelper) DispatchSwitchTo(path string, errMsg string, contextKey
return err
}
// these two mutexes are used by our background goroutines (triggered via `self.goEvery`. We don't want to
// switch to a repo while one of these goroutines is in the process of updating something
self.c.Mutexes().SyncMutex.Lock()
defer self.c.Mutexes().SyncMutex.Unlock()
self.c.Mutexes().RefreshingFilesMutex.Lock()
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
@@ -227,18 +227,6 @@ func (self *SearchHelper) OnPromptContentChanged(searchString string) {
}
}
func (self *SearchHelper) ReApplyFilter(context types.Context) {
state := self.searchState()
if context == state.Context {
filterableContext, ok := context.(types.IFilterableContext)
if ok {
filterableContext.SetSelectedLineIdx(0)
_ = filterableContext.GetView().SetOriginY(0)
filterableContext.ReApplyFilter()
}
}
}
func (self *SearchHelper) RenderSearchStatus(c types.Context) {
if c.GetKey() == context.SEARCH_CONTEXT_KEY {
return
@@ -34,24 +34,14 @@ func NewWindowArrangementHelper(
const INFO_SECTION_PADDING = " "
func (self *WindowArrangementHelper) shouldUsePortraitMode(width, height int) bool {
switch self.c.UserConfig.Gui.PortraitMode {
case "never":
return false
case "always":
return true
default: // "auto" or any garbage values in PortraitMode value
return width <= 84 && height > 45
}
}
func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
width, height := self.c.GocuiGui().Size()
sideSectionWeight, mainSectionWeight := self.getMidSectionWeights()
sidePanelsDirection := boxlayout.COLUMN
if self.shouldUsePortraitMode(width, height) {
portraitMode := width <= 84 && height > 45
if portraitMode {
sidePanelsDirection = boxlayout.ROW
}
+1 -1
View File
@@ -106,7 +106,7 @@ func (self *StatusController) onClick() error {
}
cx, _ := self.c.Views().Status.Cursor()
upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr)
upstreamStatus := presentation.BranchStatus(currentBranch, self.c.Tr)
repoName := self.c.Git().RepoPaths.RepoName()
workingTreeState := self.c.Git().Status.WorkingTreeState()
switch workingTreeState {
+20 -35
View File
@@ -7,7 +7,6 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"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"
)
@@ -31,16 +30,14 @@ func NewSyncController(
func (self *SyncController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Push),
Handler: opts.Guards.NoPopupPanel(self.HandlePush),
GetDisabledReason: self.getDisabledReasonForPushOrPull,
Description: self.c.Tr.Push,
Key: opts.GetKey(opts.Config.Universal.Push),
Handler: opts.Guards.NoPopupPanel(self.HandlePush),
Description: self.c.Tr.Push,
},
{
Key: opts.GetKey(opts.Config.Universal.Pull),
Handler: opts.Guards.NoPopupPanel(self.HandlePull),
GetDisabledReason: self.getDisabledReasonForPushOrPull,
Description: self.c.Tr.Pull,
Key: opts.GetKey(opts.Config.Universal.Pull),
Handler: opts.Guards.NoPopupPanel(self.HandlePull),
Description: self.c.Tr.Pull,
},
}
@@ -59,18 +56,6 @@ func (self *SyncController) HandlePull() error {
return self.branchCheckedOut(self.pull)()
}
func (self *SyncController) getDisabledReasonForPushOrPull() string {
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
if currentBranch != nil {
op := self.c.State().GetItemOperation(currentBranch)
if op != types.ItemOperationNone {
return self.c.Tr.CantPullOrPushSameBranchTwice
}
}
return ""
}
func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func() error {
return func() error {
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
@@ -88,13 +73,13 @@ func (self *SyncController) push(currentBranch *models.Branch) error {
if currentBranch.IsTrackingRemote() {
opts := pushOpts{}
if currentBranch.HasCommitsToPull() {
return self.requestToForcePush(currentBranch, opts)
return self.requestToForcePush(opts)
} else {
return self.pushAux(currentBranch, opts)
return self.pushAux(opts)
}
} else {
if self.c.Git().Config.GetPushToCurrent() {
return self.pushAux(currentBranch, pushOpts{setUpstream: true})
return self.pushAux(pushOpts{setUpstream: true})
} else {
return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error {
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
@@ -102,7 +87,7 @@ func (self *SyncController) push(currentBranch *models.Branch) error {
return self.c.Error(err)
}
return self.pushAux(currentBranch, pushOpts{
return self.pushAux(pushOpts{
setUpstream: true,
upstreamRemote: upstreamRemote,
upstreamBranch: upstreamBranch,
@@ -122,11 +107,11 @@ func (self *SyncController) pull(currentBranch *models.Branch) error {
return self.c.Error(err)
}
return self.PullAux(currentBranch, PullFilesOptions{Action: action})
return self.PullAux(PullFilesOptions{Action: action})
})
}
return self.PullAux(currentBranch, PullFilesOptions{Action: action})
return self.PullAux(PullFilesOptions{Action: action})
}
func (self *SyncController) setCurrentBranchUpstream(upstream string) error {
@@ -154,8 +139,8 @@ type PullFilesOptions struct {
Action string
}
func (self *SyncController) PullAux(currentBranch *models.Branch, opts PullFilesOptions) error {
return self.c.WithInlineStatus(currentBranch, types.ItemOperationPulling, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
func (self *SyncController) PullAux(opts PullFilesOptions) error {
return self.c.WithWaitingStatus(self.c.Tr.PullingStatus, func(task gocui.Task) error {
return self.pullWithLock(task, opts)
})
}
@@ -182,8 +167,8 @@ type pushOpts struct {
setUpstream bool
}
func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) error {
return self.c.WithInlineStatus(currentBranch, types.ItemOperationPushing, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
func (self *SyncController) pushAux(opts pushOpts) error {
return self.c.WithWaitingStatus(self.c.Tr.PushingStatus, func(task gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.Push)
err := self.c.Git().Sync.Push(
task,
@@ -207,18 +192,18 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
newOpts := opts
newOpts.force = true
return self.pushAux(currentBranch, newOpts)
return self.pushAux(newOpts)
},
})
return nil
}
return err
_ = self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
})
}
func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opts pushOpts) error {
func (self *SyncController) requestToForcePush(opts pushOpts) error {
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
if forcePushDisabled {
return self.c.ErrorMsg(self.c.Tr.ForcePushDisabled)
@@ -229,7 +214,7 @@ func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opt
Prompt: self.forcePushPrompt(),
HandleConfirm: func() error {
opts.force = true
return self.pushAux(currentBranch, opts)
return self.pushAux(opts)
},
})
}
+12 -14
View File
@@ -92,9 +92,10 @@ func (self *TagsController) checkout(tag *models.Tag) error {
func (self *TagsController) localDelete(tag *models.Tag) error {
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.DeleteLocalTag)
err := self.c.Git().Tag.LocalDelete(tag.Name)
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
return err
if err := self.c.Git().Tag.LocalDelete(tag.Name); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
})
}
@@ -129,10 +130,10 @@ func (self *TagsController) remoteDelete(tag *models.Tag) error {
Title: confirmTitle,
Prompt: confirmPrompt,
HandleConfirm: func() error {
return self.c.WithInlineStatus(tag, types.ItemOperationDeleting, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error {
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(t gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteTag)
if err := self.c.Git().Remote.DeleteRemoteTag(task, upstream, tag.Name); err != nil {
return err
if err := self.c.Git().Remote.DeleteRemoteTag(t, upstream, tag.Name); err != nil {
return self.c.Error(err)
}
self.c.Toast(self.c.Tr.RemoteTagDeletedMessage)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
@@ -188,17 +189,14 @@ func (self *TagsController) push(tag *models.Tag) error {
InitialContent: "origin",
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
HandleConfirm: func(response string) error {
return self.c.WithInlineStatus(tag, types.ItemOperationPushing, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error {
return self.c.WithWaitingStatus(self.c.Tr.PushingTagStatus, func(task gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.PushTag)
err := self.c.Git().Tag.Push(task, response, tag.Name)
if err != nil {
_ = self.c.Error(err)
}
// Render again to remove the inline status:
self.c.OnUIThread(func() error {
_ = self.c.Contexts().Tags.HandleRender()
return nil
})
return err
return nil
})
},
})
+3 -36
View File
@@ -8,7 +8,6 @@ import (
"strings"
"sync"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazycore/pkg/boxlayout"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
@@ -111,12 +110,6 @@ type Gui struct {
// lazygit was opened in, or if we'll retain the one we're currently in.
RetainOriginalDir bool
// stores long-running operations associated with items (e.g. when a branch
// is being pushed). At the moment the rule is to use an item operation when
// we need to talk to the remote.
itemOperations map[string]types.ItemOperation
itemOperationsMutex *deadlock.Mutex
PrevLayout PrevLayout
// this is the initial dir we are in upon opening lazygit. We hold onto this
@@ -187,27 +180,6 @@ func (self *StateAccessor) SetRetainOriginalDir(value bool) {
self.gui.RetainOriginalDir = value
}
func (self *StateAccessor) GetItemOperation(item types.HasUrn) types.ItemOperation {
self.gui.itemOperationsMutex.Lock()
defer self.gui.itemOperationsMutex.Unlock()
return self.gui.itemOperations[item.URN()]
}
func (self *StateAccessor) SetItemOperation(item types.HasUrn, operation types.ItemOperation) {
self.gui.itemOperationsMutex.Lock()
defer self.gui.itemOperationsMutex.Unlock()
self.gui.itemOperations[item.URN()] = operation
}
func (self *StateAccessor) ClearItemOperation(item types.HasUrn) {
self.gui.itemOperationsMutex.Lock()
defer self.gui.itemOperationsMutex.Unlock()
delete(self.gui.itemOperations, item.URN())
}
// we keep track of some stuff from one render to the next to see if certain
// things have changed
type PrevLayout struct {
@@ -301,6 +273,7 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
gui.gitVersion,
gui.os,
git_config.NewStdCachedGitConfig(gui.Log),
gui.Mutexes.SyncMutex,
)
if err != nil {
return err
@@ -385,7 +358,6 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
BisectInfo: git_commands.NewNullBisectInfo(),
FilesTrie: patricia.NewTrie(),
Authors: map[string]*models.Author{},
MergedBranches: set.New[string](),
},
Modes: &types.Modes{
Filtering: filtering.New(startArgs.FilterPath),
@@ -491,6 +463,7 @@ func NewGui(
RefreshingFilesMutex: &deadlock.Mutex{},
RefreshingBranchesMutex: &deadlock.Mutex{},
RefreshingStatusMutex: &deadlock.Mutex{},
SyncMutex: &deadlock.Mutex{},
LocalCommitsMutex: &deadlock.Mutex{},
SubCommitsMutex: &deadlock.Mutex{},
AuthorsMutex: &deadlock.Mutex{},
@@ -500,9 +473,6 @@ func NewGui(
},
InitialDir: initialDir,
afterLayoutFuncs: make(chan func() error, 1000),
itemOperations: make(map[string]types.ItemOperation),
itemOperationsMutex: &deadlock.Mutex{},
}
gui.PopupHandler = popup.NewPopupHandler(
@@ -653,10 +623,7 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
deadlock.Opts.LogBuf = utils.NewOnceWriter(os.Stderr, func() {
gui.g.Close()
})
// disable deadlock reporting if we're not running in debug mode, or if
// we're debugging an integration test. In this latter case, stopping at
// breakpoints and stepping through code can easily take more than 30s.
deadlock.Opts.Disable = !gui.Debug || os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) == ""
deadlock.Opts.Disable = !gui.Debug
if err := gui.Config.ReloadUserConfig(); err != nil {
return nil
-6
View File
@@ -5,7 +5,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -200,8 +199,3 @@ func (self *guiCommon) RunningIntegrationTest() bool {
func (self *guiCommon) InDemo() bool {
return self.gui.integrationTest != nil && self.gui.integrationTest.IsDemo()
}
func (self *guiCommon) WithInlineStatus(item types.HasUrn, operation types.ItemOperation, contextKey types.ContextKey, f func(gocui.Task) error) error {
self.gui.helpers.InlineStatus.WithInlineStatus(helpers.InlineStatusOpts{Item: item, Operation: operation, ContextKey: contextKey}, f)
return nil
}
+1 -3
View File
@@ -100,9 +100,7 @@ func LabelFromKey(key types.Key) string {
func GetKey(key string) types.Key {
runeCount := utf8.RuneCountInString(key)
if key == "<disabled>" {
return nil
} else if runeCount > 1 {
if runeCount > 1 {
binding, ok := keyByLabel[strings.ToLower(key)]
if !ok {
log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings)
+7 -28
View File
@@ -4,13 +4,11 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -21,30 +19,26 @@ var branchPrefixColorCache = make(map[string]style.TextStyle)
func GetBranchListDisplayStrings(
branches []*models.Branch,
getItemOperation func(item types.HasUrn) types.ItemOperation,
fullDescription bool,
diffName string,
tr *i18n.TranslationSet,
userConfig *config.UserConfig,
worktrees []*models.Worktree,
mergedBranches *set.Set[string],
) [][]string {
return lo.Map(branches, func(branch *models.Branch, _ int) []string {
diffed := branch.Name == diffName
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, tr, userConfig, worktrees, mergedBranches)
return getBranchDisplayStrings(branch, fullDescription, diffed, tr, userConfig, worktrees)
})
}
// getBranchDisplayStrings returns the display string of branch
func getBranchDisplayStrings(
b *models.Branch,
itemOperation types.ItemOperation,
fullDescription bool,
diffed bool,
tr *i18n.TranslationSet,
userConfig *config.UserConfig,
worktrees []*models.Worktree,
mergedBranches *set.Set[string],
) []string {
displayName := b.Name
if b.DisplayName != "" {
@@ -57,7 +51,7 @@ func getBranchDisplayStrings(
}
coloredName := nameTextStyle.Sprint(displayName)
branchStatus := utils.WithPadding(ColoredBranchStatus(b, itemOperation, tr), 2, utils.AlignLeft)
branchStatus := utils.WithPadding(ColoredBranchStatus(b, tr), 2, utils.AlignLeft)
if git_commands.CheckedOutByOtherWorktree(b, worktrees) {
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
@@ -77,15 +71,7 @@ func getBranchDisplayStrings(
}
if fullDescription || userConfig.Gui.ShowBranchCommitHash {
var commitStyle style.TextStyle
if b.HasCommitsToPush() {
commitStyle = getShaStyleFromStatus(models.StatusUnpushed)
} else if mergedBranches.Includes(b.FullRefName()) {
commitStyle = getShaStyleFromStatus(models.StatusMerged)
} else {
commitStyle = getShaStyleFromStatus(models.StatusPushed)
}
res = append(res, commitStyle.Sprint(utils.ShortSha(b.CommitHash)))
res = append(res, utils.ShortSha(b.CommitHash))
}
res = append(res, coloredName)
@@ -123,11 +109,9 @@ func GetBranchTextStyle(name string) style.TextStyle {
}
}
func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
func ColoredBranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
colour := style.FgYellow
if itemOperation != types.ItemOperationNone {
colour = style.FgCyan
} else if branch.UpstreamGone {
if branch.UpstreamGone {
colour = style.FgRed
} else if branch.MatchesUpstream() {
colour = style.FgGreen
@@ -135,15 +119,10 @@ func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperatio
colour = style.FgMagenta
}
return colour.Sprint(BranchStatus(branch, itemOperation, tr))
return colour.Sprint(BranchStatus(branch, tr))
}
func BranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
itemOperationStr := itemOperationToString(itemOperation, tr)
if itemOperationStr != "" {
return itemOperationStr + " " + utils.Loader()
}
func BranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
if !branch.IsTrackingRemote() {
return ""
}
+14 -19
View File
@@ -413,8 +413,20 @@ func getShaColor(
}
diffed := commit.Sha != "" && commit.Sha == diffName
shaColor := getShaStyleFromStatus(commit.Status)
shaColor := theme.DefaultTextColor
switch commit.Status {
case models.StatusUnpushed:
shaColor = style.FgRed
case models.StatusPushed:
shaColor = style.FgYellow
case models.StatusMerged:
shaColor = style.FgGreen
case models.StatusRebasing:
shaColor = style.FgBlue
case models.StatusReflog:
shaColor = style.FgBlue
default:
}
if diffed {
shaColor = theme.DiffTerminalColor
@@ -427,23 +439,6 @@ func getShaColor(
return shaColor
}
func getShaStyleFromStatus(status models.CommitStatus) style.TextStyle {
switch status {
case models.StatusUnpushed:
return style.FgRed
case models.StatusPushed:
return style.FgYellow
case models.StatusMerged:
return style.FgGreen
case models.StatusRebasing:
return style.FgBlue
case models.StatusReflog:
return style.FgBlue
default:
return theme.DefaultTextColor
}
}
func actionColorMap(action todo.TodoCommand) style.TextStyle {
switch action {
case todo.Pick:
-23
View File
@@ -1,23 +0,0 @@
package presentation
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
)
func itemOperationToString(itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
switch itemOperation {
case types.ItemOperationNone:
return ""
case types.ItemOperationPushing:
return tr.PushingStatus
case types.ItemOperationPulling:
return tr.PullingStatus
case types.ItemOperationFastForwarding:
return tr.FastForwarding
case types.ItemOperationDeleting:
return tr.DeletingStatus
}
return ""
}
+2 -3
View File
@@ -7,15 +7,14 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
)
func FormatStatus(repoName string, currentBranch *models.Branch, itemOperation types.ItemOperation, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
func FormatStatus(repoName string, currentBranch *models.Branch, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
status := ""
if currentBranch.IsRealBranch() {
status += ColoredBranchStatus(currentBranch, itemOperation, tr) + " "
status += ColoredBranchStatus(currentBranch, tr) + " "
}
if workingTreeState != enums.REBASE_MODE_NONE {
+4 -17
View File
@@ -4,27 +4,19 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
func GetTagListDisplayStrings(
tags []*models.Tag,
getItemOperation func(item types.HasUrn) types.ItemOperation,
diffName string,
tr *i18n.TranslationSet,
) [][]string {
func GetTagListDisplayStrings(tags []*models.Tag, diffName string) [][]string {
return lo.Map(tags, func(tag *models.Tag, _ int) []string {
diffed := tag.Name == diffName
return getTagDisplayStrings(tag, getItemOperation(tag), diffed, tr)
return getTagDisplayStrings(tag, diffed)
})
}
// getTagDisplayStrings returns the display string of branch
func getTagDisplayStrings(t *models.Tag, itemOperation types.ItemOperation, diffed bool, tr *i18n.TranslationSet) []string {
func getTagDisplayStrings(t *models.Tag, diffed bool) []string {
textStyle := theme.DefaultTextColor
if diffed {
textStyle = theme.DiffTerminalColor
@@ -34,11 +26,6 @@ func getTagDisplayStrings(t *models.Tag, itemOperation types.ItemOperation, diff
res = append(res, textStyle.Sprint(icons.IconForTag(t)))
}
descriptionColor := style.FgYellow
descriptionStr := descriptionColor.Sprint(t.Description())
itemOperationStr := itemOperationToString(itemOperation, tr)
if itemOperationStr != "" {
descriptionStr = style.FgCyan.Sprint(itemOperationStr+" "+utils.Loader()) + " " + descriptionStr
}
res = append(res, textStyle.Sprint(t.Name), descriptionStr)
res = append(res, textStyle.Sprint(t.Name), descriptionColor.Sprint(t.Description()))
return res
}
+4 -6
View File
@@ -45,12 +45,10 @@ func (gui *Gui) handleTestMode() {
log.Fatal("gocui should have already exited")
}()
if os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) == "" {
go utils.Safe(func() {
time.Sleep(time.Second * 40)
log.Fatal("40 seconds is up, lazygit recording took too long to complete")
})
}
go utils.Safe(func() {
time.Sleep(time.Second * 40)
log.Fatal("40 seconds is up, lazygit recording took too long to complete")
})
}
}
+10 -39
View File
@@ -1,7 +1,6 @@
package types
import (
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -88,12 +87,6 @@ type IGuiCommon interface {
// resized, if in accordion mode.
AfterLayout(f func() error)
// Wraps a function, attaching the given operation to the given item while
// the function is executing, and also causes the given context to be
// redrawn periodically. This allows the operation to be visualized with a
// spinning loader animation (e.g. when a branch is being pushed).
WithInlineStatus(item HasUrn, operation ItemOperation, contextKey ContextKey, f func(gocui.Task) error) error
// returns the gocui Gui struct. There is a good chance you don't actually want to use
// this struct and instead want to use another method above
GocuiGui() *gocui.Gui
@@ -224,17 +217,15 @@ type MenuItem struct {
}
type Model struct {
CommitFiles []*models.CommitFile
Files []*models.File
Submodules []*models.SubmoduleConfig
Branches []*models.Branch
// set of branch names of the form 'refs/heads/mybranch'
MergedBranches *set.Set[string]
Commits []*models.Commit
StashEntries []*models.StashEntry
SubCommits []*models.Commit
Remotes []*models.Remote
Worktrees []*models.Worktree
CommitFiles []*models.CommitFile
Files []*models.File
Submodules []*models.SubmoduleConfig
Branches []*models.Branch
Commits []*models.Commit
StashEntries []*models.StashEntry
SubCommits []*models.Commit
Remotes []*models.Remote
Worktrees []*models.Worktree
// FilteredReflogCommits are the ones that appear in the reflog panel.
// when in filtering mode we only include the ones that match the given path
@@ -265,6 +256,7 @@ type Mutexes struct {
RefreshingFilesMutex *deadlock.Mutex
RefreshingBranchesMutex *deadlock.Mutex
RefreshingStatusMutex *deadlock.Mutex
SyncMutex *deadlock.Mutex
LocalCommitsMutex *deadlock.Mutex
SubCommitsMutex *deadlock.Mutex
AuthorsMutex *deadlock.Mutex
@@ -273,24 +265,6 @@ type Mutexes struct {
PtyMutex *deadlock.Mutex
}
// A long-running operation associated with an item. For example, we'll show
// that a branch is being pushed from so that there's visual feedback about
// what's happening and so that you can see multiple branches' concurrent
// operations
type ItemOperation int
const (
ItemOperationNone ItemOperation = iota
ItemOperationPushing
ItemOperationPulling
ItemOperationFastForwarding
ItemOperationDeleting
)
type HasUrn interface {
URN() string
}
type IStateAccessor interface {
GetRepoPathStack() *utils.StringStack
GetRepoState() IRepoStateAccessor
@@ -303,9 +277,6 @@ type IStateAccessor interface {
SetShowExtrasWindow(bool)
GetRetainOriginalDir() bool
SetRetainOriginalDir(bool)
GetItemOperation(item HasUrn) ItemOperation
SetItemOperation(item HasUrn, operation ItemOperation)
ClearItemOperation(item HasUrn)
}
type IRepoStateAccessor interface {
-1
View File
@@ -102,7 +102,6 @@ type IFilterableContext interface {
SetFilter(string)
GetFilter() string
ClearFilter()
ReApplyFilter()
IsFiltering() bool
IsFilterableContext()
}
+1 -1
View File
@@ -169,7 +169,7 @@ func chineseTranslationSet() TranslationSet {
ToggleStagingPanel: `切换到其他面板`,
ReturnToFilesPanel: `返回文件面板`,
FastForward: `从上游快进此分支`,
FastForwarding: "抓取并快进",
FastForwarding: "抓取并快进 {{.branch}} ...",
FoundConflictsTitle: "自动合并失败",
ViewMergeRebaseOptions: "查看 合并/变基 选项",
NotMergingOrRebasing: "您目前既不进行变基也不进行合并",
+1 -1
View File
@@ -134,7 +134,7 @@ func dutchTranslationSet() TranslationSet {
ToggleStagingPanel: `Ga naar een ander paneel`,
ReturnToFilesPanel: `Ga terug naar het bestanden paneel`,
FastForward: `Fast-forward deze branch vanaf zijn upstream`,
FastForwarding: "Fast-forwarding",
FastForwarding: "Fast-forwarding {{.branch}} ...",
FoundConflictsTitle: "Conflicten!",
ViewMergeRebaseOptions: "Bekijk merge/rebase opties",
NotMergingOrRebasing: "Je bent momenteel niet aan het rebasen of mergen",
+1 -5
View File
@@ -57,8 +57,6 @@ type TranslationSet struct {
ResetFilter string
MergeConflictsTitle string
Checkout string
CantCheckoutBranchWhilePulling string
CantPullOrPushSameBranchTwice string
NoChangedFiles string
SoftReset string
AlreadyCheckedOutBranch string
@@ -848,8 +846,6 @@ func EnglishTranslationSet() TranslationSet {
Scroll: "Scroll",
MergeConflictsTitle: "Merge conflicts",
Checkout: "Checkout",
CantCheckoutBranchWhilePulling: "You cannot checkout another branch while pulling the current branch",
CantPullOrPushSameBranchTwice: "You cannot push or pull a branch while it is already being pushed or pulled",
FileFilter: "Filter files by status",
FilterStagedFiles: "Show only staged files",
FilterUnstagedFiles: "Show only unstaged files",
@@ -984,7 +980,7 @@ func EnglishTranslationSet() TranslationSet {
ToggleStagingPanel: `Switch to other panel (staged/unstaged changes)`,
ReturnToFilesPanel: `Return to files panel`,
FastForward: `Fast-forward this branch from its upstream`,
FastForwarding: "Fast-forwarding",
FastForwarding: "Fast-forwarding {{.branch}}",
FoundConflictsTitle: "Conflicts!",
ViewConflictsMenuItem: "View conflicts",
AbortMenuItem: "Abort the %s",
+1 -1
View File
@@ -170,7 +170,7 @@ func koreanTranslationSet() TranslationSet {
ToggleStagingPanel: `패널 전환`,
ReturnToFilesPanel: `파일 목록으로 돌아가기`,
FastForward: `Fast-forward this branch from its upstream`,
FastForwarding: "Fast-forwarding",
FastForwarding: "Fast-forwarding {{.branch}} ...",
FoundConflictsTitle: "Auto-merge failed",
ViewMergeRebaseOptions: "View merge/rebase options",
NotMergingOrRebasing: "You are currently neither rebasing nor merging",
+1 -1
View File
@@ -201,7 +201,7 @@ func RussianTranslationSet() TranslationSet {
ToggleStagingPanel: `Переключиться на другую панель (проиндексированные/непроиндексированные изменения)`,
ReturnToFilesPanel: `Вернуться к панели файлов`,
FastForward: `Перемотать эту ветку вперёд из её upstream-ветки`,
FastForwarding: "Получить изменения и перемотать вперёд",
FastForwarding: "Получить изменения и перемотать вперёд {{.branch}} ...",
FoundConflictsTitle: "Конфликты!",
ViewConflictsMenuItem: "Просмотр конфликтов",
AbortMenuItem: "Прервать %s",
+1 -1
View File
@@ -234,7 +234,7 @@ func traditionalChineseTranslationSet() TranslationSet {
ToggleStagingPanel: `切換至另一個面板 (已預存/未預存更改)`,
ReturnToFilesPanel: `返回檔案面板`,
FastForward: `從上游快進此分支`,
FastForwarding: "的擷取和快進中",
FastForwarding: "{{.branch}} 的擷取和快進中...",
FoundConflictsTitle: "自動合併失敗",
ViewMergeRebaseOptions: "查看合併/變基選項",
NotMergingOrRebasing: "你當前既不在變基也不在合併中",
+1 -1
View File
@@ -31,7 +31,7 @@ func main() {
integrationTest := getIntegrationTest()
if os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) != "" {
if os.Getenv("WAIT_FOR_DEBUGGER") != "" {
println("Waiting for debugger to attach...")
for !isDebuggerAttached() {
time.Sleep(time.Millisecond * 100)
+3 -9
View File
@@ -16,7 +16,6 @@ import (
const (
TEST_NAME_ENV_VAR = "TEST_NAME"
SANDBOX_ENV_VAR = "SANDBOX"
WAIT_FOR_DEBUGGER_ENV_VAR = "WAIT_FOR_DEBUGGER"
GIT_CONFIG_GLOBAL_ENV_VAR = "GIT_CONFIG_GLOBAL"
)
@@ -43,7 +42,7 @@ func RunTests(
testDir := filepath.Join(projectRootDir, "test", "_results")
if err := buildLazygit(waitForDebugger, raceDetector); err != nil {
if err := buildLazygit(raceDetector); err != nil {
return err
}
@@ -139,17 +138,12 @@ func prepareTestDir(
return createFixture(test, paths, rootDir)
}
func buildLazygit(debug bool, raceDetector bool) error {
func buildLazygit(raceDetector bool) error {
// // TODO: remove this line!
// // skipping this because I'm not making changes to the app code atm.
// return nil
args := []string{"go", "build"}
if debug {
// Disable compiler optimizations (-N) and inlining (-l) because this
// makes debugging work better
args = append(args, "-gcflags=all=-N -l")
}
if raceDetector {
args = append(args, "-race")
}
@@ -216,7 +210,7 @@ func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandb
cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", SANDBOX_ENV_VAR, "true"))
}
if waitForDebugger {
cmdObj.AddEnvVars(fmt.Sprintf("%s=true", WAIT_FOR_DEBUGGER_ENV_VAR))
cmdObj.AddEnvVars("WAIT_FOR_DEBUGGER=true")
}
// Set a race detector log path only to avoid spamming the terminal with the
// logs. We are not showing this anywhere yet.
@@ -1,42 +0,0 @@
package filter_and_search
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var FilterRemotes = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Filtering remotes",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("commit-one")
shell.CloneIntoRemote("remote1")
shell.CloneIntoRemote("remote2")
shell.CloneIntoRemote("remote3")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Remotes().
Focus().
Lines(
Contains("remote1").IsSelected(),
Contains("remote2"),
Contains("remote3"),
).
FilterOrSearch("2").
Lines(
Contains("remote2").IsSelected(),
).
// cancel the filter
PressEscape().
Tap(func() {
t.Views().Search().IsInvisible()
}).
Lines(
Contains("remote1"),
Contains("remote2").IsSelected(),
Contains("remote3"),
)
},
})
@@ -1,52 +0,0 @@
package filter_and_search
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var FilterUpdatesWhenModelChanges = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Verify that after deleting a branch the filter is reapplied to show only the remaining branches",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("first commit")
shell.NewBranch("branch-to-delete")
shell.NewBranch("other")
shell.NewBranch("checked-out-branch")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Branches().
Focus().
Lines(
Contains("checked-out-branch").IsSelected(),
Contains("other"),
Contains("branch-to-delete"),
Contains("master"),
).
FilterOrSearch("branch").
Lines(
Contains("branch-to-delete").IsSelected(),
Contains("checked-out-branch"),
).
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().
Menu().
Title(Equals("Delete branch 'branch-to-delete'?")).
Select(Contains("Delete local branch")).
Confirm()
}).
Lines(
Contains("checked-out-branch").IsSelected(),
).
// cancel the filter
PressEscape().
Lines(
Contains("checked-out-branch").IsSelected(),
Contains("other"),
Contains("master"),
)
},
})
@@ -1,26 +0,0 @@
package misc
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var DisabledKeybindings = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Confirms you can disable keybindings by setting them to <disabled>",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.UserConfig.Keybinding.Universal.PrevItem = "<disabled>"
config.UserConfig.Keybinding.Universal.NextItem = "<disabled>"
config.UserConfig.Keybinding.Universal.NextTab = "<up>"
config.UserConfig.Keybinding.Universal.PrevTab = "<down>"
},
SetupRepo: func(shell *Shell) {},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Press("<up>")
t.Views().Worktrees().IsFocused()
},
})
-3
View File
@@ -131,9 +131,7 @@ var tests = []*components.IntegrationTest{
filter_and_search.FilterMenu,
filter_and_search.FilterMenuCancelFilterWithEscape,
filter_and_search.FilterRemoteBranches,
filter_and_search.FilterRemotes,
filter_and_search.FilterSearchHistory,
filter_and_search.FilterUpdatesWhenModelChanges,
filter_and_search.NestedFilter,
filter_and_search.NestedFilterTransient,
filter_and_search.NewSearch,
@@ -172,7 +170,6 @@ var tests = []*components.IntegrationTest{
interactive_rebase.SwapWithConflict,
misc.ConfirmOnQuit,
misc.CopyToClipboard,
misc.DisabledKeybindings,
misc.InitialOpen,
misc.RecentReposOnLaunch,
patch_building.Apply,
@@ -19,8 +19,6 @@ import (
)
func main() {
println("Generating test_list.go...")
code := generateCode()
formattedCode, err := format.Source(code)
+1 -26
View File
@@ -1,10 +1,6 @@
package utils
import (
"sync"
"golang.org/x/exp/slices"
)
import "golang.org/x/exp/slices"
// NextIndex returns the index of the element that comes after the given number
func NextIndex(numbers []int, currentNumber int) int {
@@ -183,24 +179,3 @@ func Shift[T any](slice []T) (T, []T) {
slice = slice[1:]
return value, slice
}
// Map function which handles each element in its own goroutine
func ConcurrentMap[T any, V any](items []T, fn func(T) V) []V {
results := make([]V, len(items))
var wg sync.WaitGroup
for i, item := range items {
i := i
item := item
wg.Add(1)
go func() {
defer wg.Done()
results[i] = fn(item)
}()
}
wg.Wait()
return results
}
-10
View File
@@ -315,13 +315,3 @@ func TestMoveElement(t *testing.T) {
})
})
}
func TestConcurrentMap(t *testing.T) {
in := []int{1, 2, 3}
out := ConcurrentMap(in, func(i int) int {
return i * 2
})
assert.EqualValues(t, []int{2, 4, 6}, out)
}
+27
View File
@@ -0,0 +1,27 @@
package main
import (
"fmt"
"log"
"os"
"github.com/jesseduffield/lazygit/pkg/cheatsheet"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("Please provide a command: one of 'generate', 'check'")
}
command := os.Args[1]
switch command {
case "generate":
cheatsheet.Generate()
fmt.Printf("\nGenerated cheatsheets in %s\n", cheatsheet.GetKeybindingsDir())
case "check":
cheatsheet.Check()
default:
log.Fatal("\nUnknown command. Expected one of 'generate', 'check'")
}
}