mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-27 12:32:37 +02:00
Merge branch 'master' into feature/help
conflicts resolved
This commit is contained in:
commit
6f7de83bce
@ -2,14 +2,23 @@ version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.10
|
||||
- image: circleci/golang:1.11
|
||||
|
||||
working_directory: /go/src/github.com/jesseduffield/lazygit
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- pkg-cache-{{ checksum "Gopkg.lock" }}
|
||||
- run:
|
||||
name: Ensure go.mod file is up to date
|
||||
command: |
|
||||
export GO111MODULE=on
|
||||
mv go.mod /tmp/
|
||||
go mod init
|
||||
export GO111MODULE=auto
|
||||
|
||||
if [ $(diff /tmp/go.mod go.mod|wc -l) -gt 0 ]; then
|
||||
diff /tmp/go.mod go.mod
|
||||
exit 1;
|
||||
fi
|
||||
- run:
|
||||
name: Run gofmt -s
|
||||
command: |
|
||||
@ -17,6 +26,9 @@ jobs:
|
||||
find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;
|
||||
exit 1;
|
||||
fi
|
||||
- restore_cache:
|
||||
keys:
|
||||
- pkg-cache-{{ checksum "Gopkg.lock" }}-v2
|
||||
- run:
|
||||
name: Run tests
|
||||
command: |
|
||||
@ -31,9 +43,9 @@ jobs:
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- save_cache:
|
||||
key: pkg-cache-{{ checksum "Gopkg.lock" }}
|
||||
key: pkg-cache-{{ checksum "Gopkg.lock" }}-v2
|
||||
paths:
|
||||
- "/go/pkg"
|
||||
- ~/.cache/go-build
|
||||
|
||||
release:
|
||||
docker:
|
||||
|
@ -15,9 +15,10 @@ welcome your pull requests:
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've added code that need documentation, update the documentation.
|
||||
4. Be sure to test your modifications.
|
||||
5. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
6. Issue that pull request!
|
||||
4. Make sure your code follows the [effective go](https://golang.org/doc/effective_go.html) guidelines as much as possible.
|
||||
5. Be sure to test your modifications.
|
||||
6. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
7. Issue that pull request!
|
||||
|
||||
## Code of conduct
|
||||
Please note by participating in this project, you agree to abide by the [code of conduct].
|
||||
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
# run with:
|
||||
# docker build -t lazygit .
|
||||
# docker run -it lazygit:latest
|
||||
|
||||
FROM golang:alpine
|
||||
|
||||
RUN apk add -U git xdg-utils
|
||||
|
||||
ADD . /go/src/github.com/jesseduffield/lazygit
|
||||
|
||||
RUN go install github.com/jesseduffield/lazygit
|
||||
|
||||
WORKDIR /go/src/github.com/jesseduffield/lazygit
|
5
Gopkg.lock
generated
5
Gopkg.lock
generated
@ -189,11 +189,11 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:acbcdae312c37a8019e0f573a9be26499058d5e1244243655373d2fd97714658"
|
||||
digest = "1:71e6c15797951d3fabaa944d70253e36a6cee96bf54ca0bc43ca3de3b4814bbb"
|
||||
name = "github.com/jesseduffield/gocui"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "5d9e836837237cb3aadca51ecb37c7cf3345bfa4"
|
||||
revision = "2cb6e95bbbf850bb32cc1799e07d08ff0f144746"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc"
|
||||
@ -611,7 +611,6 @@
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/cloudfoundry/jibber_jabber",
|
||||
"github.com/davecgh/go-spew/spew",
|
||||
"github.com/fatih/color",
|
||||
"github.com/golang-collections/collections/stack",
|
||||
"github.com/heroku/rollrus",
|
||||
|
@ -131,3 +131,7 @@ feel free to [raise an issue](https://github.com/jesseduffield/lazygit/issues)/[
|
||||
If you want to see what I (Jesse) am up to in terms of development, follow me on
|
||||
[twitter](https://twitter.com/DuffieldJesse) or watch me program on
|
||||
[twitch](https://www.twitch.tv/jesseduffield).
|
||||
|
||||
## Alternatives
|
||||
If you find that lazygit doesn't quite satisfy your requirements, these may be a better fit:
|
||||
- [tig](https://github.com/jonas/tig)
|
||||
|
@ -14,10 +14,13 @@
|
||||
- white
|
||||
optionsTextColor:
|
||||
- blue
|
||||
commitLength:
|
||||
show: true
|
||||
update:
|
||||
method: prompt # can be: prompt | background | never
|
||||
days: 14 # how often an update is checked for
|
||||
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
|
||||
confirmOnQuit: false
|
||||
```
|
||||
|
||||
## Platform Defaults:
|
||||
@ -33,7 +36,7 @@
|
||||
|
||||
```
|
||||
os:
|
||||
openCommand: 'bash -c \"xdg-open {{filename}} &>/dev/null &\"'
|
||||
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
|
||||
```
|
||||
|
||||
### OSX:
|
||||
|
62
go.mod
Normal file
62
go.mod
Normal file
@ -0,0 +1,62 @@
|
||||
module github.com/jesseduffield/lazygit
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go v1.15.21
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/davecgh/go-spew v1.1.0
|
||||
github.com/emirpasic/gods v1.9.0
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/go-ini/ini v1.38.2
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186
|
||||
github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001
|
||||
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc
|
||||
github.com/hashicorp/go-version v1.0.0
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce
|
||||
github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
||||
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63
|
||||
github.com/jesseduffield/gocui v0.0.0-20180905104005-2cb6e95bbbf8
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
|
||||
github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55
|
||||
github.com/magiconair/properties v1.8.0
|
||||
github.com/mattn/go-colorable v0.0.9
|
||||
github.com/mattn/go-isatty v0.0.3
|
||||
github.com/mattn/go-runewidth v0.0.2
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699
|
||||
github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80
|
||||
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb
|
||||
github.com/pelletier/go-buffruneio v0.2.0
|
||||
github.com/pelletier/go-toml v1.2.0
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/sergi/go-diff v1.0.0
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0
|
||||
github.com/sirupsen/logrus v1.0.6
|
||||
github.com/spf13/afero v1.1.1
|
||||
github.com/spf13/cast v1.2.0
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834
|
||||
github.com/spf13/pflag v1.0.2
|
||||
github.com/spf13/viper v1.1.0
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
github.com/src-d/gcfg v1.3.0
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/stvp/roll v0.0.0-20170522205222-3627a5cbeaea
|
||||
github.com/tcnksm/go-gitconfig v0.1.2
|
||||
github.com/ulikunitz/xz v0.5.4
|
||||
github.com/xanzy/ssh-agent v0.2.0
|
||||
golang.org/x/crypto v0.0.0-20180808211826-de0752318171
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332
|
||||
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0
|
||||
golang.org/x/text v0.3.0
|
||||
gopkg.in/src-d/go-billy.v4 v4.2.0
|
||||
gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714
|
||||
gopkg.in/warnings.v0 v0.1.2
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
)
|
4
main.go
4
main.go
@ -43,11 +43,11 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
app, err := app.NewApp(appConfig)
|
||||
app, err := app.Setup(appConfig)
|
||||
if err != nil {
|
||||
app.Log.Error(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
app.GitCommand.SetupGit()
|
||||
|
||||
app.Gui.RunWithSubprocesses()
|
||||
}
|
||||
|
@ -65,8 +65,8 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
|
||||
})
|
||||
}
|
||||
|
||||
// NewApp retruns a new applications
|
||||
func NewApp(config config.AppConfigurer) (*App, error) {
|
||||
// Setup bootstrap a new application
|
||||
func Setup(config config.AppConfigurer) (*App, error) {
|
||||
app := &App{
|
||||
closers: []io.Closer{},
|
||||
Config: config,
|
||||
|
@ -15,6 +15,48 @@ import (
|
||||
gogit "gopkg.in/src-d/go-git.v4"
|
||||
)
|
||||
|
||||
func verifyInGitRepo(runCmd func(string) error) error {
|
||||
return runCmd("git status")
|
||||
}
|
||||
|
||||
func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
|
||||
for {
|
||||
f, err := stat(".git")
|
||||
|
||||
if err == nil && f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = chdir(".."); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupRepositoryAndWorktree(openGitRepository func(string) (*gogit.Repository, error), sLocalize func(string) string) (repository *gogit.Repository, worktree *gogit.Worktree, err error) {
|
||||
repository, err = openGitRepository(".")
|
||||
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
|
||||
return nil, nil, errors.New(sLocalize("GitconfigParseErr"))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
worktree, err = repository.Worktree()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GitCommand is our main git interface
|
||||
type GitCommand struct {
|
||||
Log *logrus.Entry
|
||||
@ -26,22 +68,36 @@ type GitCommand struct {
|
||||
|
||||
// NewGitCommand it runs git commands
|
||||
func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) (*GitCommand, error) {
|
||||
gitCommand := &GitCommand{
|
||||
var worktree *gogit.Worktree
|
||||
var repo *gogit.Repository
|
||||
|
||||
fs := []func() error{
|
||||
func() error {
|
||||
return verifyInGitRepo(osCommand.RunCommand)
|
||||
},
|
||||
func() error {
|
||||
return navigateToRepoRootDirectory(os.Stat, os.Chdir)
|
||||
},
|
||||
func() error {
|
||||
var err error
|
||||
repo, worktree, err = setupRepositoryAndWorktree(gogit.PlainOpen, tr.SLocalize)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
for _, f := range fs {
|
||||
if err := f(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &GitCommand{
|
||||
Log: log,
|
||||
OSCommand: osCommand,
|
||||
Tr: tr,
|
||||
}
|
||||
return gitCommand, nil
|
||||
}
|
||||
|
||||
// SetupGit sets git repo up
|
||||
func (c *GitCommand) SetupGit() {
|
||||
c.verifyInGitRepo()
|
||||
c.navigateToRepoRootDirectory()
|
||||
if err := c.setupWorktree(); err != nil {
|
||||
c.Log.Error(err)
|
||||
panic(err)
|
||||
}
|
||||
Worktree: worktree,
|
||||
Repo: repo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetStashEntries stash entryies
|
||||
@ -145,46 +201,11 @@ func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File {
|
||||
return append(headResults, tailResults...)
|
||||
}
|
||||
|
||||
func (c *GitCommand) verifyInGitRepo() {
|
||||
if output, err := c.OSCommand.RunCommandWithOutput("git status"); err != nil {
|
||||
fmt.Println(output)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBranchName branch name
|
||||
func (c *GitCommand) GetBranchName() (string, error) {
|
||||
return c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
|
||||
}
|
||||
|
||||
func (c *GitCommand) navigateToRepoRootDirectory() {
|
||||
_, err := os.Stat(".git")
|
||||
for os.IsNotExist(err) {
|
||||
c.Log.Debug("going up a directory to find the root")
|
||||
os.Chdir("..")
|
||||
_, err = os.Stat(".git")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GitCommand) setupWorktree() error {
|
||||
r, err := gogit.PlainOpen(".")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
|
||||
errorMessage := c.Tr.SLocalize("GitconfigParseErr")
|
||||
return errors.New(errorMessage)
|
||||
}
|
||||
return err
|
||||
}
|
||||
c.Repo = r
|
||||
|
||||
w, err := r.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Worktree = w
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetHard does the equivalent of `git reset --hard HEAD`
|
||||
func (c *GitCommand) ResetHard() error {
|
||||
return c.Worktree.Reset(&gogit.ResetOptions{Mode: gogit.HardReset})
|
||||
@ -434,15 +455,6 @@ func (c *GitCommand) GetBranchGraph(branchName string) (string, error) {
|
||||
return c.OSCommand.RunCommandWithOutput("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branchName)
|
||||
}
|
||||
|
||||
// Map (from https://gobyexample.com/collection-functions)
|
||||
func Map(vs []string, f func(string) string) []string {
|
||||
vsm := make([]string, len(vs))
|
||||
for i, v := range vs {
|
||||
vsm[i] = f(v)
|
||||
}
|
||||
return vsm
|
||||
}
|
||||
|
||||
func includesString(list []string, a string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
|
@ -1,15 +1,53 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/test"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
gogit "gopkg.in/src-d/go-git.v4"
|
||||
)
|
||||
|
||||
type fileInfoMock struct {
|
||||
name string
|
||||
size int64
|
||||
fileMode os.FileMode
|
||||
fileModTime time.Time
|
||||
isDir bool
|
||||
sys interface{}
|
||||
}
|
||||
|
||||
func (f fileInfoMock) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f fileInfoMock) Size() int64 {
|
||||
return f.size
|
||||
}
|
||||
|
||||
func (f fileInfoMock) Mode() os.FileMode {
|
||||
return f.fileMode
|
||||
}
|
||||
|
||||
func (f fileInfoMock) ModTime() time.Time {
|
||||
return f.fileModTime
|
||||
}
|
||||
|
||||
func (f fileInfoMock) IsDir() bool {
|
||||
return f.isDir
|
||||
}
|
||||
|
||||
func (f fileInfoMock) Sys() interface{} {
|
||||
return f.sys
|
||||
}
|
||||
|
||||
func newDummyLog() *logrus.Entry {
|
||||
log := logrus.New()
|
||||
log.Out = ioutil.Discard
|
||||
@ -20,6 +58,201 @@ func newDummyGitCommand() *GitCommand {
|
||||
return &GitCommand{
|
||||
Log: newDummyLog(),
|
||||
OSCommand: newDummyOSCommand(),
|
||||
Tr: i18n.NewLocalizer(newDummyLog()),
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyInGitRepo(t *testing.T) {
|
||||
type scenario struct {
|
||||
runCmd func(string) error
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
func(string) error {
|
||||
return nil
|
||||
},
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
func(string) error {
|
||||
return fmt.Errorf("fatal: Not a git repository (or any of the parent directories): .git")
|
||||
},
|
||||
func(err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s.test(verifyInGitRepo(s.runCmd))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNavigateToRepoRootDirectory(t *testing.T) {
|
||||
type scenario struct {
|
||||
stat func(string) (os.FileInfo, error)
|
||||
chdir func(string) error
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
func(string) (os.FileInfo, error) {
|
||||
return fileInfoMock{isDir: true}, nil
|
||||
},
|
||||
func(string) error {
|
||||
return nil
|
||||
},
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
func(string) (os.FileInfo, error) {
|
||||
return nil, fmt.Errorf("An error occurred")
|
||||
},
|
||||
func(string) error {
|
||||
return nil
|
||||
},
|
||||
func(err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "An error occurred")
|
||||
},
|
||||
},
|
||||
{
|
||||
func(string) (os.FileInfo, error) {
|
||||
return nil, os.ErrNotExist
|
||||
},
|
||||
func(string) error {
|
||||
return fmt.Errorf("An error occurred")
|
||||
},
|
||||
func(err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "An error occurred")
|
||||
},
|
||||
},
|
||||
{
|
||||
func(string) (os.FileInfo, error) {
|
||||
return nil, os.ErrNotExist
|
||||
},
|
||||
func(string) error {
|
||||
return fmt.Errorf("An error occurred")
|
||||
},
|
||||
func(err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "An error occurred")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s.test(navigateToRepoRootDirectory(s.stat, s.chdir))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupRepositoryAndWorktree(t *testing.T) {
|
||||
type scenario struct {
|
||||
openGitRepository func(string) (*gogit.Repository, error)
|
||||
sLocalize func(string) string
|
||||
test func(*gogit.Repository, *gogit.Worktree, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
func(string) (*gogit.Repository, error) {
|
||||
return nil, fmt.Errorf(`unquoted '\' must be followed by new line`)
|
||||
},
|
||||
func(string) string {
|
||||
return "error translated"
|
||||
},
|
||||
func(r *gogit.Repository, w *gogit.Worktree, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "error translated")
|
||||
},
|
||||
},
|
||||
{
|
||||
func(string) (*gogit.Repository, error) {
|
||||
return nil, fmt.Errorf("Error from inside gogit")
|
||||
},
|
||||
func(string) string { return "" },
|
||||
func(r *gogit.Repository, w *gogit.Worktree, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "Error from inside gogit")
|
||||
},
|
||||
},
|
||||
{
|
||||
func(string) (*gogit.Repository, error) {
|
||||
return &gogit.Repository{}, nil
|
||||
},
|
||||
func(string) string { return "" },
|
||||
func(r *gogit.Repository, w *gogit.Worktree, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, gogit.ErrIsBareRepository, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
func(string) (*gogit.Repository, error) {
|
||||
assert.NoError(t, os.RemoveAll("/tmp/lazygit-test"))
|
||||
r, err := gogit.PlainInit("/tmp/lazygit-test", false)
|
||||
assert.NoError(t, err)
|
||||
return r, nil
|
||||
},
|
||||
func(string) string { return "" },
|
||||
func(r *gogit.Repository, w *gogit.Worktree, err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s.test(setupRepositoryAndWorktree(s.openGitRepository, s.sLocalize))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGitCommand(t *testing.T) {
|
||||
actual, err := os.Getwd()
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
assert.NoError(t, os.Chdir(actual))
|
||||
}()
|
||||
|
||||
type scenario struct {
|
||||
setup func()
|
||||
test func(*GitCommand, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
func() {
|
||||
assert.NoError(t, os.Chdir("/tmp"))
|
||||
},
|
||||
func(gitCmd *GitCommand, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error())
|
||||
},
|
||||
},
|
||||
{
|
||||
func() {
|
||||
assert.NoError(t, os.RemoveAll("/tmp/lazygit-test"))
|
||||
_, err := gogit.PlainInit("/tmp/lazygit-test", false)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.Chdir("/tmp/lazygit-test"))
|
||||
},
|
||||
func(gitCmd *GitCommand, err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s.setup()
|
||||
s.test(NewGitCommand(newDummyLog(), newDummyOSCommand(), i18n.NewLocalizer(newDummyLog())))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ func TestOSCommandRunCommandWithOutput(t *testing.T) {
|
||||
{
|
||||
"rmdir unexisting-folder",
|
||||
func(output string, err error) {
|
||||
assert.Regexp(t, "rmdir: .* 'unexisting-folder': .*", err.Error())
|
||||
assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error())
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -66,7 +66,7 @@ func TestOSCommandRunCommand(t *testing.T) {
|
||||
{
|
||||
"rmdir unexisting-folder",
|
||||
func(err error) {
|
||||
assert.Regexp(t, "rmdir: .* 'unexisting-folder': .*", err.Error())
|
||||
assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -222,10 +222,13 @@ func GetDefaultConfig() []byte {
|
||||
- white
|
||||
optionsTextColor:
|
||||
- blue
|
||||
commitLength:
|
||||
show: true
|
||||
update:
|
||||
method: prompt # can be: prompt | background | never
|
||||
days: 14 # how often a update is checked for
|
||||
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
|
||||
confirmOnQuit: false
|
||||
`)
|
||||
}
|
||||
|
||||
|
@ -4,5 +4,5 @@ package config
|
||||
func GetPlatformDefaultConfig() []byte {
|
||||
return []byte(
|
||||
`os:
|
||||
openCommand: 'bash -c \"xdg-open {{filename}} &>/dev/null &\"'`)
|
||||
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'`)
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
@ -33,20 +36,6 @@ func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.switchFocus(g, v, gui.getFilesView(g))
|
||||
}
|
||||
|
||||
func (gui *Gui) handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error {
|
||||
// resising ahead of time so that the top line doesn't get hidden to make
|
||||
// room for the cursor on the second line
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Buffer())
|
||||
if _, err := g.SetView("commitMessage", x0, y0, x1, y1+1, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v.EditNewLine()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"CloseConfirm",
|
||||
@ -57,3 +46,42 @@ func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
)
|
||||
return gui.renderString(g, "options", message)
|
||||
}
|
||||
|
||||
func (gui *Gui) simpleEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
|
||||
switch {
|
||||
case key == gocui.KeyBackspace || key == gocui.KeyBackspace2:
|
||||
v.EditDelete(true)
|
||||
case key == gocui.KeyDelete:
|
||||
v.EditDelete(false)
|
||||
case key == gocui.KeyArrowDown:
|
||||
v.MoveCursor(0, 1, false)
|
||||
case key == gocui.KeyArrowUp:
|
||||
v.MoveCursor(0, -1, false)
|
||||
case key == gocui.KeyArrowLeft:
|
||||
v.MoveCursor(-1, 0, false)
|
||||
case key == gocui.KeyArrowRight:
|
||||
v.MoveCursor(1, 0, false)
|
||||
case key == gocui.KeyTab:
|
||||
v.EditNewLine()
|
||||
case key == gocui.KeySpace:
|
||||
v.EditWrite(' ')
|
||||
case key == gocui.KeyInsert:
|
||||
v.Overwrite = !v.Overwrite
|
||||
default:
|
||||
v.EditWrite(ch)
|
||||
}
|
||||
|
||||
gui.RenderCommitLength()
|
||||
}
|
||||
|
||||
func (gui *Gui) getBufferLength(view *gocui.View) string {
|
||||
return " " + strconv.Itoa(strings.Count(view.Buffer(), "")-1) + " "
|
||||
}
|
||||
|
||||
func (gui *Gui) RenderCommitLength() {
|
||||
if !gui.Config.GetUserConfig().GetBool("gui.commitLength.show") {
|
||||
return
|
||||
}
|
||||
v := gui.getCommitMessageView(gui.g)
|
||||
v.Subtitle = gui.getBufferLength(v)
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error) func(*gocui.Gui, *gocui.View) error {
|
||||
@ -116,20 +115,6 @@ func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, t
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleNewline(g *gocui.Gui, v *gocui.View) error {
|
||||
// resising ahead of time so that the top line doesn't get hidden to make
|
||||
// room for the cursor on the second line
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Buffer())
|
||||
if _, err := g.SetView("confirmation", x0, y0, x1, y1+1, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v.EditNewLine()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
actions := gui.Tr.TemplateLocalize(
|
||||
"CloseConfirm",
|
||||
@ -144,9 +129,6 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go
|
||||
if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("confirmation", gocui.KeyTab, gocui.ModNone, gui.handleNewline); err != nil {
|
||||
return err
|
||||
}
|
||||
return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose))
|
||||
}
|
||||
|
||||
@ -161,17 +143,3 @@ func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error {
|
||||
coloredMessage := colorFunction(strings.TrimSpace(message))
|
||||
return gui.createConfirmationPanel(g, currentView, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
// If the confirmation panel is already displayed, just resize the width,
|
||||
// otherwise continue
|
||||
content := utils.TrimTrailingNewline(v.Buffer())
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, content)
|
||||
vx0, vy0, vx1, vy1 := v.Dimensions()
|
||||
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
|
||||
return nil
|
||||
}
|
||||
gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel"))
|
||||
_, err := g.SetView(v.Name(), x0, y0, x1, y1, 0)
|
||||
return err
|
||||
}
|
||||
|
@ -202,6 +202,7 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
g.SetViewOnTop("commitMessage")
|
||||
gui.switchFocus(g, filesView, commitMessageView)
|
||||
gui.RenderCommitLength()
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
|
@ -263,6 +263,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
commitMessageView.Title = gui.Tr.SLocalize("CommitMessage")
|
||||
commitMessageView.FgColor = gocui.ColorWhite
|
||||
commitMessageView.Editable = true
|
||||
commitMessageView.Editor = gocui.EditorFunc(gui.simpleEditor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,9 +309,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
gui.resizePopupPanels(g)
|
||||
|
||||
return nil
|
||||
return gui.resizeCurrentPopupPanel(g)
|
||||
}
|
||||
|
||||
func (gui *Gui) promptAnonymousReporting() error {
|
||||
@ -365,14 +364,6 @@ func (gui *Gui) goEvery(g *gocui.Gui, interval time.Duration, function func(*goc
|
||||
}()
|
||||
}
|
||||
|
||||
func (gui *Gui) resizePopupPanels(g *gocui.Gui) error {
|
||||
v := g.CurrentView()
|
||||
if v.Name() == "commitMessage" || v.Name() == "confirmation" {
|
||||
return gui.resizePopupPanel(g, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run setup the gui with keybindings and start the mainloop
|
||||
func (gui *Gui) Run() error {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
|
||||
@ -430,5 +421,10 @@ func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.State.Updating {
|
||||
return gui.createUpdateQuitConfirmation(g, v)
|
||||
}
|
||||
if gui.Config.GetUserConfig().GetBool("confirmOnQuit") {
|
||||
return gui.createConfirmationPanel(g, v, "", gui.Tr.SLocalize("ConfirmQuit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}, nil)
|
||||
}
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
@ -339,11 +339,6 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
Key: gocui.KeyEsc,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitClose,
|
||||
}, {
|
||||
ViewName: "commitMessage",
|
||||
Key: gocui.KeyTab,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleNewlineCommitMessage,
|
||||
}, {
|
||||
ViewName: "menu",
|
||||
Key: gocui.KeyEsc,
|
||||
|
@ -277,3 +277,25 @@ func (gui *Gui) currentViewName(g *gocui.Gui) string {
|
||||
currentView := g.CurrentView()
|
||||
return currentView.Name()
|
||||
}
|
||||
|
||||
func (gui *Gui) resizeCurrentPopupPanel(g *gocui.Gui) error {
|
||||
v := g.CurrentView()
|
||||
if v.Name() == "commitMessage" || v.Name() == "confirmation" {
|
||||
return gui.resizePopupPanel(g, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
// If the confirmation panel is already displayed, just resize the width,
|
||||
// otherwise continue
|
||||
content := v.Buffer()
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, content)
|
||||
vx0, vy0, vx1, vy1 := v.Dimensions()
|
||||
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
|
||||
return nil
|
||||
}
|
||||
gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel"))
|
||||
_, err := g.SetView(v.Name(), x0, y0, x1, y1, 0)
|
||||
return err
|
||||
}
|
||||
|
@ -367,6 +367,9 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "mergeIntoCurrentBranch",
|
||||
Other: `merge into currently checked out branch`,
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmQuit",
|
||||
Other: `Are you sure you want to quit?`,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -387,6 +387,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "mergeIntoCurrentBranch",
|
||||
Other: `merge into currently checked out branch`,
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmQuit",
|
||||
Other: `Are you sure you want to quit?`,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -365,6 +365,9 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "mergeIntoCurrentBranch",
|
||||
Other: `scal do obecnej gałęzi`,
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmQuit",
|
||||
Other: `Na pewno chcesz wyjść z programu?`,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
2
vendor/github.com/jesseduffield/gocui/edit.go
generated
vendored
2
vendor/github.com/jesseduffield/gocui/edit.go
generated
vendored
@ -114,8 +114,8 @@ func (v *View) EditDelete(back bool) {
|
||||
func (v *View) EditNewLine() {
|
||||
v.breakLine(v.cx, v.cy)
|
||||
v.ox = 0
|
||||
v.cy = v.cy + 1
|
||||
v.cx = 0
|
||||
v.MoveCursor(0, 1, true)
|
||||
}
|
||||
|
||||
// MoveCursor moves the cursor taking into account the width of the line/view,
|
||||
|
24
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
24
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
@ -476,6 +476,11 @@ func (g *Gui) flush() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if v.Subtitle != "" {
|
||||
if err := g.drawSubtitle(v, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := g.draw(v); err != nil {
|
||||
return err
|
||||
@ -582,6 +587,25 @@ func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawSubtitle draws the subtitle of the view.
|
||||
func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error {
|
||||
if v.y0 < 0 || v.y0 >= g.maxY {
|
||||
return nil
|
||||
}
|
||||
|
||||
start := v.x1 - 5 - len(v.Subtitle)
|
||||
for i, ch := range v.Subtitle {
|
||||
x := start + i
|
||||
if x >= v.x1 {
|
||||
break
|
||||
}
|
||||
if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// draw manages the cursor and calls the draw function of a view.
|
||||
func (g *Gui) draw(v *View) error {
|
||||
if g.Cursor {
|
||||
|
3
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
3
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
@ -77,6 +77,9 @@ type View struct {
|
||||
// If Frame is true, Title allows to configure a title for the view.
|
||||
Title string
|
||||
|
||||
// If Frame is true, Subtitle allows to configure a subtitle for the view.
|
||||
Subtitle string
|
||||
|
||||
// If Mask is true, the View will display the mask instead of the real
|
||||
// content
|
||||
Mask rune
|
||||
|
Loading…
x
Reference in New Issue
Block a user