mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-19 22:33:16 +02:00
Fix pushing to branch when upstream not stored locally (#3619)
- **PR Description** This fixes three different problems; two were recent regressions, one has never worked. 1. For branches whose remote is not stored locally, don't ask to force push right away. Try a normal push first. This broke with #3528. 2. For branches whose remote is not stored locally (but only for those), if the server rejects the update, ask to force push. This broke with #3387. 3. When force-pushing a branch whose remote is not stored locally, use `--force` instead of `--force-with-lease`; otherwise the push would always fail with a "stale info" error. This has never worked. Fixes #3588.
This commit is contained in:
commit
c5baa5da3a
@ -19,6 +19,7 @@ func NewSyncCommands(gitCommon *GitCommon) *SyncCommands {
|
|||||||
// Push pushes to a branch
|
// Push pushes to a branch
|
||||||
type PushOpts struct {
|
type PushOpts struct {
|
||||||
Force bool
|
Force bool
|
||||||
|
ForceWithLease bool
|
||||||
UpstreamRemote string
|
UpstreamRemote string
|
||||||
UpstreamBranch string
|
UpstreamBranch string
|
||||||
SetUpstream bool
|
SetUpstream bool
|
||||||
@ -30,7 +31,8 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmdArgs := NewGitCmd("push").
|
cmdArgs := NewGitCmd("push").
|
||||||
ArgIf(opts.Force, "--force-with-lease").
|
ArgIf(opts.Force, "--force").
|
||||||
|
ArgIf(opts.ForceWithLease, "--force-with-lease").
|
||||||
ArgIf(opts.SetUpstream, "--set-upstream").
|
ArgIf(opts.SetUpstream, "--set-upstream").
|
||||||
ArgIf(opts.UpstreamRemote != "", opts.UpstreamRemote).
|
ArgIf(opts.UpstreamRemote != "", opts.UpstreamRemote).
|
||||||
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
|
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
|
||||||
|
@ -18,24 +18,32 @@ func TestSyncPush(t *testing.T) {
|
|||||||
scenarios := []scenario{
|
scenarios := []scenario{
|
||||||
{
|
{
|
||||||
testName: "Push with force disabled",
|
testName: "Push with force disabled",
|
||||||
opts: PushOpts{Force: false},
|
opts: PushOpts{ForceWithLease: false},
|
||||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push"})
|
assert.Equal(t, cmdObj.Args(), []string{"git", "push"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
testName: "Push with force-with-lease enabled",
|
||||||
|
opts: PushOpts{ForceWithLease: true},
|
||||||
|
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||||
|
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
testName: "Push with force enabled",
|
testName: "Push with force enabled",
|
||||||
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.Args(), []string{"git", "push", "--force-with-lease"})
|
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "Push with force disabled, upstream supplied",
|
testName: "Push with force disabled, upstream supplied",
|
||||||
opts: PushOpts{
|
opts: PushOpts{
|
||||||
Force: false,
|
ForceWithLease: false,
|
||||||
UpstreamRemote: "origin",
|
UpstreamRemote: "origin",
|
||||||
UpstreamBranch: "master",
|
UpstreamBranch: "master",
|
||||||
},
|
},
|
||||||
@ -47,7 +55,7 @@ func TestSyncPush(t *testing.T) {
|
|||||||
{
|
{
|
||||||
testName: "Push with force disabled, setting upstream",
|
testName: "Push with force disabled, setting upstream",
|
||||||
opts: PushOpts{
|
opts: PushOpts{
|
||||||
Force: false,
|
ForceWithLease: false,
|
||||||
UpstreamRemote: "origin",
|
UpstreamRemote: "origin",
|
||||||
UpstreamBranch: "master",
|
UpstreamBranch: "master",
|
||||||
SetUpstream: true,
|
SetUpstream: true,
|
||||||
@ -58,9 +66,9 @@ func TestSyncPush(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "Push with force enabled, setting upstream",
|
testName: "Push with force-with-lease enabled, setting upstream",
|
||||||
opts: PushOpts{
|
opts: PushOpts{
|
||||||
Force: true,
|
ForceWithLease: true,
|
||||||
UpstreamRemote: "origin",
|
UpstreamRemote: "origin",
|
||||||
UpstreamBranch: "master",
|
UpstreamBranch: "master",
|
||||||
SetUpstream: true,
|
SetUpstream: true,
|
||||||
@ -73,7 +81,7 @@ func TestSyncPush(t *testing.T) {
|
|||||||
{
|
{
|
||||||
testName: "Push with remote branch but no origin",
|
testName: "Push with remote branch but no origin",
|
||||||
opts: PushOpts{
|
opts: PushOpts{
|
||||||
Force: true,
|
ForceWithLease: true,
|
||||||
UpstreamRemote: "",
|
UpstreamRemote: "",
|
||||||
UpstreamBranch: "master",
|
UpstreamBranch: "master",
|
||||||
SetUpstream: true,
|
SetUpstream: true,
|
||||||
|
@ -104,7 +104,7 @@ func (b *Branch) IsBehindForPull() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Branch) IsBehindForPush() bool {
|
func (b *Branch) IsBehindForPush() bool {
|
||||||
return b.BehindForPush != "" && b.BehindForPush != "0"
|
return b.RemoteBranchStoredLocally() && b.BehindForPush != "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// for when we're in a detached head state
|
// for when we're in a detached head state
|
||||||
|
@ -89,7 +89,7 @@ func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func(
|
|||||||
func (self *SyncController) push(currentBranch *models.Branch) error {
|
func (self *SyncController) push(currentBranch *models.Branch) error {
|
||||||
// if we are behind our upstream branch we'll ask if the user wants to force push
|
// if we are behind our upstream branch we'll ask if the user wants to force push
|
||||||
if currentBranch.IsTrackingRemote() {
|
if currentBranch.IsTrackingRemote() {
|
||||||
opts := pushOpts{}
|
opts := pushOpts{remoteBranchStoredLocally: currentBranch.RemoteBranchStoredLocally()}
|
||||||
if currentBranch.IsBehindForPush() {
|
if currentBranch.IsBehindForPush() {
|
||||||
return self.requestToForcePush(currentBranch, opts)
|
return self.requestToForcePush(currentBranch, opts)
|
||||||
} else {
|
} else {
|
||||||
@ -180,9 +180,16 @@ func (self *SyncController) pullWithLock(task gocui.Task, opts PullFilesOptions)
|
|||||||
|
|
||||||
type pushOpts struct {
|
type pushOpts struct {
|
||||||
force bool
|
force bool
|
||||||
|
forceWithLease bool
|
||||||
upstreamRemote string
|
upstreamRemote string
|
||||||
upstreamBranch string
|
upstreamBranch string
|
||||||
setUpstream bool
|
setUpstream bool
|
||||||
|
|
||||||
|
// If this is false, we can't tell ahead of time whether a force-push will
|
||||||
|
// be necessary, so we start with a normal push and offer to force-push if
|
||||||
|
// the server rejected. If this is true, we don't offer to force-push if the
|
||||||
|
// server rejected, but rather ask the user to fetch.
|
||||||
|
remoteBranchStoredLocally bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) error {
|
func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) error {
|
||||||
@ -192,14 +199,33 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
|
|||||||
task,
|
task,
|
||||||
git_commands.PushOpts{
|
git_commands.PushOpts{
|
||||||
Force: opts.force,
|
Force: opts.force,
|
||||||
|
ForceWithLease: opts.forceWithLease,
|
||||||
UpstreamRemote: opts.upstreamRemote,
|
UpstreamRemote: opts.upstreamRemote,
|
||||||
UpstreamBranch: opts.upstreamBranch,
|
UpstreamBranch: opts.upstreamBranch,
|
||||||
SetUpstream: opts.setUpstream,
|
SetUpstream: opts.setUpstream,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "Updates were rejected") {
|
if !opts.force && !opts.forceWithLease && strings.Contains(err.Error(), "Updates were rejected") {
|
||||||
|
if opts.remoteBranchStoredLocally {
|
||||||
return errors.New(self.c.Tr.UpdatesRejected)
|
return errors.New(self.c.Tr.UpdatesRejected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
|
||||||
|
if forcePushDisabled {
|
||||||
|
return errors.New(self.c.Tr.UpdatesRejectedAndForcePushDisabled)
|
||||||
|
}
|
||||||
|
_ = self.c.Confirm(types.ConfirmOpts{
|
||||||
|
Title: self.c.Tr.ForcePush,
|
||||||
|
Prompt: self.forcePushPrompt(),
|
||||||
|
HandleConfirm: func() error {
|
||||||
|
newOpts := opts
|
||||||
|
newOpts.force = true
|
||||||
|
|
||||||
|
return self.pushAux(currentBranch, newOpts)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||||
@ -216,7 +242,7 @@ func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opt
|
|||||||
Title: self.c.Tr.ForcePush,
|
Title: self.c.Tr.ForcePush,
|
||||||
Prompt: self.forcePushPrompt(),
|
Prompt: self.forcePushPrompt(),
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
opts.force = true
|
opts.forceWithLease = true
|
||||||
return self.pushAux(currentBranch, opts)
|
return self.pushAux(currentBranch, opts)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -132,6 +132,7 @@ func chineseTranslationSet() TranslationSet {
|
|||||||
ForcePush: "强制推送",
|
ForcePush: "强制推送",
|
||||||
ForcePushPrompt: "您的分支已与远程分支不同。按‘esc’取消,或‘enter’强制推送.",
|
ForcePushPrompt: "您的分支已与远程分支不同。按‘esc’取消,或‘enter’强制推送.",
|
||||||
ForcePushDisabled: "您的分支已与远程分支不同, 并且您已经禁用了强行推送",
|
ForcePushDisabled: "您的分支已与远程分支不同, 并且您已经禁用了强行推送",
|
||||||
|
UpdatesRejectedAndForcePushDisabled: "更新被拒绝,您已禁用强制推送",
|
||||||
CheckForUpdate: "检查更新",
|
CheckForUpdate: "检查更新",
|
||||||
CheckingForUpdates: "正在检查更新…",
|
CheckingForUpdates: "正在检查更新…",
|
||||||
OnLatestVersionErr: "已是最新版本",
|
OnLatestVersionErr: "已是最新版本",
|
||||||
|
@ -198,6 +198,7 @@ type TranslationSet struct {
|
|||||||
ForcePushPrompt string
|
ForcePushPrompt string
|
||||||
ForcePushDisabled string
|
ForcePushDisabled string
|
||||||
UpdatesRejected string
|
UpdatesRejected string
|
||||||
|
UpdatesRejectedAndForcePushDisabled string
|
||||||
CheckForUpdate string
|
CheckForUpdate string
|
||||||
CheckingForUpdates string
|
CheckingForUpdates string
|
||||||
UpdateAvailableTitle string
|
UpdateAvailableTitle string
|
||||||
@ -1160,6 +1161,7 @@ func EnglishTranslationSet() TranslationSet {
|
|||||||
ForcePushPrompt: "Your branch has diverged from the remote branch. Press {{.cancelKey}} to cancel, or {{.confirmKey}} to force push.",
|
ForcePushPrompt: "Your branch has diverged from the remote branch. Press {{.cancelKey}} to cancel, or {{.confirmKey}} to force push.",
|
||||||
ForcePushDisabled: "Your branch has diverged from the remote branch and you've disabled force pushing",
|
ForcePushDisabled: "Your branch has diverged from the remote branch and you've disabled force pushing",
|
||||||
UpdatesRejected: "Updates were rejected. Please fetch and examine the remote changes before pushing again.",
|
UpdatesRejected: "Updates were rejected. Please fetch and examine the remote changes before pushing again.",
|
||||||
|
UpdatesRejectedAndForcePushDisabled: "Updates were rejected and you have disabled force pushing",
|
||||||
CheckForUpdate: "Check for update",
|
CheckForUpdate: "Check for update",
|
||||||
CheckingForUpdates: "Checking for updates...",
|
CheckingForUpdates: "Checking for updates...",
|
||||||
UpdateAvailableTitle: "Update available!",
|
UpdateAvailableTitle: "Update available!",
|
||||||
|
@ -124,6 +124,7 @@ func koreanTranslationSet() TranslationSet {
|
|||||||
ForcePush: "강제 푸시",
|
ForcePush: "강제 푸시",
|
||||||
ForcePushPrompt: "브랜치가 원격 브랜치에서 분기하고 있습니다. 'esc'를 눌러 취소하거나, 'enter'를 눌러 강제로 푸시하세요.",
|
ForcePushPrompt: "브랜치가 원격 브랜치에서 분기하고 있습니다. 'esc'를 눌러 취소하거나, 'enter'를 눌러 강제로 푸시하세요.",
|
||||||
ForcePushDisabled: "브랜치가 원격 브랜치에서 분기하고 있습니다. force push가 비활성화 되었습니다.",
|
ForcePushDisabled: "브랜치가 원격 브랜치에서 분기하고 있습니다. force push가 비활성화 되었습니다.",
|
||||||
|
UpdatesRejectedAndForcePushDisabled: "업데이트가 거부되었으며 강제 푸시를 비활성화했습니다.",
|
||||||
CheckForUpdate: "업데이트 확인",
|
CheckForUpdate: "업데이트 확인",
|
||||||
CheckingForUpdates: "업데이트 확인 중...",
|
CheckingForUpdates: "업데이트 확인 중...",
|
||||||
UpdateAvailableTitle: "새로운 업데이트 사용가능!",
|
UpdateAvailableTitle: "새로운 업데이트 사용가능!",
|
||||||
|
@ -183,6 +183,7 @@ func polishTranslationSet() TranslationSet {
|
|||||||
ForcePush: "Wymuś wysłanie",
|
ForcePush: "Wymuś wysłanie",
|
||||||
ForcePushPrompt: "Twoja gałąź rozbiegła się z gałęzią zdalną. Naciśnij {{.cancelKey}}, aby anulować, lub {{.confirmKey}}, aby wymusić wysłanie.",
|
ForcePushPrompt: "Twoja gałąź rozbiegła się z gałęzią zdalną. Naciśnij {{.cancelKey}}, aby anulować, lub {{.confirmKey}}, aby wymusić wysłanie.",
|
||||||
ForcePushDisabled: "Twoja gałąź rozbiegła się z gałęzią zdalną i masz wyłączone wymuszanie wysyłania",
|
ForcePushDisabled: "Twoja gałąź rozbiegła się z gałęzią zdalną i masz wyłączone wymuszanie wysyłania",
|
||||||
|
UpdatesRejectedAndForcePushDisabled: "Aktualizacje zostały odrzucone i wyłączyłeś wymuszenie wysłania",
|
||||||
CheckForUpdate: "Sprawdź aktualizacje",
|
CheckForUpdate: "Sprawdź aktualizacje",
|
||||||
CheckingForUpdates: "Sprawdzanie aktualizacji...",
|
CheckingForUpdates: "Sprawdzanie aktualizacji...",
|
||||||
UpdateAvailableTitle: "Dostępna aktualizacja!",
|
UpdateAvailableTitle: "Dostępna aktualizacja!",
|
||||||
|
@ -152,6 +152,7 @@ func RussianTranslationSet() TranslationSet {
|
|||||||
ForcePush: "Принудительная отправка изменении",
|
ForcePush: "Принудительная отправка изменении",
|
||||||
ForcePushPrompt: "Ветка отклонилась от удалённой ветки. Нажмите «esc», чтобы отменить, или «enter», чтобы начать принудительную отправку изменении.",
|
ForcePushPrompt: "Ветка отклонилась от удалённой ветки. Нажмите «esc», чтобы отменить, или «enter», чтобы начать принудительную отправку изменении.",
|
||||||
ForcePushDisabled: "Ветка отклонилась от удалённой ветки. Принудительная отправка изменении была отключена",
|
ForcePushDisabled: "Ветка отклонилась от удалённой ветки. Принудительная отправка изменении была отключена",
|
||||||
|
UpdatesRejectedAndForcePushDisabled: "Обновления были отклонены. Принудительная отправка изменении была отключена",
|
||||||
CheckForUpdate: "Проверить обновления",
|
CheckForUpdate: "Проверить обновления",
|
||||||
CheckingForUpdates: "Проверка обновлений...",
|
CheckingForUpdates: "Проверка обновлений...",
|
||||||
UpdateAvailableTitle: "Доступно обновление!",
|
UpdateAvailableTitle: "Доступно обновление!",
|
||||||
|
@ -184,6 +184,7 @@ func traditionalChineseTranslationSet() TranslationSet {
|
|||||||
ForcePush: "強制推送",
|
ForcePush: "強制推送",
|
||||||
ForcePushPrompt: "你的分支與遠端分支分岔。按 'ESC' 取消,或按 'Enter' 強制推送。",
|
ForcePushPrompt: "你的分支與遠端分支分岔。按 'ESC' 取消,或按 'Enter' 強制推送。",
|
||||||
ForcePushDisabled: "你的分支與遠端分支分岔,你已禁用強制推送",
|
ForcePushDisabled: "你的分支與遠端分支分岔,你已禁用強制推送",
|
||||||
|
UpdatesRejectedAndForcePushDisabled: "更新被拒絕,你已禁用強制推送",
|
||||||
CheckForUpdate: "檢查更新",
|
CheckForUpdate: "檢查更新",
|
||||||
CheckingForUpdates: "正在檢查更新...",
|
CheckingForUpdates: "正在檢查更新...",
|
||||||
UpdateAvailableTitle: "有可用的更新!",
|
UpdateAvailableTitle: "有可用的更新!",
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ForcePushRemoteBranchNotStoredLocally = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Push a branch whose remote branch is not stored locally, requiring a force push",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
shell.EmptyCommit("one")
|
||||||
|
shell.EmptyCommit("two")
|
||||||
|
|
||||||
|
shell.Clone("some-remote")
|
||||||
|
|
||||||
|
// remove the 'two' commit so that we have something to pull from the remote
|
||||||
|
shell.HardReset("HEAD^")
|
||||||
|
|
||||||
|
shell.SetConfig("branch.master.remote", "../some-remote")
|
||||||
|
shell.SetConfig("branch.master.pushRemote", "../some-remote")
|
||||||
|
shell.SetConfig("branch.master.merge", "refs/heads/master")
|
||||||
|
|
||||||
|
shell.CreateFileAndAdd("file1", "file1 content")
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
t.Views().Commits().
|
||||||
|
Lines(
|
||||||
|
Contains("one"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Views().Status().Content(Contains("? repo → master"))
|
||||||
|
|
||||||
|
// We're behind our upstream now, so we expect to be asked to force-push
|
||||||
|
t.Views().Files().IsFocused().Press(keys.Universal.Push)
|
||||||
|
|
||||||
|
t.ExpectPopup().Confirmation().
|
||||||
|
Title(Equals("Force push")).
|
||||||
|
Content(Equals("Your branch has diverged from the remote branch. Press <esc> to cancel, or <enter> to force push.")).
|
||||||
|
Confirm()
|
||||||
|
|
||||||
|
// Make a new local commit
|
||||||
|
t.Views().Files().IsFocused().Press(keys.Files.CommitChanges)
|
||||||
|
t.ExpectPopup().CommitMessagePanel().Type("new").Confirm()
|
||||||
|
|
||||||
|
t.Views().Commits().
|
||||||
|
Lines(
|
||||||
|
Contains("new"),
|
||||||
|
Contains("one"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pushing this works without needing to force push
|
||||||
|
t.Views().Files().IsFocused().Press(keys.Universal.Push)
|
||||||
|
|
||||||
|
// Now add the clone as a remote just so that we can check if what we
|
||||||
|
// pushed arrived there correctly
|
||||||
|
t.Views().Remotes().Focus().
|
||||||
|
Press(keys.Universal.New)
|
||||||
|
|
||||||
|
t.ExpectPopup().Prompt().
|
||||||
|
Title(Equals("New remote name:")).Type("some-remote").Confirm()
|
||||||
|
t.ExpectPopup().Prompt().
|
||||||
|
Title(Equals("New remote url:")).Type("../some-remote").Confirm()
|
||||||
|
t.Views().Remotes().Lines(
|
||||||
|
Contains("some-remote").IsSelected(),
|
||||||
|
).
|
||||||
|
PressEnter()
|
||||||
|
|
||||||
|
t.Views().RemoteBranches().IsFocused().Lines(
|
||||||
|
Contains("master").IsSelected(),
|
||||||
|
).
|
||||||
|
PressEnter()
|
||||||
|
|
||||||
|
t.Views().SubCommits().IsFocused().Lines(
|
||||||
|
Contains("new"),
|
||||||
|
Contains("one"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
@ -276,6 +276,7 @@ var tests = []*components.IntegrationTest{
|
|||||||
sync.ForcePush,
|
sync.ForcePush,
|
||||||
sync.ForcePushMultipleMatching,
|
sync.ForcePushMultipleMatching,
|
||||||
sync.ForcePushMultipleUpstream,
|
sync.ForcePushMultipleUpstream,
|
||||||
|
sync.ForcePushRemoteBranchNotStoredLocally,
|
||||||
sync.ForcePushTriangular,
|
sync.ForcePushTriangular,
|
||||||
sync.Pull,
|
sync.Pull,
|
||||||
sync.PullAndSetUpstream,
|
sync.PullAndSetUpstream,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user