mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-29 02:35:39 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17cb09fa7b | ||
|
|
4797734a1d | ||
|
|
a54b3f0279 | ||
|
|
afbde5be3c | ||
|
|
aac8bd4676 | ||
|
|
70cfc565e7 | ||
|
|
d0ede21e9c | ||
|
|
8b049be31d | ||
|
|
a8dc4aaf1b |
@@ -120,6 +120,10 @@ separate, reviewable commit that the user decides when to fold in. A bare
|
||||
`--amend` rewrites the commit on the spot and skips that checkpoint. Don't
|
||||
treat "I'm only touching the tip commit" as an exception.
|
||||
|
||||
Always use `fixup!` or `amend!` commits, never amend changes directly, even if
|
||||
you naturally would because "the branch isn't pushed yet". The user always wants
|
||||
to review what you changed, so make this transparent; no exceptions.
|
||||
|
||||
**When the tip is the wrong place for a fixup, insert it mid-branch.**
|
||||
Committing a fixup at the tip of the branch only works while the code it
|
||||
touches still looks the same there; once later commits have rewritten that
|
||||
|
||||
@@ -16,7 +16,7 @@ require (
|
||||
github.com/cli/go-gh/v2 v2.13.0
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/gdamore/tcell/v3 v3.4.2
|
||||
github.com/gdamore/tcell/v3 v3.5.0
|
||||
github.com/go-errors/errors v1.5.1
|
||||
github.com/gookit/color v1.6.1
|
||||
github.com/integrii/flaggy v1.8.0
|
||||
|
||||
@@ -30,8 +30,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
github.com/gdamore/tcell/v3 v3.4.2 h1:gGW+6z2Bz5Wl2mNwFlm9+eRmg2JQrWcKjSkL1LRfpNU=
|
||||
github.com/gdamore/tcell/v3 v3.4.2/go.mod h1:Oe5U3S3jm3NzypswDNUhe+LUnF5CoFq2b4sepD++QHo=
|
||||
github.com/gdamore/tcell/v3 v3.5.0 h1:SCp9czLv2K2aPORgD6+4fjV0xNAzKIkiezCkp6bHLe4=
|
||||
github.com/gdamore/tcell/v3 v3.5.0/go.mod h1:Oe5U3S3jm3NzypswDNUhe+LUnF5CoFq2b4sepD++QHo=
|
||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
|
||||
+3
-4
@@ -61,10 +61,9 @@ func Run(
|
||||
}
|
||||
}
|
||||
|
||||
func NewCommon(config config.AppConfigurer) (*common.Common, error) {
|
||||
func NewCommon(config config.AppConfigurer, log *logrus.Entry) (*common.Common, error) {
|
||||
userConfig := config.GetUserConfig()
|
||||
appState := config.GetAppState()
|
||||
log := newLogger(config)
|
||||
// Initialize with English for the time being; the real translation set for
|
||||
// the configured language will be read after reading the user config
|
||||
tr := i18n.EnglishTranslationSet()
|
||||
@@ -80,8 +79,8 @@ func NewCommon(config config.AppConfigurer) (*common.Common, error) {
|
||||
return cmn, nil
|
||||
}
|
||||
|
||||
func newLogger(cfg config.AppConfigurer) *logrus.Entry {
|
||||
if cfg.GetDebug() {
|
||||
func NewLogger(debug bool) *logrus.Entry {
|
||||
if debug {
|
||||
logPath, err := config.LogPath()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
+21
-22
@@ -3,14 +3,13 @@ package daemon
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
|
||||
@@ -66,14 +65,14 @@ func getInstruction() Instruction {
|
||||
return mapping[getDaemonKind()](jsonData)
|
||||
}
|
||||
|
||||
func Handle(common *common.Common) {
|
||||
func Handle(log *logrus.Entry) {
|
||||
if !InDaemonMode() {
|
||||
return
|
||||
}
|
||||
|
||||
instruction := getInstruction()
|
||||
|
||||
if err := instruction.run(common); err != nil {
|
||||
if err := instruction.run(log); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -107,7 +106,7 @@ type Instruction interface {
|
||||
SerializedInstructions() string
|
||||
|
||||
// runs the instruction
|
||||
run(common *common.Common) error
|
||||
run(log *logrus.Entry) error
|
||||
}
|
||||
|
||||
func serializeInstruction[T any](instruction T) string {
|
||||
@@ -147,7 +146,7 @@ func (self *ExitImmediatelyInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *ExitImmediatelyInstruction) run(common *common.Common) error {
|
||||
func (self *ExitImmediatelyInstruction) run(log *logrus.Entry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -165,8 +164,8 @@ func (self *RemoveUpdateRefsForCopiedBranchInstruction) SerializedInstructions()
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *RemoveUpdateRefsForCopiedBranchInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
func (self *RemoveUpdateRefsForCopiedBranchInstruction) run(log *logrus.Entry) error {
|
||||
return handleInteractiveRebase(log, func(path string) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -193,8 +192,8 @@ func (self *ChangeTodoActionsInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
func (self *ChangeTodoActionsInstruction) run(log *logrus.Entry) error {
|
||||
return handleInteractiveRebase(log, func(path string) error {
|
||||
changes := lo.Map(self.Changes, func(c ChangeTodoAction, _ int) utils.TodoChange {
|
||||
return utils.TodoChange{
|
||||
Hash: c.Hash,
|
||||
@@ -225,8 +224,8 @@ func (self *DropMergeCommitInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *DropMergeCommitInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
func (self *DropMergeCommitInstruction) run(log *logrus.Entry) error {
|
||||
return handleInteractiveRebase(log, func(path string) error {
|
||||
return utils.DropMergeCommit(path, self.Hash, getCommentChar())
|
||||
})
|
||||
}
|
||||
@@ -256,8 +255,8 @@ func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
func (self *MoveFixupCommitDownInstruction) run(log *logrus.Entry) error {
|
||||
return handleInteractiveRebase(log, func(path string) error {
|
||||
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, self.ChangeToFixup, getCommentChar())
|
||||
})
|
||||
}
|
||||
@@ -282,14 +281,14 @@ func (self *MoveTodosUpInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *MoveTodosUpInstruction) run(common *common.Common) error {
|
||||
func (self *MoveTodosUpInstruction) run(log *logrus.Entry) error {
|
||||
todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Hash: hash,
|
||||
}
|
||||
})
|
||||
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return handleInteractiveRebase(log, func(path string) error {
|
||||
return utils.MoveTodos(path, todosToMove, false, -self.Distance, getCommentChar())
|
||||
})
|
||||
}
|
||||
@@ -314,14 +313,14 @@ func (self *MoveTodosDownInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *MoveTodosDownInstruction) run(common *common.Common) error {
|
||||
func (self *MoveTodosDownInstruction) run(log *logrus.Entry) error {
|
||||
todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Hash: hash,
|
||||
}
|
||||
})
|
||||
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return handleInteractiveRebase(log, func(path string) error {
|
||||
return utils.MoveTodos(path, todosToMove, false, self.Distance, getCommentChar())
|
||||
})
|
||||
}
|
||||
@@ -340,8 +339,8 @@ func (self *InsertBreakInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *InsertBreakInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
func (self *InsertBreakInstruction) run(log *logrus.Entry) error {
|
||||
return handleInteractiveRebase(log, func(path string) error {
|
||||
return utils.PrependStrToTodoFile(path, []byte("break\n"))
|
||||
})
|
||||
}
|
||||
@@ -364,8 +363,8 @@ func (self *WriteRebaseTodoInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *WriteRebaseTodoInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
func (self *WriteRebaseTodoInstruction) run(log *logrus.Entry) error {
|
||||
return handleInteractiveRebase(log, func(path string) error {
|
||||
return os.WriteFile(path, self.TodosFileContent, 0o644)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stefanhaller/git-todo-parser/todo"
|
||||
)
|
||||
|
||||
@@ -17,9 +17,9 @@ type ChangeTodoAction struct {
|
||||
Flag string
|
||||
}
|
||||
|
||||
func handleInteractiveRebase(common *common.Common, f func(path string) error) error {
|
||||
common.Log.Info("Lazygit invoked as interactive rebase demon")
|
||||
common.Log.Info("args: ", os.Args)
|
||||
func handleInteractiveRebase(log *logrus.Entry, f func(path string) error) error {
|
||||
log.Info("Lazygit invoked as interactive rebase demon")
|
||||
log.Info("args: ", os.Args)
|
||||
path := os.Args[1]
|
||||
|
||||
if strings.HasSuffix(path, "git-rebase-todo") {
|
||||
@@ -32,7 +32,7 @@ func handleInteractiveRebase(common *common.Common, f func(path string) error) e
|
||||
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
|
||||
// but in this case we don't need to edit it, so we'll just return
|
||||
} else {
|
||||
common.Log.Info("Lazygit demon did not match on any use cases")
|
||||
log.Info("Lazygit demon did not match on any use cases")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+10
-6
@@ -93,6 +93,15 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
|
||||
env.SetGitDirEnv(cliArgs.GitDir)
|
||||
}
|
||||
|
||||
// The log file lives in the config dir, so this must come after setting the
|
||||
// CONFIG_DIR env var above.
|
||||
logger := NewLogger(cliArgs.Debug)
|
||||
|
||||
if daemon.InDaemonMode() {
|
||||
daemon.Handle(logger)
|
||||
return
|
||||
}
|
||||
|
||||
if cliArgs.PrintVersionInfo {
|
||||
gitVersion := getGitVersionInfo()
|
||||
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s, git version=%s\n", buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, buildInfo.Version, runtime.GOOS, runtime.GOARCH, gitVersion)
|
||||
@@ -154,16 +163,11 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
|
||||
appConfig.SaveGlobalUserConfig()
|
||||
}
|
||||
|
||||
common, err := NewCommon(appConfig)
|
||||
common, err := NewCommon(appConfig, logger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if daemon.InDaemonMode() {
|
||||
daemon.Handle(common)
|
||||
return
|
||||
}
|
||||
|
||||
if cliArgs.Profile {
|
||||
go func() {
|
||||
if err := http.ListenAndServe("localhost:6060", nil); err != nil {
|
||||
|
||||
@@ -19,12 +19,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ func CommandToRun() string {
|
||||
}
|
||||
|
||||
func GetKeybindingsDir() string {
|
||||
return utils.GetLazyRootDirectory() + "/docs-master/keybindings"
|
||||
return utils.MustFindLazygitRootDirectory() + "/docs-master/keybindings"
|
||||
}
|
||||
|
||||
func generateAtDir(cheatsheetDir string) {
|
||||
@@ -58,10 +58,11 @@ func generateAtDir(cheatsheetDir string) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
mConfig := config.NewDummyAppConfig()
|
||||
logger := app.NewLogger(mConfig.GetDebug())
|
||||
|
||||
for lang := range translationSetsByLang {
|
||||
mConfig.GetUserConfig().Gui.Language = lang
|
||||
common, err := app.NewCommon(mConfig)
|
||||
common, err := app.NewCommon(mConfig, logger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -54,7 +54,7 @@ func runAndPrintFatalError(test *components.IntegrationTest, f func() error) {
|
||||
}
|
||||
|
||||
func getTestsToRun(testNames []string) []*components.IntegrationTest {
|
||||
allIntegrationTests := tests.GetTests(utils.GetLazyRootDirectory())
|
||||
allIntegrationTests := tests.GetTests(utils.MustFindLazygitRootDirectory())
|
||||
var testsToRun []*components.IntegrationTest
|
||||
|
||||
if len(testNames) == 0 {
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -37,8 +37,13 @@ func TestIntegration(t *testing.T) {
|
||||
codeCoverageDir := os.Getenv("LAZYGIT_GOCOVERDIR")
|
||||
testNumber := 0
|
||||
|
||||
err := components.RunTests(components.RunTestArgs{
|
||||
Tests: tests.GetTests(utils.GetLazyRootDirectory()),
|
||||
rootDir, err := utils.FindLazygitRootDirectory()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = components.RunTests(components.RunTestArgs{
|
||||
Tests: tests.GetTests(rootDir),
|
||||
Logf: t.Logf,
|
||||
RunCmd: runCmdHeadless,
|
||||
TestWrapper: func(test *components.IntegrationTest, f func() error) {
|
||||
|
||||
@@ -9,12 +9,13 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||
lazycoreUtils "github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -23,7 +24,7 @@ import (
|
||||
var SLOW_INPUT_DELAY = 600
|
||||
|
||||
func RunTUI(raceDetector bool) {
|
||||
rootDir := utils.GetLazyRootDirectory()
|
||||
rootDir := utils.MustFindLazygitRootDirectory()
|
||||
testDir := filepath.Join(rootDir, "test", "integration")
|
||||
|
||||
app := newApp(testDir)
|
||||
@@ -206,7 +207,7 @@ type app struct {
|
||||
}
|
||||
|
||||
func newApp(testDir string) *app {
|
||||
return &app{testDir: testDir, allTests: tests.GetTests(utils.GetLazyRootDirectory())}
|
||||
return &app{testDir: testDir, allTests: tests.GetTests(utils.MustFindLazygitRootDirectory())}
|
||||
}
|
||||
|
||||
func (self *app) getCurrentTest() *components.IntegrationTest {
|
||||
@@ -224,7 +225,7 @@ func (self *app) loadTests() {
|
||||
}
|
||||
|
||||
func (self *app) adjustCursor() {
|
||||
self.itemIdx = utils.Clamp(self.itemIdx, 0, len(self.filteredTests)-1)
|
||||
self.itemIdx = lazycoreUtils.Clamp(self.itemIdx, 0, len(self.filteredTests)-1)
|
||||
}
|
||||
|
||||
func (self *app) filterWithString(needle string) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
lazycoreUtils "github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -40,12 +39,15 @@ type RunTestArgs struct {
|
||||
// showing what's actually happening during the test, but it's still good at running
|
||||
// tests in telling you about their results.
|
||||
func RunTests(args RunTestArgs) error {
|
||||
projectRootDir := lazycoreUtils.GetLazyRootDirectory()
|
||||
err := os.Chdir(projectRootDir)
|
||||
projectRootDir, err := utils.FindLazygitRootDirectory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Chdir(projectRootDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
testDir := filepath.Join(projectRootDir, "test", "_results")
|
||||
if err := buildLazygit(args); err != nil {
|
||||
return err
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
lazycoreUtils "github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"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/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -227,7 +227,12 @@ func TestFailingFixture(t *testing.T) {
|
||||
paths := NewPaths(t.TempDir())
|
||||
assert.NoError(t, os.MkdirAll(paths.ActualRepo(), 0o777))
|
||||
|
||||
workingDir, err := createFixture(test, paths, lazycoreUtils.GetLazyRootDirectory())
|
||||
rootDir, err := utils.FindLazygitRootDirectory()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
workingDir, err := createFixture(test, paths, rootDir)
|
||||
|
||||
assert.ErrorContains(t, err, "git checkout no-such-branch")
|
||||
assert.Empty(t, workingDir)
|
||||
|
||||
@@ -9,14 +9,14 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/karimkhaleel/jsonschema"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func GetSchemaDir() string {
|
||||
return utils.GetLazyRootDirectory() + "/schema-master"
|
||||
return utils.MustFindLazygitRootDirectory() + "/schema-master"
|
||||
}
|
||||
|
||||
func GenerateSchema() *jsonschema.Schema {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/karimkhaleel/jsonschema"
|
||||
"github.com/samber/lo"
|
||||
|
||||
@@ -163,7 +163,7 @@ func (n *Node) MarshalYAML() (any, error) {
|
||||
}
|
||||
|
||||
func writeToConfigDocs(config []byte) error {
|
||||
configPath := utils.GetLazyRootDirectory() + "/docs-master/Config.md"
|
||||
configPath := utils.MustFindLazygitRootDirectory() + "/docs-master/Config.md"
|
||||
markdown, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading Config.md file %w", err)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// FindLazygitRootDirectory returns the root directory of the lazygit source
|
||||
// tree, by searching the working directory and its parents for the go.mod file
|
||||
// that declares lazygit's module. Only development tools use it: the
|
||||
// integration test runner, the cheatsheet generator, and the JSON schema
|
||||
// generator. Not to be confused with finding the root directory of the
|
||||
// repository that lazygit is being run in.
|
||||
//
|
||||
// We search upwards rather than expect to be called from the root directory,
|
||||
// because `go test` runs each test binary in the source directory of its
|
||||
// package, not in the directory that `go test` was invoked from.
|
||||
func FindLazygitRootDirectory() (string, error) {
|
||||
startDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dir := startDir
|
||||
for {
|
||||
if declaresLazygitModule(filepath.Join(dir, "go.mod")) {
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
return "", fmt.Errorf(
|
||||
"failed to find the lazygit root directory: there is no go.mod for a lazygit module in %s or any of its parent directories",
|
||||
startDir)
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
// MustFindLazygitRootDirectory is FindLazygitRootDirectory for tools that can't
|
||||
// do anything useful if the directory isn't found.
|
||||
func MustFindLazygitRootDirectory() string {
|
||||
dir, err := FindLazygitRootDirectory()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// A fork is free to rename the module, so we accept any module path with a
|
||||
// "lazygit" element in it, e.g. github.com/jesseduffield/lazygit.
|
||||
func declaresLazygitModule(goModPath string) bool {
|
||||
contents, err := os.ReadFile(goModPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return lo.SomeBy(strings.Split(string(contents), "\n"), func(line string) bool {
|
||||
fields := strings.Fields(line)
|
||||
return len(fields) >= 2 && fields[0] == "module" &&
|
||||
slices.Contains(strings.Split(fields[1], "/"), "lazygit")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFindLazygitRootDirectory(t *testing.T) {
|
||||
// This test runs in the pkg/utils directory, so we expect the function to
|
||||
// search two levels up for the project root.
|
||||
expectedRootDir, err := filepath.Abs(filepath.Join("..", ".."))
|
||||
assert.NoError(t, err)
|
||||
|
||||
rootDir, err := FindLazygitRootDirectory()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedRootDir, rootDir)
|
||||
}
|
||||
|
||||
func TestFindLazygitRootDirectoryOutsideProject(t *testing.T) {
|
||||
t.Chdir(t.TempDir())
|
||||
|
||||
_, err := FindLazygitRootDirectory()
|
||||
|
||||
assert.ErrorContains(t, err, "there is no go.mod for a lazygit module")
|
||||
}
|
||||
|
||||
func TestDeclaresLazygitModule(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
testName string
|
||||
contents string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
testName: "lazygit's go.mod",
|
||||
contents: "module github.com/jesseduffield/lazygit\n\ngo 1.25.0\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
testName: "a fork's go.mod",
|
||||
contents: "module gitlab.com/somebody-else/lazygit\n\ngo 1.25.0\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
testName: "a fork's go.mod with a major version suffix",
|
||||
contents: "module github.com/somebody-else/lazygit/v2\n\ngo 1.25.0\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
testName: "module declaration preceded by a comment",
|
||||
contents: "// a comment\n\nmodule github.com/jesseduffield/lazygit\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
testName: "module declaration followed by a comment",
|
||||
contents: "module github.com/jesseduffield/lazygit // comment\n\ngo 1.25.0\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
testName: "another project's go.mod",
|
||||
contents: "module github.com/jesseduffield/lazydocker\n\ngo 1.25.0\n",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
testName: "no module declaration",
|
||||
contents: "go 1.25.0\n",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "go.mod")
|
||||
assert.NoError(t, os.WriteFile(path, []byte(scenario.contents), 0o644))
|
||||
|
||||
assert.Equal(t, scenario.expected, declaresLazygitModule(path))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeclaresLazygitModuleWithoutAGoModFile(t *testing.T) {
|
||||
assert.False(t, declaresLazygitModule(filepath.Join(t.TempDir(), "go.mod")))
|
||||
}
|
||||
+42
@@ -252,3 +252,45 @@ func (cb *CellBuffer) Fill(r rune, style Style) {
|
||||
c.width = 1
|
||||
}
|
||||
}
|
||||
|
||||
// FillArea fills a rectangular region of the cell buffer with the specified
|
||||
// character and style. The region starts at column x, row y and extends w
|
||||
// columns to the right and h rows down; any part of it lying outside the
|
||||
// buffer is simply skipped, so callers do not need to clip coordinates
|
||||
// themselves. A zero or negative width or height fills nothing. As with
|
||||
// Fill, this doesn't support combining characters or wide runes, and a
|
||||
// ColorNone foreground or background leaves that color unchanged.
|
||||
func (cb *CellBuffer) FillArea(x, y, w, h int, r rune, style Style) {
|
||||
if w <= 0 || h <= 0 {
|
||||
return
|
||||
}
|
||||
x0 := max(x, 0)
|
||||
y0 := max(y, 0)
|
||||
// Clip the far edge to the buffer without ever evaluating x+w or y+h when
|
||||
// they would overflow a signed int. Because w and h are positive here,
|
||||
// x < cb.w-w is equivalent to x+w < cb.w but cannot overflow, and it is
|
||||
// only true when x+w is small enough to compute safely.
|
||||
x1 := cb.w
|
||||
if x < cb.w-w {
|
||||
x1 = x + w
|
||||
}
|
||||
y1 := cb.h
|
||||
if y < cb.h-h {
|
||||
y1 = y + h
|
||||
}
|
||||
for row := y0; row < y1; row++ {
|
||||
for col := x0; col < x1; col++ {
|
||||
c := &cb.cells[(row*cb.w)+col]
|
||||
c.currStr = string(r)
|
||||
cs := style
|
||||
if cs.fg == ColorNone {
|
||||
cs.fg = c.currStyle.fg
|
||||
}
|
||||
if cs.bg == ColorNone {
|
||||
cs.bg = c.currStyle.bg
|
||||
}
|
||||
c.currStyle = cs
|
||||
c.width = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
-19
@@ -64,9 +64,10 @@ const (
|
||||
const defaultControlStringLimit = 64 * 1024
|
||||
|
||||
const (
|
||||
// loneEscapeTimeout keeps bare Escape responsive when using legacy
|
||||
// keyboard reporting, where ESC can also prefix an Alt-modified key.
|
||||
loneEscapeTimeout = 200 * time.Millisecond
|
||||
// loneEscapeTimeout keeps bare Escape responsive. A lone ESC byte is
|
||||
// always ambiguous, because it can also prefix an Alt-modified key or a
|
||||
// longer sequence, so it cannot be resolved until this expires.
|
||||
loneEscapeTimeout = 50 * time.Millisecond
|
||||
|
||||
// escapeSequenceTimeout bounds incomplete escape sequences. Once a
|
||||
// sequence introducer has arrived, it is no longer ambiguous with a lone
|
||||
@@ -129,27 +130,15 @@ func asciiByteFromInt(n int) (byte, bool) {
|
||||
return byte(n), true
|
||||
}
|
||||
|
||||
// Waiting returns true if the processor is waiting for
|
||||
// some more input (i.e. we are not in in the initial state.)
|
||||
// This can occur when we have ambiguous escape sequences, such
|
||||
// as the lone escape. If this is typed, we expect at least a minimal
|
||||
// inter-key delay before the next stroke occurs, and the caller
|
||||
// should check for waiting, and call Scan() or ScanUTF8() to
|
||||
// finish the processing. (Typically after a delay of around 100ms.)
|
||||
func (ip *inputParser) Waiting() bool {
|
||||
ip.l.Lock()
|
||||
defer ip.l.Unlock()
|
||||
return ip.state != istInit
|
||||
}
|
||||
|
||||
// waitDuration reports how long to wait for the next byte before resetting an
|
||||
// incomplete escape sequence. A bare ESC is only ambiguous with legacy
|
||||
// keyboard reporting; other protocols can use the longer sequence deadline.
|
||||
// incomplete escape sequence. A bare ESC is ambiguous under every keyboard
|
||||
// protocol, so it gets the short deadline; only once an introducer has arrived
|
||||
// is the longer sequence deadline used.
|
||||
func (ip *inputParser) waitDuration() time.Duration {
|
||||
if ip.state == istInit {
|
||||
return 0
|
||||
}
|
||||
if ip.state == istEsc && ip.legacy {
|
||||
if ip.state == istEsc {
|
||||
return loneEscapeTimeout
|
||||
}
|
||||
return escapeSequenceTimeout
|
||||
@@ -867,6 +856,9 @@ func (ip *inputParser) handleXda(str string) {
|
||||
}
|
||||
|
||||
func calcModifier(n int) ModMask {
|
||||
if n < 1 {
|
||||
return ModNone
|
||||
}
|
||||
n--
|
||||
m := ModNone
|
||||
if n&1 != 0 {
|
||||
@@ -987,6 +979,26 @@ func kittyModifierKey(code int) ModMask {
|
||||
}
|
||||
}
|
||||
|
||||
// kittyKeyText extracts the associated text (kitty mode 16) from a csi-u
|
||||
// event's params: the third ;-field, codepoints :separated. Empty when
|
||||
// the event carries no text (control keys, specials, terminals without
|
||||
// mode 16), so callers fall back to the base key.
|
||||
func kittyKeyText(params string) string {
|
||||
fields := strings.Split(params, ";")
|
||||
if len(fields) < 3 || fields[2] == "" {
|
||||
return ""
|
||||
}
|
||||
var b strings.Builder
|
||||
// Reject C0 control chars, DEL, and C1 control chars: they must never
|
||||
// surface as key text.
|
||||
for cp := range strings.SplitSeq(fields[2], ":") {
|
||||
if n, err := strconv.ParseInt(cp, 10, 32); err == nil && n >= 0x20 && (n < 0x7f || n > 0x9f) && utf8.ValidRune(rune(n)) {
|
||||
b.WriteRune(rune(n))
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (ip *inputParser) handleMouse(mode rune, params []int) {
|
||||
|
||||
// XTerm mouse events only report at most one button at a time,
|
||||
@@ -1421,6 +1433,12 @@ func (ip *inputParser) handleCsi(mode rune, params []byte, intermediate []byte)
|
||||
if mod1 := kittyModifierKey(P0); mod1 != ModNone {
|
||||
mod |= mod1
|
||||
}
|
||||
// kitty mode 16: text is the layout-correct output, sent with its
|
||||
// modifiers - keep both. No text falls through to the base key.
|
||||
if text := kittyKeyText(pstr); text != "" {
|
||||
ip.postKeyEx(KeyRune, text, mod, pressed, physical, repeat)
|
||||
return
|
||||
}
|
||||
if key != KeyRune {
|
||||
ip.postKeyEx(key, "", mod, pressed, physical, repeat)
|
||||
} else if chr != 0 {
|
||||
|
||||
+15
@@ -39,6 +39,14 @@ type Screen interface {
|
||||
// is called (or Sync).
|
||||
Fill(rune, Style)
|
||||
|
||||
// FillArea fills a rectangular region of the screen with the given
|
||||
// character and style. The region starts at column x, row y and
|
||||
// extends width columns to the right and height rows down. Any part
|
||||
// of the region outside the screen is ignored, so it's safe to pass
|
||||
// coordinates that overflow the screen. Like Fill, the change is not
|
||||
// visible until Show (or Sync) is called.
|
||||
FillArea(x int, y int, width int, height int, r rune, style Style)
|
||||
|
||||
// Put writes the first grapheme of the given string with th
|
||||
// given style at the given coordinates. (Only the first grapheme
|
||||
// occupying either one or two cells is stored.) It returns the
|
||||
@@ -426,6 +434,13 @@ func (b *baseScreen) Fill(r rune, style Style) {
|
||||
b.Unlock()
|
||||
}
|
||||
|
||||
func (b *baseScreen) FillArea(x, y, width, height int, r rune, style Style) {
|
||||
cb := b.GetCells()
|
||||
b.Lock()
|
||||
cb.FillArea(x, y, width, height, r, style)
|
||||
b.Unlock()
|
||||
}
|
||||
|
||||
func (b *baseScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
|
||||
b.Put(x, y, string(append([]rune{mainc}, combc...)), style)
|
||||
}
|
||||
|
||||
+14
-3
@@ -179,7 +179,7 @@ const (
|
||||
notifyDesktop777 = "\x1b]777;notify;%s;%s\x1b\\" // Most commonly supported
|
||||
queryKittyKbd = "\x1b[?u" // Query for Kitty keyboard support
|
||||
enableKittyKbd = "\x1b[=1u" // Technically this pushes
|
||||
enableKittyKbdAdv = "\x1b[=15u" // disambiguation, events, alternate keys, all keys
|
||||
enableKittyKbdAdv = "\x1b[=31u" // disambiguation, events, alternate keys, all keys, text
|
||||
disableKittyKbd = "\x1b[=0u" // Technically this means pop previous mode
|
||||
queryXTermKbd = "\x1b[?4m" // Query for XTerm modify other keys support
|
||||
enableXTermKbd = "\x1b[>4;2m" // Enable modify other keys protocol
|
||||
@@ -1374,13 +1374,24 @@ func (t *tScreen) inputLoop(stopQ chan struct{}) {
|
||||
|
||||
defer t.wg.Done()
|
||||
for {
|
||||
readDone := make(chan bool)
|
||||
chunk := make([]byte, 128)
|
||||
var n int
|
||||
var e error
|
||||
select {
|
||||
case <-stopQ:
|
||||
return
|
||||
default:
|
||||
go func() {
|
||||
n, e = t.tty.Read(chunk)
|
||||
close(readDone)
|
||||
}()
|
||||
select {
|
||||
case <-stopQ:
|
||||
return
|
||||
case <-readDone:
|
||||
}
|
||||
}
|
||||
chunk := make([]byte, 128)
|
||||
n, e := t.tty.Read(chunk)
|
||||
switch e {
|
||||
case nil:
|
||||
default:
|
||||
|
||||
Vendored
+1
-1
@@ -45,7 +45,7 @@ github.com/fatih/color
|
||||
# github.com/gdamore/encoding v1.0.1
|
||||
## explicit; go 1.9
|
||||
github.com/gdamore/encoding
|
||||
# github.com/gdamore/tcell/v3 v3.4.2
|
||||
# github.com/gdamore/tcell/v3 v3.5.0
|
||||
## explicit; go 1.25.0
|
||||
github.com/gdamore/tcell/v3
|
||||
github.com/gdamore/tcell/v3/color
|
||||
|
||||
Reference in New Issue
Block a user