1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-04-15 11:56:37 +02:00

Merge pull request #2333 from Ryooooooga/push-force-if-includes

This commit is contained in:
Jesse Duffield 2022-12-30 22:33:04 +11:00 committed by GitHub
commit 31bdd27e88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 186 additions and 91 deletions

View File

@ -7,8 +7,6 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strconv"
"strings" "strings"
"github.com/go-errors/errors" "github.com/go-errors/errors"
@ -17,7 +15,6 @@ import (
appTypes "github.com/jesseduffield/lazygit/pkg/app/types" appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
@ -101,54 +98,36 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
return app, err return app, err
} }
gitVersion, err := app.validateGitVersion()
if err != nil {
return app, err
}
showRecentRepos, err := app.setupRepo() showRecentRepos, err := app.setupRepo()
if err != nil { if err != nil {
return app, err return app, err
} }
gitConfig := git_config.NewStdCachedGitConfig(app.Log) app.Gui, err = gui.NewGui(common, config, gitVersion, app.Updater, showRecentRepos, dirName)
app.Gui, err = gui.NewGui(common, config, gitConfig, app.Updater, showRecentRepos, dirName)
if err != nil { if err != nil {
return app, err return app, err
} }
return app, nil return app, nil
} }
func (app *App) validateGitVersion() error { func (app *App) validateGitVersion() (*git_commands.GitVersion, error) {
output, err := app.OSCommand.Cmd.New("git --version").RunWithOutput() version, err := git_commands.GetGitVersion(app.OSCommand)
// if we get an error anywhere here we'll show the same status // if we get an error anywhere here we'll show the same status
minVersionError := errors.New(app.Tr.MinGitVersionError) minVersionError := errors.New(app.Tr.MinGitVersionError)
if err != nil { if err != nil {
return minVersionError return nil, minVersionError
} }
if isGitVersionValid(output) { if version.IsOlderThan(2, 0, 0) {
return nil return nil, minVersionError
} }
return minVersionError return version, nil
}
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 isDirectoryAGitRepository(dir string) (bool, error) { func isDirectoryAGitRepository(dir string) (bool, error) {
@ -169,10 +148,6 @@ func openRecentRepo(app *App) bool {
} }
func (app *App) setupRepo() (bool, error) { func (app *App) setupRepo() (bool, error) {
if err := app.validateGitVersion(); err != nil {
return false, err
}
if env.GetGitDirEnv() != "" { if env.GetGitDirEnv() != "" {
// we've been given the git dir directly. We'll verify this dir when initializing our Git object // we've been given the git dir directly. We'll verify this dir when initializing our Git object
return false, nil return false, nil

View File

@ -1,45 +0,0 @@
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 {
s := s
t.Run(s.versionStr, func(t *testing.T) {
result := isGitVersionValid(s.versionStr)
assert.Equal(t, result, s.expectedResult)
})
}
}

View File

@ -53,6 +53,7 @@ type Loaders struct {
func NewGitCommand( func NewGitCommand(
cmn *common.Common, cmn *common.Common,
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand, osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig, gitConfig git_config.IGitConfig,
syncMutex *deadlock.Mutex, syncMutex *deadlock.Mutex,
@ -73,6 +74,7 @@ func NewGitCommand(
return NewGitCommandAux( return NewGitCommandAux(
cmn, cmn,
version,
osCommand, osCommand,
gitConfig, gitConfig,
dotGitDir, dotGitDir,
@ -83,6 +85,7 @@ func NewGitCommand(
func NewGitCommandAux( func NewGitCommandAux(
cmn *common.Common, cmn *common.Common,
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand, osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig, gitConfig git_config.IGitConfig,
dotGitDir string, dotGitDir string,
@ -100,7 +103,7 @@ func NewGitCommandAux(
fileLoader := git_commands.NewFileLoader(cmn, cmd, configCommands) fileLoader := git_commands.NewFileLoader(cmn, cmd, configCommands)
gitCommon := git_commands.NewGitCommon(cmn, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex) gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex)
statusCommands := git_commands.NewStatusCommands(gitCommon) statusCommands := git_commands.NewStatusCommands(gitCommon)
flowCommands := git_commands.NewFlowCommands(gitCommon) flowCommands := git_commands.NewFlowCommands(gitCommon)
remoteCommands := git_commands.NewRemoteCommands(gitCommon) remoteCommands := git_commands.NewRemoteCommands(gitCommon)

View File

@ -9,6 +9,7 @@ import (
type GitCommon struct { type GitCommon struct {
*common.Common *common.Common
version *GitVersion
cmd oscommands.ICmdObjBuilder cmd oscommands.ICmdObjBuilder
os *oscommands.OSCommand os *oscommands.OSCommand
dotGitDir string dotGitDir string
@ -20,6 +21,7 @@ type GitCommon struct {
func NewGitCommon( func NewGitCommon(
cmn *common.Common, cmn *common.Common,
version *GitVersion,
cmd oscommands.ICmdObjBuilder, cmd oscommands.ICmdObjBuilder,
osCommand *oscommands.OSCommand, osCommand *oscommands.OSCommand,
dotGitDir string, dotGitDir string,
@ -29,6 +31,7 @@ func NewGitCommon(
) *GitCommon { ) *GitCommon {
return &GitCommon{ return &GitCommon{
Common: cmn, Common: cmn,
version: version,
cmd: cmd, cmd: cmd,
os: osCommand, os: osCommand,
dotGitDir: dotGitDir, dotGitDir: dotGitDir,

View File

@ -15,6 +15,7 @@ import (
type commonDeps struct { type commonDeps struct {
runner *oscommands.FakeCmdObjRunner runner *oscommands.FakeCmdObjRunner
userConfig *config.UserConfig userConfig *config.UserConfig
gitVersion *GitVersion
gitConfig *git_config.FakeGitConfig gitConfig *git_config.FakeGitConfig
getenv func(string) string getenv func(string) string
removeFile func(string) error removeFile func(string) error
@ -48,6 +49,11 @@ func buildGitCommon(deps commonDeps) *GitCommon {
gitCommon.Common.UserConfig = config.GetDefaultConfig() gitCommon.Common.UserConfig = config.GetDefaultConfig()
} }
gitCommon.version = deps.gitVersion
if gitCommon.version == nil {
gitCommon.version = &GitVersion{2, 0, 0, ""}
}
gitConfig := deps.gitConfig gitConfig := deps.gitConfig
if gitConfig == nil { if gitConfig == nil {
gitConfig = git_config.NewFakeGitConfig(nil) gitConfig = git_config.NewFakeGitConfig(nil)

View File

@ -29,7 +29,11 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error)
cmdStr := "git push" cmdStr := "git push"
if opts.Force { if opts.Force {
cmdStr += " --force-with-lease" if self.version.IsOlderThan(2, 30, 0) {
cmdStr += " --force-with-lease"
} else {
cmdStr += " --force-with-lease --force-if-includes"
}
} }
if opts.SetUpstream { if opts.SetUpstream {

View File

@ -10,6 +10,7 @@ import (
func TestSyncPush(t *testing.T) { func TestSyncPush(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
version *GitVersion
opts PushOpts opts PushOpts
test func(oscommands.ICmdObj, error) test func(oscommands.ICmdObj, error)
} }
@ -17,6 +18,7 @@ func TestSyncPush(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
testName: "Push with force disabled", testName: "Push with force disabled",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{Force: false}, opts: PushOpts{Force: false},
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push") assert.Equal(t, cmdObj.ToString(), "git push")
@ -25,14 +27,25 @@ func TestSyncPush(t *testing.T) {
}, },
{ {
testName: "Push with force enabled", testName: "Push with force enabled",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{Force: true}, opts: PushOpts{Force: true},
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease") assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease")
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{
testName: "Push with force enabled (>= 2.30.0)",
version: &GitVersion{2, 30, 0, ""},
opts: PushOpts{Force: true},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease --force-if-includes")
assert.NoError(t, err)
},
},
{ {
testName: "Push with force disabled, upstream supplied", testName: "Push with force disabled, upstream supplied",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{ opts: PushOpts{
Force: false, Force: false,
UpstreamRemote: "origin", UpstreamRemote: "origin",
@ -45,6 +58,7 @@ func TestSyncPush(t *testing.T) {
}, },
{ {
testName: "Push with force disabled, setting upstream", testName: "Push with force disabled, setting upstream",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{ opts: PushOpts{
Force: false, Force: false,
UpstreamRemote: "origin", UpstreamRemote: "origin",
@ -58,6 +72,7 @@ func TestSyncPush(t *testing.T) {
}, },
{ {
testName: "Push with force enabled, setting upstream", testName: "Push with force enabled, setting upstream",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{ opts: PushOpts{
Force: true, Force: true,
UpstreamRemote: "origin", UpstreamRemote: "origin",
@ -69,8 +84,23 @@ func TestSyncPush(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{
testName: "Push with force enabled, setting upstream (>= 2.30.0)",
version: &GitVersion{2, 30, 0, ""},
opts: PushOpts{
Force: true,
UpstreamRemote: "origin",
UpstreamBranch: "master",
SetUpstream: true,
},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), `git push --force-with-lease --force-if-includes --set-upstream "origin" "master"`)
assert.NoError(t, err)
},
},
{ {
testName: "Push with remote branch but no origin", testName: "Push with remote branch but no origin",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{ opts: PushOpts{
Force: true, Force: true,
UpstreamRemote: "", UpstreamRemote: "",
@ -87,7 +117,7 @@ func TestSyncPush(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
s := s s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{}) instance := buildSyncCommands(commonDeps{gitVersion: s.version})
s.test(instance.PushCmdObj(s.opts)) s.test(instance.PushCmdObj(s.opts))
}) })
} }

View File

@ -0,0 +1,67 @@
package git_commands
import (
"errors"
"regexp"
"strconv"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)
type GitVersion struct {
Major, Minor, Patch int
Additional string
}
func GetGitVersion(osCommand *oscommands.OSCommand) (*GitVersion, error) {
versionStr, _, err := osCommand.Cmd.New("git --version").RunWithOutputs()
if err != nil {
return nil, err
}
version, err := ParseGitVersion(versionStr)
if err != nil {
return nil, err
}
return version, nil
}
func ParseGitVersion(versionStr string) (*GitVersion, error) {
// versionStr should be something like:
// git version 2.39.0
// git version 2.37.1 (Apple Git-137.1)
re := regexp.MustCompile(`[^\d]+(\d+)(\.\d+)?(\.\d+)?(.*)`)
matches := re.FindStringSubmatch(versionStr)
if len(matches) < 5 {
return nil, errors.New("unexpected git version format: " + versionStr)
}
v := &GitVersion{}
var err error
if v.Major, err = strconv.Atoi(matches[1]); err != nil {
return nil, err
}
if len(matches[2]) > 1 {
if v.Minor, err = strconv.Atoi(matches[2][1:]); err != nil {
return nil, err
}
}
if len(matches[3]) > 1 {
if v.Patch, err = strconv.Atoi(matches[3][1:]); err != nil {
return nil, err
}
}
v.Additional = strings.Trim(matches[4], " \r\n")
return v, nil
}
func (v *GitVersion) IsOlderThan(major, minor, patch int) bool {
actual := v.Major*1000*1000 + v.Minor*1000 + v.Patch
required := major*1000*1000 + minor*1000 + patch
return actual < required
}

View File

@ -0,0 +1,47 @@
package git_commands
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseGitVersion(t *testing.T) {
scenarios := []struct {
input string
expected GitVersion
}{
{
input: "git version 2.39.0",
expected: GitVersion{Major: 2, Minor: 39, Patch: 0, Additional: ""},
},
{
input: "git version 2.37.1 (Apple Git-137.1)",
expected: GitVersion{Major: 2, Minor: 37, Patch: 1, Additional: "(Apple Git-137.1)"},
},
{
input: "git version 2.37 (Apple Git-137.1)",
expected: GitVersion{Major: 2, Minor: 37, Patch: 0, Additional: "(Apple Git-137.1)"},
},
}
for _, s := range scenarios {
actual, err := ParseGitVersion(s.input)
assert.NoError(t, err)
assert.NotNil(t, actual)
assert.Equal(t, s.expected.Major, actual.Major)
assert.Equal(t, s.expected.Minor, actual.Minor)
assert.Equal(t, s.expected.Patch, actual.Patch)
assert.Equal(t, s.expected.Additional, actual.Additional)
}
}
func TestGitVersionIsOlderThan(t *testing.T) {
assert.False(t, (&GitVersion{2, 0, 0, ""}).IsOlderThan(1, 99, 99))
assert.False(t, (&GitVersion{2, 0, 0, ""}).IsOlderThan(2, 0, 0))
assert.False(t, (&GitVersion{2, 1, 0, ""}).IsOlderThan(2, 0, 9))
assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(2, 1, 0))
assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(3, 0, 0))
}

View File

@ -8,6 +8,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
gogit "github.com/jesseduffield/go-git/v5" gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_config" "github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
@ -218,6 +219,7 @@ func TestNewGitCommand(t *testing.T) {
s.setup() s.setup()
s.test( s.test(
NewGitCommand(utils.NewDummyCommon(), NewGitCommand(utils.NewDummyCommon(),
&git_commands.GitVersion{},
oscommands.NewDummyOSCommand(), oscommands.NewDummyOSCommand(),
git_config.NewFakeGitConfig(nil), git_config.NewFakeGitConfig(nil),
&deadlock.Mutex{}, &deadlock.Mutex{},

View File

@ -1,7 +1,7 @@
package gui package gui
import ( import (
"github.com/jesseduffield/lazygit/pkg/commands/git_config" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/updates" "github.com/jesseduffield/lazygit/pkg/updates"
@ -17,6 +17,6 @@ func NewDummyUpdater() *updates.Updater {
func NewDummyGui() *Gui { func NewDummyGui() *Gui {
newAppConfig := config.NewDummyAppConfig() newAppConfig := config.NewDummyAppConfig()
dummyGui, _ := NewGui(utils.NewDummyCommon(), newAppConfig, git_config.NewFakeGitConfig(nil), NewDummyUpdater(), false, "") dummyGui, _ := NewGui(utils.NewDummyCommon(), newAppConfig, &git_commands.GitVersion{}, NewDummyUpdater(), false, "")
return dummyGui return dummyGui
} }

View File

@ -76,9 +76,10 @@ type Repo string
// Gui wraps the gocui Gui object which handles rendering and events // Gui wraps the gocui Gui object which handles rendering and events
type Gui struct { type Gui struct {
*common.Common *common.Common
g *gocui.Gui g *gocui.Gui
git *commands.GitCommand gitVersion *git_commands.GitVersion
os *oscommands.OSCommand git *commands.GitCommand
os *oscommands.OSCommand
// this is the state of the GUI for the current repo // this is the state of the GUI for the current repo
State *GuiRepoState State *GuiRepoState
@ -222,6 +223,7 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, reuseState bool) error {
var err error var err error
gui.git, err = commands.NewGitCommand( gui.git, err = commands.NewGitCommand(
gui.Common, gui.Common,
gui.gitVersion,
gui.os, gui.os,
git_config.NewStdCachedGitConfig(gui.Log), git_config.NewStdCachedGitConfig(gui.Log),
gui.Mutexes.SyncMutex, gui.Mutexes.SyncMutex,
@ -341,13 +343,14 @@ func initialContext(contextTree *context.ContextTree, startArgs appTypes.StartAr
func NewGui( func NewGui(
cmn *common.Common, cmn *common.Common,
config config.AppConfigurer, config config.AppConfigurer,
gitConfig git_config.IGitConfig, gitVersion *git_commands.GitVersion,
updater *updates.Updater, updater *updates.Updater,
showRecentRepos bool, showRecentRepos bool,
initialDir string, initialDir string,
) (*Gui, error) { ) (*Gui, error) {
gui := &Gui{ gui := &Gui{
Common: cmn, Common: cmn,
gitVersion: gitVersion,
Config: config, Config: config,
Updater: updater, Updater: updater,
statusManager: &statusManager{}, statusManager: &statusManager{},