mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-29 02:35:39 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
307d051ec2 | ||
|
|
3a668011fa | ||
|
|
14c8b80494 | ||
|
|
10dde518bc | ||
|
|
1e1c90c92e | ||
|
|
4954791443 | ||
|
|
c471f4927a | ||
|
|
9eba98302e | ||
|
|
250fe740b2 | ||
|
|
70eda031dc | ||
|
|
86f296a898 | ||
|
|
71ff18318d | ||
|
|
46cce28758 | ||
|
|
5611d9a3ef | ||
|
|
40bec49de8 | ||
|
|
f99d5f74d4 | ||
|
|
30a066aa41 | ||
|
|
1dcc3363d0 | ||
|
|
c6948582e6 | ||
|
|
196c83d058 |
@@ -49,6 +49,7 @@ Default path for the config file:
|
||||
autoFetch: true
|
||||
branchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --"
|
||||
overrideGpg: false # prevents lazygit from spawning a separate process when using GPG
|
||||
disableForcePushing: false
|
||||
update:
|
||||
method: prompt # can be: prompt | background | never
|
||||
days: 14 # how often an update is checked for
|
||||
|
||||
@@ -39,7 +39,7 @@ func main() {
|
||||
flaggy.Bool(&debuggingFlag, "d", "debug", "Run in debug mode with logging")
|
||||
|
||||
configFlag := false
|
||||
flaggy.Bool(&configFlag, "c", "config", "Print the current default config")
|
||||
flaggy.Bool(&configFlag, "c", "config", "Print the default config")
|
||||
|
||||
flaggy.Parse()
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@ package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
@@ -129,7 +132,47 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func (app *App) validateGitVersion() error {
|
||||
output, err := app.OSCommand.RunCommandWithOutput("git --version")
|
||||
// if we get an error anywhere here we'll show the same status
|
||||
minVersionError := errors.New(app.Tr.SLocalize("minGitVersionError"))
|
||||
if err != nil {
|
||||
return minVersionError
|
||||
}
|
||||
|
||||
if isGitVersionValid(output) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return minVersionError
|
||||
}
|
||||
|
||||
func isGitVersionValid(versionStr string) bool {
|
||||
// output should be something like: 'git version 2.23.0 (blah)'
|
||||
re := regexp.MustCompile(`[^\d]+([\d\.]+)`)
|
||||
matches := re.FindStringSubmatch(versionStr)
|
||||
|
||||
if len(matches) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
gitVersion := matches[1]
|
||||
majorVersion, err := strconv.Atoi(gitVersion[0:1])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if majorVersion < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (app *App) setupRepo() (bool, error) {
|
||||
if err := app.validateGitVersion(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// if we are not in a git repo, we ask if we want to `git init`
|
||||
if err := app.OSCommand.RunCommand("git status"); err != nil {
|
||||
cwd, err := os.Getwd()
|
||||
@@ -216,6 +259,14 @@ func (app *App) Close() error {
|
||||
func (app *App) KnownError(err error) (string, bool) {
|
||||
errorMessage := err.Error()
|
||||
|
||||
knownErrorMessages := []string{app.Tr.SLocalize("minGitVersionError")}
|
||||
|
||||
for _, message := range knownErrorMessages {
|
||||
if errorMessage == message {
|
||||
return message, true
|
||||
}
|
||||
}
|
||||
|
||||
mappings := []errorMapping{
|
||||
{
|
||||
originalError: "fatal: not a git repository",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsGitVersionValid(t *testing.T) {
|
||||
type scenario struct {
|
||||
versionStr string
|
||||
expectedResult bool
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"git version 1.9.0",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"git version 1.9.0 (Apple Git-128)",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"git version 2.4.0",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"git version 2.24.3 (Apple Git-128)",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.versionStr, func(t *testing.T) {
|
||||
result := isGitVersionValid(s.versionStr)
|
||||
assert.Equal(t, result, s.expectedResult)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,9 @@ type Commit struct {
|
||||
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
|
||||
Author string
|
||||
UnixTimestamp int64
|
||||
|
||||
// IsMerge tells us whether we're dealing with a merge commit i.e. a commit with two parents
|
||||
IsMerge bool
|
||||
}
|
||||
|
||||
func (c *Commit) ShortSha() string {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -29,21 +28,19 @@ const SEPARATION_CHAR = "|"
|
||||
|
||||
// CommitListBuilder returns a list of Branch objects for the current repo
|
||||
type CommitListBuilder struct {
|
||||
Log *logrus.Entry
|
||||
GitCommand *GitCommand
|
||||
OSCommand *OSCommand
|
||||
Tr *i18n.Localizer
|
||||
CherryPickedCommits []*Commit
|
||||
Log *logrus.Entry
|
||||
GitCommand *GitCommand
|
||||
OSCommand *OSCommand
|
||||
Tr *i18n.Localizer
|
||||
}
|
||||
|
||||
// NewCommitListBuilder builds a new commit list builder
|
||||
func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *OSCommand, tr *i18n.Localizer, cherryPickedCommits []*Commit) *CommitListBuilder {
|
||||
func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *OSCommand, tr *i18n.Localizer) *CommitListBuilder {
|
||||
return &CommitListBuilder{
|
||||
Log: log,
|
||||
GitCommand: gitCommand,
|
||||
OSCommand: osCommand,
|
||||
Tr: tr,
|
||||
CherryPickedCommits: cherryPickedCommits,
|
||||
Log: log,
|
||||
GitCommand: gitCommand,
|
||||
OSCommand: osCommand,
|
||||
Tr: tr,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +55,9 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
|
||||
unixTimestamp := split[1]
|
||||
author := split[2]
|
||||
extraInfo := strings.TrimSpace(split[3])
|
||||
message := strings.Join(split[4:], SEPARATION_CHAR)
|
||||
parentHashes := split[4]
|
||||
|
||||
message := strings.Join(split[5:], SEPARATION_CHAR)
|
||||
tags := []string{}
|
||||
|
||||
if extraInfo != "" {
|
||||
@@ -71,6 +70,10 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
|
||||
|
||||
unitTimestampInt, _ := strconv.Atoi(unixTimestamp)
|
||||
|
||||
// Any commit with multiple parents is a merge commit.
|
||||
// If there's a space then it means there must be more than one parent hash
|
||||
isMerge := strings.Contains(parentHashes, " ")
|
||||
|
||||
return &Commit{
|
||||
Sha: sha,
|
||||
Name: message,
|
||||
@@ -78,6 +81,7 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
|
||||
ExtraInfo: extraInfo,
|
||||
UnixTimestamp: int64(unitTimestampInt),
|
||||
Author: author,
|
||||
IsMerge: isMerge,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,38 +92,71 @@ type GetCommitsOptions struct {
|
||||
RefName string // e.g. "HEAD" or "my_branch"
|
||||
}
|
||||
|
||||
func (c *CommitListBuilder) MergeRebasingCommits(commits []*Commit) ([]*Commit, error) {
|
||||
// chances are we have as many commits as last time so we'll set the capacity to be the old length
|
||||
result := make([]*Commit, 0, len(commits))
|
||||
for i, commit := range commits {
|
||||
if commit.Status != "rebasing" { // removing the existing rebase commits so we can add the refreshed ones
|
||||
result = append(result, commits[i:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rebaseMode, err := c.GitCommand.RebaseMode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rebaseMode == "" {
|
||||
// not in rebase mode so return original commits
|
||||
return result, nil
|
||||
}
|
||||
|
||||
rebasingCommits, err := c.getRebasingCommits(rebaseMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rebasingCommits) > 0 {
|
||||
result = append(rebasingCommits, result...)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetCommits obtains the commits of the current branch
|
||||
func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*Commit, error) {
|
||||
commits := []*Commit{}
|
||||
var rebasingCommits []*Commit
|
||||
rebaseMode := ""
|
||||
rebaseMode, err := c.GitCommand.RebaseMode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.IncludeRebaseCommits {
|
||||
if opts.IncludeRebaseCommits && opts.FilterPath == "" {
|
||||
var err error
|
||||
rebaseMode, err = c.GitCommand.RebaseMode()
|
||||
rebasingCommits, err = c.MergeRebasingCommits(commits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rebaseMode != "" && opts.FilterPath == "" {
|
||||
// here we want to also prepend the commits that we're in the process of rebasing
|
||||
rebasingCommits, err = c.getRebasingCommits(rebaseMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rebasingCommits) > 0 {
|
||||
commits = append(commits, rebasingCommits...)
|
||||
}
|
||||
}
|
||||
commits = append(commits, rebasingCommits...)
|
||||
}
|
||||
|
||||
passedFirstPushedCommit := false
|
||||
firstPushedCommit, err := c.getFirstPushedCommit(opts.RefName)
|
||||
if err != nil {
|
||||
// must have no upstream branch so we'll consider everything as pushed
|
||||
passedFirstPushedCommit = true
|
||||
}
|
||||
|
||||
unpushedCommits := c.getUnpushedCommits(opts.RefName)
|
||||
cmd := c.getLogCmd(opts)
|
||||
|
||||
err := RunLineOutputCmd(cmd, func(line string) (bool, error) {
|
||||
err = RunLineOutputCmd(cmd, func(line string) (bool, error) {
|
||||
if strings.Split(line, " ")[0] != "gpg:" {
|
||||
commit := c.extractCommitFromLine(line)
|
||||
_, unpushed := unpushedCommits[commit.ShortSha()]
|
||||
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
||||
if commit.Sha == firstPushedCommit {
|
||||
passedFirstPushedCommit = true
|
||||
}
|
||||
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[!passedFirstPushedCommit]
|
||||
commits = append(commits, commit)
|
||||
}
|
||||
return false, nil
|
||||
@@ -135,7 +172,7 @@ func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*Commit, error
|
||||
currentCommit.Name = fmt.Sprintf("%s %s", youAreHere, currentCommit.Name)
|
||||
}
|
||||
|
||||
commits, err = c.setCommitMergedStatuses(commits)
|
||||
commits, err = c.setCommitMergedStatuses(opts.RefName, commits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -253,8 +290,8 @@ func (c *CommitListBuilder) commitFromPatch(content string) (*Commit, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CommitListBuilder) setCommitMergedStatuses(commits []*Commit) ([]*Commit, error) {
|
||||
ancestor, err := c.getMergeBase()
|
||||
func (c *CommitListBuilder) setCommitMergedStatuses(refName string, commits []*Commit) ([]*Commit, error) {
|
||||
ancestor, err := c.getMergeBase(refName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -276,7 +313,7 @@ func (c *CommitListBuilder) setCommitMergedStatuses(commits []*Commit) ([]*Commi
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
func (c *CommitListBuilder) getMergeBase() (string, error) {
|
||||
func (c *CommitListBuilder) getMergeBase(refName string) (string, error) {
|
||||
currentBranch, _, err := c.GitCommand.CurrentBranchName()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -288,23 +325,29 @@ func (c *CommitListBuilder) getMergeBase() (string, error) {
|
||||
}
|
||||
|
||||
// swallowing error because it's not a big deal; probably because there are no commits yet
|
||||
output, _ := c.OSCommand.RunCommandWithOutput("git merge-base HEAD %s", baseBranch)
|
||||
return output, nil
|
||||
output, _ := c.OSCommand.RunCommandWithOutput("git merge-base %s %s", refName, baseBranch)
|
||||
return ignoringWarnings(output), nil
|
||||
}
|
||||
|
||||
// getUnpushedCommits Returns the sha's of the commits that have not yet been pushed
|
||||
// to the remote branch of the current branch, a map is returned to ease look up
|
||||
func (c *CommitListBuilder) getUnpushedCommits(refName string) map[string]bool {
|
||||
pushables := map[string]bool{}
|
||||
o, err := c.OSCommand.RunCommandWithOutput("git rev-list %s@{u}..%s --abbrev-commit --abbrev=8", refName, refName)
|
||||
func ignoringWarnings(commandOutput string) string {
|
||||
trimmedOutput := strings.TrimSpace(commandOutput)
|
||||
split := strings.Split(trimmedOutput, "\n")
|
||||
// need to get last line in case the first line is a warning about how the error is ambiguous.
|
||||
// At some point we should find a way to make it unambiguous
|
||||
lastLine := split[len(split)-1]
|
||||
|
||||
return lastLine
|
||||
}
|
||||
|
||||
// getFirstPushedCommit returns the first commit SHA which has been pushed to the ref's upstream.
|
||||
// all commits above this are deemed unpushed and marked as such.
|
||||
func (c *CommitListBuilder) getFirstPushedCommit(refName string) (string, error) {
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git merge-base %s %s@{u}", refName, refName)
|
||||
if err != nil {
|
||||
return pushables
|
||||
}
|
||||
for _, p := range utils.SplitLines(o) {
|
||||
pushables[p] = true
|
||||
return "", err
|
||||
}
|
||||
|
||||
return pushables
|
||||
return ignoringWarnings(output), nil
|
||||
}
|
||||
|
||||
// getLog gets the git log.
|
||||
@@ -319,5 +362,18 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd {
|
||||
filterFlag = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(opts.FilterPath))
|
||||
}
|
||||
|
||||
return c.OSCommand.ExecutableFromString(fmt.Sprintf("git log %s --oneline --pretty=format:\"%%H%s%%at%s%%aN%s%%d%s%%s\" %s --abbrev=%d --date=unix %s", opts.RefName, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, limitFlag, 20, filterFlag))
|
||||
return c.OSCommand.ExecutableFromString(
|
||||
fmt.Sprintf(
|
||||
"git log %s --oneline --pretty=format:\"%%H%s%%at%s%%aN%s%%d%s%%p%s%%s\" %s --abbrev=%d --date=unix %s",
|
||||
opts.RefName,
|
||||
SEPARATION_CHAR,
|
||||
SEPARATION_CHAR,
|
||||
SEPARATION_CHAR,
|
||||
SEPARATION_CHAR,
|
||||
SEPARATION_CHAR,
|
||||
limitFlag,
|
||||
20,
|
||||
filterFlag,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,50 +13,10 @@ func NewDummyCommitListBuilder() *CommitListBuilder {
|
||||
osCommand := NewDummyOSCommand()
|
||||
|
||||
return &CommitListBuilder{
|
||||
Log: NewDummyLog(),
|
||||
GitCommand: NewDummyGitCommandWithOSCommand(osCommand),
|
||||
OSCommand: osCommand,
|
||||
Tr: i18n.NewLocalizer(NewDummyLog()),
|
||||
CherryPickedCommits: []*Commit{},
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitListBuilderGetUnpushedCommits is a function.
|
||||
func TestCommitListBuilderGetUnpushedCommits(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(map[string]bool)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Can't retrieve pushable commits",
|
||||
func(string, ...string) *exec.Cmd {
|
||||
return exec.Command("test")
|
||||
},
|
||||
func(pushables map[string]bool) {
|
||||
assert.EqualValues(t, map[string]bool{}, pushables)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Retrieve pushable commits",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
return exec.Command("echo", "8a2bb0e\n78976bc")
|
||||
},
|
||||
func(pushables map[string]bool) {
|
||||
assert.Len(t, pushables, 2)
|
||||
assert.EqualValues(t, map[string]bool{"8a2bb0e": true, "78976bc": true}, pushables)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.getUnpushedCommits("HEAD"))
|
||||
})
|
||||
Log: NewDummyLog(),
|
||||
GitCommand: NewDummyGitCommandWithOSCommand(osCommand),
|
||||
OSCommand: osCommand,
|
||||
Tr: i18n.NewLocalizer(NewDummyLog()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +66,7 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
|
||||
},
|
||||
func(output string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "blah\n", output)
|
||||
assert.Equal(t, "blah", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -126,7 +86,7 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
|
||||
},
|
||||
func(output string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "blah\n", output)
|
||||
assert.Equal(t, "blah", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -145,7 +105,7 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.getMergeBase())
|
||||
s.test(c.getMergeBase("HEAD"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+12
-4
@@ -910,7 +910,8 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string
|
||||
debug = "TRUE"
|
||||
}
|
||||
|
||||
splitCmd := str.ToArgv(fmt.Sprintf("git rebase --interactive --autostash --keep-empty --rebase-merges %s", baseSha))
|
||||
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty %s", baseSha)
|
||||
splitCmd := str.ToArgv(cmdStr)
|
||||
|
||||
cmd := c.OSCommand.command(splitCmd[0], splitCmd[1:]...)
|
||||
|
||||
@@ -962,11 +963,18 @@ func (c *GitCommand) GenerateGenericRebaseTodo(commits []*Commit, actionIndex in
|
||||
|
||||
todo := ""
|
||||
for i, commit := range commits[0:baseIndex] {
|
||||
a := "pick"
|
||||
var commitAction string
|
||||
if i == actionIndex {
|
||||
a = action
|
||||
commitAction = action
|
||||
} else if commit.IsMerge {
|
||||
// your typical interactive rebase will actually drop merge commits by default. Damn git CLI, you scary!
|
||||
// doing this means we don't need to worry about rebasing over merges which always causes problems.
|
||||
// you typically shouldn't be doing rebases that pass over merge commits anyway.
|
||||
commitAction = "drop"
|
||||
} else {
|
||||
commitAction = "pick"
|
||||
}
|
||||
todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo
|
||||
todo = commitAction + " " + commit.Sha + " " + commit.Name + "\n" + todo
|
||||
}
|
||||
|
||||
return todo, commits[baseIndex].Sha, nil
|
||||
|
||||
@@ -1639,7 +1639,7 @@ func TestGitCommandRebaseBranch(t *testing.T) {
|
||||
"master",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: "git rebase --interactive --autostash --keep-empty --rebase-merges master",
|
||||
Expect: "git rebase --interactive --autostash --keep-empty master",
|
||||
Replace: "echo",
|
||||
},
|
||||
}),
|
||||
@@ -1652,7 +1652,7 @@ func TestGitCommandRebaseBranch(t *testing.T) {
|
||||
"master",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: "git rebase --interactive --autostash --keep-empty --rebase-merges master",
|
||||
Expect: "git rebase --interactive --autostash --keep-empty master",
|
||||
Replace: "test",
|
||||
},
|
||||
}),
|
||||
@@ -1775,7 +1775,7 @@ func TestGitCommandDiscardOldFileChanges(t *testing.T) {
|
||||
"test999.txt",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: "git rebase --interactive --autostash --keep-empty --rebase-merges abcdef",
|
||||
Expect: "git rebase --interactive --autostash --keep-empty abcdef",
|
||||
Replace: "echo",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -276,6 +276,7 @@ git:
|
||||
autoFetch: true
|
||||
branchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --"
|
||||
overrideGpg: false # prevents lazygit from spawning a separate process when using GPG
|
||||
disableForcePushing: false
|
||||
update:
|
||||
method: prompt # can be: prompt | background | never
|
||||
days: 14 # how often a update is checked for
|
||||
|
||||
@@ -97,7 +97,10 @@ func (gui *Gui) refreshCommits() error {
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshCommitsWithLimit() error {
|
||||
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.Modes.CherryPicking.CherryPickedCommits)
|
||||
gui.State.BranchCommitsMutex.Lock()
|
||||
defer gui.State.BranchCommitsMutex.Unlock()
|
||||
|
||||
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr)
|
||||
|
||||
commits, err := builder.GetCommits(
|
||||
commands.GetCommitsOptions{
|
||||
@@ -115,6 +118,21 @@ func (gui *Gui) refreshCommitsWithLimit() error {
|
||||
return gui.postRefreshUpdate(gui.Contexts.BranchCommits.Context)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshRebaseCommits() error {
|
||||
gui.State.BranchCommitsMutex.Lock()
|
||||
defer gui.State.BranchCommitsMutex.Unlock()
|
||||
|
||||
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr)
|
||||
|
||||
updatedCommits, err := builder.MergeRebasingCommits(gui.State.Commits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.Commits = updatedCommits
|
||||
|
||||
return gui.postRefreshUpdate(gui.Contexts.BranchCommits.Context)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -256,9 +274,8 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
|
||||
if err := gui.GitCommand.EditRebaseTodo(gui.State.Panels.Commits.SelectedLineIdx, action); err != nil {
|
||||
return false, gui.surfaceError(err)
|
||||
}
|
||||
// TODO: consider doing this in a way that is less expensive. We don't actually
|
||||
// need to reload all the commits, just the TODO commits.
|
||||
return true, gui.refreshSidePanels(refreshOptions{scope: []int{COMMITS}})
|
||||
|
||||
return true, gui.refreshRebaseCommits()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -301,7 +318,7 @@ func (gui *Gui) handleCommitMoveDown(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
gui.State.Panels.Commits.SelectedLineIdx++
|
||||
return gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI, scope: []int{COMMITS, BRANCHES}})
|
||||
return gui.refreshRebaseCommits()
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("MovingStatus"), func() error {
|
||||
@@ -328,7 +345,7 @@ func (gui *Gui) handleCommitMoveUp(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.surfaceError(err)
|
||||
}
|
||||
gui.State.Panels.Commits.SelectedLineIdx--
|
||||
return gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI, scope: []int{COMMITS, BRANCHES}})
|
||||
return gui.refreshRebaseCommits()
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("MovingStatus"), func() error {
|
||||
|
||||
+20
-9
@@ -332,15 +332,17 @@ func (gui *Gui) handleAmendCommitPress() error {
|
||||
title: strings.Title(gui.Tr.SLocalize("AmendLastCommit")),
|
||||
prompt: gui.Tr.SLocalize("SureToAmend"),
|
||||
handleConfirm: func() error {
|
||||
ok, err := gui.runSyncOrAsyncCommand(gui.GitCommand.AmendHead())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("AmendingStatus"), func() error {
|
||||
ok, err := gui.runSyncOrAsyncCommand(gui.GitCommand.AmendHead())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
|
||||
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -517,6 +519,11 @@ func (gui *Gui) pushWithForceFlag(v *gocui.View, force bool, upstream string, ar
|
||||
branchName := gui.getCheckedOutBranch().Name
|
||||
err := gui.GitCommand.Push(branchName, force, upstream, args, gui.promptUserForCredential)
|
||||
if err != nil && !force && strings.Contains(err.Error(), "Updates were rejected") {
|
||||
forcePushDisabled := gui.Config.GetUserConfig().GetBool("git.disableForcePushing")
|
||||
if forcePushDisabled {
|
||||
gui.createErrorPanel(gui.Tr.SLocalize("UpdatesRejectedAndForcePushDisabled"))
|
||||
return
|
||||
}
|
||||
gui.ask(askOpts{
|
||||
title: gui.Tr.SLocalize("ForcePush"),
|
||||
prompt: gui.Tr.SLocalize("ForcePushPrompt"),
|
||||
@@ -524,7 +531,6 @@ func (gui *Gui) pushWithForceFlag(v *gocui.View, force bool, upstream string, ar
|
||||
return gui.pushWithForceFlag(v, true, upstream, args)
|
||||
},
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
gui.handleCredentialsPopup(err)
|
||||
@@ -564,6 +570,11 @@ func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.pushWithForceFlag(v, false, "", "")
|
||||
}
|
||||
|
||||
forcePushDisabled := gui.Config.GetUserConfig().GetBool("git.disableForcePushing")
|
||||
if forcePushDisabled {
|
||||
return gui.createErrorPanel(gui.Tr.SLocalize("ForcePushDisabled"))
|
||||
}
|
||||
|
||||
return gui.ask(askOpts{
|
||||
title: gui.Tr.SLocalize("ForcePush"),
|
||||
prompt: gui.Tr.SLocalize("ForcePushPrompt"),
|
||||
|
||||
@@ -147,12 +147,11 @@ func (gui *Gui) handleInfoClick(g *gocui.Gui, v *gocui.View) error {
|
||||
cx, _ := v.Cursor()
|
||||
width, _ := v.Size()
|
||||
|
||||
if width-cx > len(gui.Tr.SLocalize("(reset)")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, mode := range gui.modeStatuses() {
|
||||
if mode.isActive() {
|
||||
if width-cx > len(gui.Tr.SLocalize("(reset)")) {
|
||||
return nil
|
||||
}
|
||||
return mode.reset()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,6 +297,7 @@ type guiState struct {
|
||||
RefreshingFilesMutex sync.Mutex
|
||||
RefreshingStatusMutex sync.Mutex
|
||||
FetchMutex sync.Mutex
|
||||
BranchCommitsMutex sync.Mutex
|
||||
Searching searchingState
|
||||
ScreenMode int
|
||||
SideView *gocui.View
|
||||
|
||||
@@ -32,7 +32,6 @@ func getFullDescriptionDisplayStringsForCommit(c *commands.Commit, cherryPickedC
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
blue := color.New(color.FgBlue)
|
||||
cyan := color.New(color.FgCyan)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
diffedColor := color.New(theme.DiffTerminalColor)
|
||||
|
||||
@@ -66,7 +65,7 @@ func getFullDescriptionDisplayStringsForCommit(c *commands.Commit, cherryPickedC
|
||||
tagString := ""
|
||||
secondColumnString := blue.Sprint(utils.UnixToDate(c.UnixTimestamp))
|
||||
if c.Action != "" {
|
||||
secondColumnString = cyan.Sprint(c.Action)
|
||||
secondColumnString = color.New(actionColorMap(c.Action)).Sprint(c.Action)
|
||||
} else if c.ExtraInfo != "" {
|
||||
tagColor := color.New(color.FgMagenta, color.Bold)
|
||||
tagString = utils.ColoredStringDirect(c.ExtraInfo, tagColor) + " "
|
||||
@@ -82,7 +81,6 @@ func getDisplayStringsForCommit(c *commands.Commit, cherryPickedCommitShaMap map
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
blue := color.New(color.FgBlue)
|
||||
cyan := color.New(color.FgCyan)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
diffedColor := color.New(theme.DiffTerminalColor)
|
||||
|
||||
@@ -116,7 +114,7 @@ func getDisplayStringsForCommit(c *commands.Commit, cherryPickedCommitShaMap map
|
||||
actionString := ""
|
||||
tagString := ""
|
||||
if c.Action != "" {
|
||||
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "
|
||||
actionString = color.New(actionColorMap(c.Action)).Sprint(utils.WithPadding(c.Action, 7)) + " "
|
||||
} else if len(c.Tags) > 0 {
|
||||
tagColor := color.New(color.FgMagenta, color.Bold)
|
||||
tagString = utils.ColoredStringDirect(strings.Join(c.Tags, " "), tagColor) + " "
|
||||
@@ -124,3 +122,18 @@ func getDisplayStringsForCommit(c *commands.Commit, cherryPickedCommitShaMap map
|
||||
|
||||
return []string{shaColor.Sprint(c.ShortSha()), actionString + tagString + defaultColor.Sprint(c.Name)}
|
||||
}
|
||||
|
||||
func actionColorMap(str string) color.Attribute {
|
||||
switch str {
|
||||
case "pick":
|
||||
return color.FgCyan
|
||||
case "drop":
|
||||
return color.FgRed
|
||||
case "edit":
|
||||
return color.FgGreen
|
||||
case "fixup":
|
||||
return color.FgMagenta
|
||||
default:
|
||||
return color.FgYellow
|
||||
}
|
||||
}
|
||||
|
||||
+13
-4
@@ -23,11 +23,16 @@ func (gui *Gui) handleOpenSearch(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
func (gui *Gui) handleSearch(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Searching.searchString = gui.getSearchView().Buffer()
|
||||
if err := gui.switchContextToView(gui.State.Searching.view.Name()); err != nil {
|
||||
if err := gui.returnFromContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.State.Searching.view.Search(gui.State.Searching.searchString); err != nil {
|
||||
view := gui.State.Searching.view
|
||||
if view == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := view.Search(gui.State.Searching.searchString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -86,9 +91,13 @@ func (gui *Gui) onSearchEscape() error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSearchEscape(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.switchContextToView(gui.State.Searching.view.Name()); err != nil {
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.onSearchEscape()
|
||||
if err := gui.returnFromContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func (gui *Gui) handleViewSubCommitFiles() error {
|
||||
|
||||
func (gui *Gui) switchToSubCommitsContext(refName string) error {
|
||||
// need to populate my sub commits
|
||||
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.Modes.CherryPicking.CherryPickedCommits)
|
||||
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr)
|
||||
|
||||
commits, err := builder.GetCommits(
|
||||
commands.GetCommitsOptions{
|
||||
|
||||
+7
-1
@@ -418,6 +418,12 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ForcePushPrompt",
|
||||
Other: "Jouw branch is afgeweken van de remote branch. Druk 'esc' om te annuleren, of 'enter' om geforceert te pushen.",
|
||||
}, &i18n.Message{
|
||||
ID: "ForcePushDisabled",
|
||||
Other: "Your branch has diverged from the remote branch and you've disabled force pushing",
|
||||
}, &i18n.Message{
|
||||
ID: "UpdatesRejectedAndForcePushDisabled",
|
||||
Other: "Updates were rejected and you have disabled force pushing",
|
||||
}, &i18n.Message{
|
||||
ID: "checkForUpdate",
|
||||
Other: "check voor updates",
|
||||
@@ -446,7 +452,7 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
1) Als je meer van lazygit zijn features wilt leren bekijk dan deze video:
|
||||
https://youtu.be/CPLdltN7wgE
|
||||
|
||||
3) Als je git gebruikt, ben je een programmeur! Met jouw hulp kunnen we lazygit verbeteren, dus overweeg om een donateur te worden en mee te doen aan het plezier op
|
||||
2) Als je git gebruikt, ben je een programmeur! Met jouw hulp kunnen we lazygit verbeteren, dus overweeg om een donateur te worden en mee te doen aan het plezier op
|
||||
https://github.com/jesseduffield/lazygit`,
|
||||
}, &i18n.Message{
|
||||
ID: "GitconfigParseErr",
|
||||
|
||||
+10
-1
@@ -426,6 +426,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ForcePushPrompt",
|
||||
Other: "Your branch has diverged from the remote branch. Press 'esc' to cancel, or 'enter' to force push.",
|
||||
}, &i18n.Message{
|
||||
ID: "ForcePushDisabled",
|
||||
Other: "Your branch has diverged from the remote branch and you've disabled force pushing",
|
||||
}, &i18n.Message{
|
||||
ID: "UpdatesRejectedAndForcePushDisabled",
|
||||
Other: "Updates were rejected and you have disabled force pushing",
|
||||
}, &i18n.Message{
|
||||
ID: "checkForUpdate",
|
||||
Other: "check for update",
|
||||
@@ -454,7 +460,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
1) If you want to learn about lazygit's features, watch this vid:
|
||||
https://youtu.be/CPLdltN7wgE
|
||||
|
||||
3) If you're using git, that makes you a programmer! With your help we can make lazygit better, so consider becoming a contributor and joining the fun at
|
||||
2) If you're using git, that makes you a programmer! With your help we can make lazygit better, so consider becoming a contributor and joining the fun at
|
||||
https://github.com/jesseduffield/lazygit`,
|
||||
}, &i18n.Message{
|
||||
ID: "GitconfigParseErr",
|
||||
@@ -1176,6 +1182,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "viewCommits",
|
||||
Other: "view commits",
|
||||
}, &i18n.Message{
|
||||
ID: "minGitVersionError",
|
||||
Other: "Git version must be at least 2.0 (i.e. from 2014 onwards). Please upgrade your git version. Alternatively raise an issue at https://github.com/jesseduffield/lazygit/issues for lazygit to be more backwards compatible.",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -338,6 +338,12 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ForcePushPrompt",
|
||||
Other: "Twoja gałąź rozeszła się z gałęzią zdalną. Wciśnij 'esc' aby anulować lub 'enter' aby wymusić wypchnięcie.",
|
||||
}, &i18n.Message{
|
||||
ID: "ForcePushDisabled",
|
||||
Other: "Your branch has diverged from the remote branch and you've disabled force pushing",
|
||||
}, &i18n.Message{
|
||||
ID: "UpdatesRejectedAndForcePushDisabled",
|
||||
Other: "Updates were rejected and you have disabled force pushing",
|
||||
}, &i18n.Message{
|
||||
ID: "checkForUpdate",
|
||||
Other: "sprawdź aktualizacje",
|
||||
|
||||
Reference in New Issue
Block a user