From 20b268fa90455e26ec929e813ec540621a331e3e Mon Sep 17 00:00:00 2001 From: root Date: Sat, 21 Feb 2026 23:07:31 +0800 Subject: [PATCH 1/2] Add support for git-svn repository. 1. Can detect repository automatically. 2. Remap push/pull/fetch commands to git svn dcommit/rebase/fetch commands. 3. Command log window will show the repository type. --- pkg/commands/git_commands/common.go | 31 +++++++++++++++++++++++++- pkg/commands/git_commands/sync.go | 15 +++++++++++++ pkg/config/user_config.go | 3 +++ pkg/gui/command_log_panel.go | 6 +++++ pkg/gui/controllers/sync_controller.go | 16 +++++++++++++ 5 files changed, 70 insertions(+), 1 deletion(-) diff --git a/pkg/commands/git_commands/common.go b/pkg/commands/git_commands/common.go index 28dd78e8bb7..f78e849792f 100644 --- a/pkg/commands/git_commands/common.go +++ b/pkg/commands/git_commands/common.go @@ -1,6 +1,8 @@ package git_commands import ( + "os" + "path/filepath" gogit "github.com/jesseduffield/go-git/v5" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/common" @@ -16,6 +18,27 @@ type GitCommon struct { repo *gogit.Repository config *ConfigCommands pagerConfig *config.PagerConfig + IsGitSvnRepo bool +} + +func (self *GitCommon) detectGitSvnRepo() { + if self.Common != nil && !self.Common.UserConfig().Git.EnableGitSvnCompat { + self.IsGitSvnRepo = false + return + } + + if self.repoPaths == nil { + self.IsGitSvnRepo = false + return + } + + svnDir := filepath.Join(self.repoPaths.RepoGitDirPath(), "svn") + if info, err := os.Stat(svnDir); err == nil && info.IsDir() { + self.IsGitSvnRepo = true + self.Common.Log.Info("Detected Git-SVN repository (found .git/svn)") + } else { + self.IsGitSvnRepo = false + } } func NewGitCommon( @@ -28,7 +51,7 @@ func NewGitCommon( config *ConfigCommands, pagerConfig *config.PagerConfig, ) *GitCommon { - return &GitCommon{ + gitCommon := &GitCommon{ Common: cmn, version: version, cmd: cmd, @@ -38,4 +61,10 @@ func NewGitCommon( config: config, pagerConfig: pagerConfig, } + gitCommon.detectGitSvnRepo() + return gitCommon +} + +func (self *GitCommon) IsSvnRepo() bool { + return self.IsGitSvnRepo } diff --git a/pkg/commands/git_commands/sync.go b/pkg/commands/git_commands/sync.go index d64a0910c98..f09e37451e6 100644 --- a/pkg/commands/git_commands/sync.go +++ b/pkg/commands/git_commands/sync.go @@ -29,6 +29,11 @@ type PushOpts struct { } func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (*oscommands.CmdObj, error) { + if self.IsGitSvnRepo { + cmdArgs := NewGitCmd("svn").Arg("dcommit").ToArgv() + return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task), nil + } + if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" { return nil, errors.New(self.Tr.MustSpecifyOriginError) } @@ -63,6 +68,11 @@ func (self *SyncCommands) fetchCommandBuilder(fetchAll bool) *GitCommandBuilder } func (self *SyncCommands) FetchCmdObj(task gocui.Task) *oscommands.CmdObj { + if self.IsGitSvnRepo { + cmdArgs := NewGitCmd("svn").Arg("fetch").ToArgv() + return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task) + } + cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv() cmdObj := self.cmd.New(cmdArgs) @@ -96,6 +106,11 @@ type PullOptions struct { } func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error { + if self.IsGitSvnRepo { + cmdArgs := NewGitCmd("svn").Arg("rebase").ToArgv() + return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() + } + cmdArgs := NewGitCmd("pull"). Arg("--no-edit"). ArgIf(opts.FastForwardOnly, "--ff-only"). diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index abf591801f5..a88c49d3f66 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -321,6 +321,8 @@ type GitConfig struct { RemoteBranchSortOrder string `yaml:"remoteBranchSortOrder" jsonschema:"enum=date,enum=alphabetical"` // When copying commit hashes to the clipboard, truncate them to this length. Set to 40 to disable truncation. TruncateCopiedCommitHashesTo int `yaml:"truncateCopiedCommitHashesTo"` + // If true, will detect if git repository is created using git-svn, is so, will use git svn dcommit/rebase for push/pull operations. + EnableGitSvnCompat bool `toml:"EnableGitSvnCompat"` } type PagerType string @@ -857,6 +859,7 @@ func GetDefaultConfig() *UserConfig { BranchPrefix: "", ParseEmoji: false, TruncateCopiedCommitHashesTo: 12, + EnableGitSvnCompat: true, }, Refresher: RefresherConfig{ RefreshInterval: 10, diff --git a/pkg/gui/command_log_panel.go b/pkg/gui/command_log_panel.go index d4b847c946d..21d510a2342 100644 --- a/pkg/gui/command_log_panel.go +++ b/pkg/gui/command_log_panel.go @@ -59,6 +59,12 @@ func (gui *Gui) printCommandLogHeader() { ) fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr)) + if gui.git.Sync.GitCommon.IsSvnRepo() { + fmt.Fprintln(gui.Views.Extras, "Is a Git-SVN repository: Pull=rebase | Push=dcommit") + } else { + fmt.Fprintln(gui.Views.Extras, "Is a Git repository") + } + if gui.c.UserConfig().Gui.ShowRandomTip { fmt.Fprintf( gui.Views.Extras, diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go index 023ac0d2589..e1d9e409fb8 100644 --- a/pkg/gui/controllers/sync_controller.go +++ b/pkg/gui/controllers/sync_controller.go @@ -175,6 +175,12 @@ func (self *SyncController) pullWithLock(task gocui.Task, opts PullFilesOptions) }, ) + if self.c.Git().Sync.GitCommon.IsSvnRepo() { + if err != nil { + return fmt.Errorf("Git-SVN rebase failed: %w", err) + } + } + return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) } @@ -195,6 +201,12 @@ type pushOpts struct { func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) error { return self.c.WithInlineStatus(currentBranch, types.ItemOperationPushing, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.Push) + + if self.c.Git().Sync.GitCommon.IsSvnRepo() { + opts.force = false + opts.forceWithLease = false + } + err := self.c.Git().Sync.Push( task, git_commands.PushOpts{ @@ -206,6 +218,10 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) SetUpstream: opts.setUpstream, }) if err != nil { + if self.c.Git().Sync.GitCommon.IsSvnRepo() { + return fmt.Errorf("Git-SVN dcommit failed: %w", err) + } + if !opts.force && !opts.forceWithLease && strings.Contains(err.Error(), "Updates were rejected") { if opts.remoteBranchStoredLocally { return errors.New(self.c.Tr.UpdatesRejected) From 793ab9a0c9f02bfabb596653a3c390ad4f49919d Mon Sep 17 00:00:00 2001 From: root Date: Sun, 22 Feb 2026 15:55:58 +0800 Subject: [PATCH 2/2] Don't ask for upstream when pull/push for git-svn repository. --- pkg/gui/controllers/sync_controller.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go index e1d9e409fb8..2c37bac3bcf 100644 --- a/pkg/gui/controllers/sync_controller.go +++ b/pkg/gui/controllers/sync_controller.go @@ -87,6 +87,10 @@ func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func( } func (self *SyncController) push(currentBranch *models.Branch) error { + if self.c.Git().Sync.GitCommon.IsSvnRepo() { + return self.pushAux(currentBranch, pushOpts{setUpstream: false}) + } + // if we are behind our upstream branch we'll ask if the user wants to force push if currentBranch.IsTrackingRemote() { opts := pushOpts{remoteBranchStoredLocally: currentBranch.RemoteBranchStoredLocally()} @@ -119,14 +123,16 @@ func (self *SyncController) pull(currentBranch *models.Branch) error { action := self.c.Tr.Actions.Pull // if we have no upstream branch we need to set that first - if !currentBranch.IsTrackingRemote() { - return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error { - if err := self.setCurrentBranchUpstream(upstream); err != nil { - return err - } + if !self.c.Git().Sync.GitCommon.IsSvnRepo() { + if !currentBranch.IsTrackingRemote() { + return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error { + if err := self.setCurrentBranchUpstream(upstream); err != nil { + return err + } - return self.PullAux(currentBranch, PullFilesOptions{Action: action}) - }) + return self.PullAux(currentBranch, PullFilesOptions{Action: action}) + }) + } } return self.PullAux(currentBranch, PullFilesOptions{Action: action})