mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-06 05:57:38 -04:00
The pane beside a commit's diff showed the patch being built from it by assembling the text itself. That text could not be handed to a diff renderer the way a diff can: a stdin filter might have coped, but a tool that diffs two files could not. Its idea of how much context to show around a hunk was also its own rather than git's. Materialize the patch instead: write each of its files as it is before the patch into one tree and as it is after into another, and let git diff the two trees. The patch becomes a diff of real files, rendered by whatever renders the rest of them, with git's own context around it. Its lines can then be pointed at; taking them back out of the patch will need that. The trees are named a and b, so that with git's own prefixes suppressed the paths read like an ordinary diff's over the repo's own paths. They are written when the patch changes rather than when it is shown, the patch builder counting its own versions for that, and they go away with the patch. A renamed file is materialized under the name the patch expects to find it under. Where the patch carries the rename, that is the name the file had before, so the rename comes out as a rename. git names the trees themselves in the two rename lines, having only the two paths to go by. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
205 lines
8.3 KiB
Go
205 lines
8.3 KiB
Go
package commands
|
|
|
|
import (
|
|
"os"
|
|
|
|
"github.com/go-errors/errors"
|
|
|
|
"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/patch"
|
|
"github.com/jesseduffield/lazygit/pkg/common"
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
"github.com/jesseduffield/lazygit/pkg/env"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
)
|
|
|
|
// GitCommand is our main git interface
|
|
type GitCommand struct {
|
|
Blame *git_commands.BlameCommands
|
|
Branch *git_commands.BranchCommands
|
|
Commit *git_commands.CommitCommands
|
|
Config *git_commands.ConfigCommands
|
|
Custom *git_commands.CustomCommands
|
|
Diff *git_commands.DiffCommands
|
|
File *git_commands.FileCommands
|
|
Flow *git_commands.FlowCommands
|
|
Patch *git_commands.PatchCommands
|
|
Rebase *git_commands.RebaseCommands
|
|
Remote *git_commands.RemoteCommands
|
|
Stash *git_commands.StashCommands
|
|
Status *git_commands.StatusCommands
|
|
Submodule *git_commands.SubmoduleCommands
|
|
Sync *git_commands.SyncCommands
|
|
Tag *git_commands.TagCommands
|
|
WorkingTree *git_commands.WorkingTreeCommands
|
|
Bisect *git_commands.BisectCommands
|
|
Worktree *git_commands.WorktreeCommands
|
|
Version *git_commands.GitVersion
|
|
RepoPaths *git_commands.RepoPaths
|
|
GitHub *git_commands.GitHubCommands
|
|
HostingService *git_commands.HostingService
|
|
|
|
Loaders Loaders
|
|
}
|
|
|
|
type Loaders struct {
|
|
BranchLoader *git_commands.BranchLoader
|
|
CommitFileLoader *git_commands.CommitFileLoader
|
|
CommitLoader *git_commands.CommitLoader
|
|
FileLoader *git_commands.FileLoader
|
|
ReflogCommitLoader *git_commands.ReflogCommitLoader
|
|
RemoteLoader *git_commands.RemoteLoader
|
|
StashLoader *git_commands.StashLoader
|
|
TagLoader *git_commands.TagLoader
|
|
Worktrees *git_commands.WorktreeLoader
|
|
}
|
|
|
|
func NewGitCommand(
|
|
cmn *common.Common,
|
|
version *git_commands.GitVersion,
|
|
osCommand *oscommands.OSCommand,
|
|
gitConfig git_config.IGitConfig,
|
|
diffRendererConfigManager *config.DiffRendererConfigManager,
|
|
) (*GitCommand, error) {
|
|
repoPaths, err := git_commands.GetRepoPaths(osCommand.Cmd, version)
|
|
if err != nil {
|
|
return nil, errors.Errorf("Error getting repo paths: %v", err)
|
|
}
|
|
|
|
// A bare repo has no worktree for us to work in. Callers that can offer the
|
|
// user something better (app.setupRepo) check for this first; getting here
|
|
// means nobody could, e.g. because --git-dir was pointed at a bare repo.
|
|
if repoPaths.IsBareRepo() {
|
|
return nil, errors.New(cmn.Tr.BareRepoNotSupported)
|
|
}
|
|
|
|
err = os.Chdir(repoPaths.WorktreePath())
|
|
if err != nil {
|
|
return nil, utils.WrapError(err)
|
|
}
|
|
|
|
// Everything we run through the command builder gets told where the repo is
|
|
// by the builder itself, but subprocesses don't go through it: user-defined
|
|
// custom commands, an editor, and the lazygit we re-enter as git's sequence
|
|
// editor during a rebase. Put it in the process env for those.
|
|
env.SetGitLocationEnvVars(repoPaths.GitLocationEnvVars())
|
|
|
|
// Pin the config reads to the repo directory like all other git commands
|
|
// (see NewGitCmdObjBuilder); the config commands run outside that builder.
|
|
gitConfig.SetDir(repoPaths.WorktreePath())
|
|
|
|
return NewGitCommandAux(
|
|
cmn,
|
|
version,
|
|
osCommand,
|
|
gitConfig,
|
|
repoPaths,
|
|
diffRendererConfigManager,
|
|
), nil
|
|
}
|
|
|
|
func NewGitCommandAux(
|
|
cmn *common.Common,
|
|
version *git_commands.GitVersion,
|
|
osCommand *oscommands.OSCommand,
|
|
gitConfig git_config.IGitConfig,
|
|
repoPaths *git_commands.RepoPaths,
|
|
diffRendererConfigManager *config.DiffRendererConfigManager,
|
|
) *GitCommand {
|
|
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd, repoPaths.WorktreePath(), repoPaths.GitLocationEnvVars())
|
|
|
|
// here we're doing a bunch of dependency injection for each of our commands structs.
|
|
// This is admittedly messy, but allows us to test each command struct in isolation,
|
|
// and allows for better namespacing when compared to having every method living
|
|
// on the one struct.
|
|
// common ones are: cmn, osCommand, dotGitDir, configCommands
|
|
configCommands := git_commands.NewConfigCommands(cmn, gitConfig)
|
|
|
|
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, configCommands, diffRendererConfigManager)
|
|
|
|
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
|
|
statusCommands := git_commands.NewStatusCommands(gitCommon)
|
|
flowCommands := git_commands.NewFlowCommands(gitCommon)
|
|
remoteCommands := git_commands.NewRemoteCommands(gitCommon)
|
|
branchCommands := git_commands.NewBranchCommands(gitCommon)
|
|
syncCommands := git_commands.NewSyncCommands(gitCommon)
|
|
tagCommands := git_commands.NewTagCommands(gitCommon)
|
|
commitCommands := git_commands.NewCommitCommands(gitCommon)
|
|
customCommands := git_commands.NewCustomCommands(gitCommon)
|
|
diffCommands := git_commands.NewDiffCommands(gitCommon)
|
|
fileCommands := git_commands.NewFileCommands(gitCommon)
|
|
submoduleCommands := git_commands.NewSubmoduleCommands(gitCommon)
|
|
workingTreeCommands := git_commands.NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
|
|
rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands)
|
|
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
|
|
patchBuilder := patch.NewPatchBuilder(cmn.Log,
|
|
func(from string, to string, reverse bool, filename string, previousPath string) (string, error) {
|
|
// A patch is built from git's own diff: what a diff renderer would make of it
|
|
// is a picture of it, not something that can be applied.
|
|
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, previousPath, git_commands.DiffModePlain)
|
|
},
|
|
func() (string, error) {
|
|
// Under lazygit's own temp dir, so that it honours the configured location
|
|
// and is cleaned up with everything else when we exit.
|
|
return os.MkdirTemp(osCommand.GetTempDir(), "custom-patch-")
|
|
})
|
|
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
|
|
bisectCommands := git_commands.NewBisectCommands(gitCommon)
|
|
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
|
|
blameCommands := git_commands.NewBlameCommands(gitCommon)
|
|
gitHubCommands := git_commands.NewGitHubCommands(gitCommon)
|
|
hostingServiceCommands := git_commands.NewHostingServiceCommand(gitCommon)
|
|
|
|
branchLoader := git_commands.NewBranchLoader(cmn, gitCommon, cmd, branchCommands.CurrentBranchInfo, branchCommands.HasLocalOnlyCommits, configCommands)
|
|
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
|
|
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.WorkingTreeState, gitCommon)
|
|
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
|
remoteLoader := git_commands.NewRemoteLoader(gitCommon)
|
|
worktreeLoader := git_commands.NewWorktreeLoader(gitCommon)
|
|
stashLoader := git_commands.NewStashLoader(cmn, cmd)
|
|
tagLoader := git_commands.NewTagLoader(cmn, cmd)
|
|
|
|
return &GitCommand{
|
|
Blame: blameCommands,
|
|
Branch: branchCommands,
|
|
Commit: commitCommands,
|
|
Config: configCommands,
|
|
Custom: customCommands,
|
|
Diff: diffCommands,
|
|
File: fileCommands,
|
|
Flow: flowCommands,
|
|
Patch: patchCommands,
|
|
Rebase: rebaseCommands,
|
|
Remote: remoteCommands,
|
|
Stash: stashCommands,
|
|
Status: statusCommands,
|
|
Submodule: submoduleCommands,
|
|
Sync: syncCommands,
|
|
Tag: tagCommands,
|
|
Bisect: bisectCommands,
|
|
WorkingTree: workingTreeCommands,
|
|
Worktree: worktreeCommands,
|
|
Version: version,
|
|
GitHub: gitHubCommands,
|
|
HostingService: hostingServiceCommands,
|
|
Loaders: Loaders{
|
|
BranchLoader: branchLoader,
|
|
CommitFileLoader: commitFileLoader,
|
|
CommitLoader: commitLoader,
|
|
FileLoader: fileLoader,
|
|
ReflogCommitLoader: reflogCommitLoader,
|
|
RemoteLoader: remoteLoader,
|
|
Worktrees: worktreeLoader,
|
|
StashLoader: stashLoader,
|
|
TagLoader: tagLoader,
|
|
},
|
|
RepoPaths: repoPaths,
|
|
}
|
|
}
|
|
|
|
func VerifyInGitRepo(osCommand *oscommands.OSCommand) error {
|
|
return osCommand.Cmd.New(git_commands.NewGitCmd("rev-parse").Arg("--git-dir").ToArgv()).DontLog().Run()
|
|
}
|