2018-08-12 11:31:27 +02:00
package commands
import (
"os"
2019-11-05 03:42:07 +02:00
"path/filepath"
2018-08-12 11:50:55 +02:00
"strings"
2019-02-18 12:29:43 +02:00
2019-02-11 12:30:27 +02:00
"github.com/go-errors/errors"
2022-08-07 01:44:50 +02:00
"github.com/sasha-s/go-deadlock"
2019-02-11 12:30:27 +02:00
2020-10-06 11:50:54 +02:00
gogit "github.com/jesseduffield/go-git/v5"
2022-01-08 05:00:36 +02:00
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
2021-10-23 00:52:19 +02:00
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
2020-09-29 11:10:57 +02:00
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
2020-08-15 03:18:40 +02:00
"github.com/jesseduffield/lazygit/pkg/commands/patch"
2021-12-29 02:37:15 +02:00
"github.com/jesseduffield/lazygit/pkg/common"
2020-09-27 08:17:26 +02:00
"github.com/jesseduffield/lazygit/pkg/env"
2018-08-12 13:04:47 +02:00
"github.com/jesseduffield/lazygit/pkg/utils"
2018-08-12 11:31:27 +02:00
)
2022-01-02 01:34:33 +02:00
// GitCommand is our main git interface
type GitCommand struct {
2022-01-08 05:00:36 +02:00
Branch * git_commands . BranchCommands
Commit * git_commands . CommitCommands
Config * git_commands . ConfigCommands
Custom * git_commands . CustomCommands
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
2022-01-19 09:32:27 +02:00
Bisect * git_commands . BisectCommands
2022-01-07 11:36:11 +02:00
Loaders Loaders
2022-01-02 01:34:33 +02:00
}
2019-11-17 03:07:36 +02:00
2022-01-02 01:21:32 +02:00
type Loaders struct {
2022-11-11 04:19:29 +02:00
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
2022-01-02 01:21:32 +02:00
}
2021-10-23 00:52:19 +02:00
func NewGitCommand (
2021-12-29 02:37:15 +02:00
cmn * common . Common ,
2022-12-26 16:43:08 +02:00
version * git_commands . GitVersion ,
2021-10-23 00:52:19 +02:00
osCommand * oscommands . OSCommand ,
gitConfig git_config . IGitConfig ,
2022-08-07 01:44:50 +02:00
syncMutex * deadlock . Mutex ,
2021-10-23 00:52:19 +02:00
) ( * GitCommand , error ) {
2020-09-27 07:36:04 +02:00
if err := navigateToRepoRootDirectory ( os . Stat , os . Chdir ) ; err != nil {
return nil , err
}
2022-04-07 18:45:08 +02:00
repo , err := setupRepository ( gogit . PlainOpenWithOptions , gogit . PlainOpenOptions { DetectDotGit : false , EnableDotGitCommonDir : true } , cmn . Tr . GitconfigParseErr )
2022-01-02 01:34:33 +02:00
if err != nil {
2020-09-27 07:36:04 +02:00
return nil , err
2018-09-02 17:15:27 +02:00
}
2022-09-13 12:11:03 +02:00
dotGitDir , err := findDotGitDir ( os . Stat , os . ReadFile )
2019-05-12 09:04:32 +02:00
if err != nil {
return nil , err
}
2022-01-02 01:34:33 +02:00
return NewGitCommandAux (
cmn ,
2022-12-26 16:43:08 +02:00
version ,
2022-01-02 01:34:33 +02:00
osCommand ,
gitConfig ,
dotGitDir ,
repo ,
2022-01-16 05:46:53 +02:00
syncMutex ,
2022-01-02 01:34:33 +02:00
) , nil
}
2021-12-29 05:33:38 +02:00
2022-01-02 01:34:33 +02:00
func NewGitCommandAux (
cmn * common . Common ,
2022-12-26 16:43:08 +02:00
version * git_commands . GitVersion ,
2022-01-02 01:34:33 +02:00
osCommand * oscommands . OSCommand ,
gitConfig git_config . IGitConfig ,
dotGitDir string ,
repo * gogit . Repository ,
2022-08-07 01:44:50 +02:00
syncMutex * deadlock . Mutex ,
2022-01-02 01:34:33 +02:00
) * GitCommand {
cmd := NewGitCmdObjBuilder ( cmn . Log , osCommand . Cmd )
2019-11-05 09:10:47 +02:00
2022-01-07 05:45:18 +02:00
// 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.
2022-01-18 12:26:21 +02:00
// common ones are: cmn, osCommand, dotGitDir, configCommands
2022-01-08 05:44:07 +02:00
configCommands := git_commands . NewConfigCommands ( cmn , gitConfig , repo )
2022-01-18 12:26:21 +02:00
2022-11-11 04:19:29 +02:00
fileLoader := git_commands . NewFileLoader ( cmn , cmd , configCommands )
2022-12-26 16:43:08 +02:00
gitCommon := git_commands . NewGitCommon ( cmn , version , cmd , osCommand , dotGitDir , repo , configCommands , syncMutex )
2022-01-18 12:26:21 +02:00
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 )
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 )
2022-01-02 01:34:33 +02:00
// TODO: have patch manager take workingTreeCommands in its entirety
2023-02-03 21:20:20 +02:00
patchManager := patch . NewPatchManager ( cmn . Log , workingTreeCommands . ApplyPatch ,
func ( from string , to string , reverse bool , filename string , plain bool ) ( string , error ) {
// TODO: make patch manager take Gui.IgnoreWhitespaceInDiffView into
// account. For now we just pass false.
return workingTreeCommands . ShowFileDiff ( from , to , reverse , filename , plain , false )
} )
2022-01-18 12:26:21 +02:00
patchCommands := git_commands . NewPatchCommands ( gitCommon , rebaseCommands , commitCommands , statusCommands , stashCommands , patchManager )
2022-01-19 09:32:27 +02:00
bisectCommands := git_commands . NewBisectCommands ( gitCommon )
2022-01-02 01:34:33 +02:00
2022-10-16 14:31:42 +02:00
branchLoader := git_commands . NewBranchLoader ( cmn , branchCommands . GetRawBranches , branchCommands . CurrentBranchInfo , configCommands )
2022-11-11 04:19:29 +02:00
commitFileLoader := git_commands . NewCommitFileLoader ( cmn , cmd )
2022-10-16 14:31:42 +02:00
commitLoader := git_commands . NewCommitLoader ( cmn , cmd , dotGitDir , branchCommands . CurrentBranchInfo , statusCommands . RebaseMode )
2022-11-11 04:19:29 +02:00
reflogCommitLoader := git_commands . NewReflogCommitLoader ( cmn , cmd )
remoteLoader := git_commands . NewRemoteLoader ( cmn , cmd , repo . Remotes )
stashLoader := git_commands . NewStashLoader ( cmn , cmd )
2023-02-19 12:36:06 +02:00
tagLoader := git_commands . NewTagLoader ( cmn , cmd )
2022-11-11 04:19:29 +02:00
2022-01-02 01:34:33 +02:00
return & GitCommand {
Branch : branchCommands ,
Commit : commitCommands ,
Config : configCommands ,
2022-01-07 11:36:11 +02:00
Custom : customCommands ,
File : fileCommands ,
Flow : flowCommands ,
2022-01-02 01:34:33 +02:00
Patch : patchCommands ,
2022-01-07 11:36:11 +02:00
Rebase : rebaseCommands ,
2022-01-02 01:34:33 +02:00
Remote : remoteCommands ,
2022-01-07 11:36:11 +02:00
Stash : stashCommands ,
Status : statusCommands ,
Submodule : submoduleCommands ,
2022-01-02 01:34:33 +02:00
Sync : syncCommands ,
2022-01-07 11:36:11 +02:00
Tag : tagCommands ,
2022-01-19 09:32:27 +02:00
Bisect : bisectCommands ,
2022-01-07 11:36:11 +02:00
WorkingTree : workingTreeCommands ,
2022-01-02 01:34:33 +02:00
Loaders : Loaders {
2022-11-11 04:19:29 +02:00
BranchLoader : branchLoader ,
CommitFileLoader : commitFileLoader ,
CommitLoader : commitLoader ,
FileLoader : fileLoader ,
ReflogCommitLoader : reflogCommitLoader ,
RemoteLoader : remoteLoader ,
StashLoader : stashLoader ,
TagLoader : tagLoader ,
2022-01-02 01:34:33 +02:00
} ,
2022-01-02 01:21:32 +02:00
}
2018-09-02 17:15:27 +02:00
}
2020-09-29 12:03:39 +02:00
func navigateToRepoRootDirectory ( stat func ( string ) ( os . FileInfo , error ) , chdir func ( string ) error ) error {
gitDir := env . GetGitDirEnv ( )
if gitDir != "" {
// we've been given the git directory explicitly so no need to navigate to it
_ , err := stat ( gitDir )
if err != nil {
return utils . WrapError ( err )
2020-09-29 00:47:14 +02:00
}
2018-08-12 11:50:55 +02:00
2020-09-29 12:03:39 +02:00
return nil
2018-11-29 18:57:28 +02:00
}
2020-09-29 12:03:39 +02:00
// we haven't been given the git dir explicitly so we assume it's in the current working directory as `.git/` (or an ancestor directory)
2020-08-11 13:18:38 +02:00
2020-09-29 12:03:39 +02:00
for {
_ , err := stat ( ".git" )
2020-08-11 13:18:38 +02:00
2020-09-29 12:03:39 +02:00
if err == nil {
return nil
2018-11-25 14:15:36 +02:00
}
2018-08-12 11:50:55 +02:00
2020-09-29 12:03:39 +02:00
if ! os . IsNotExist ( err ) {
return utils . WrapError ( err )
2020-03-26 11:29:35 +02:00
}
2018-09-10 21:57:08 +02:00
2020-09-29 12:03:39 +02:00
if err = chdir ( ".." ) ; err != nil {
return utils . WrapError ( err )
2019-02-20 10:47:01 +02:00
}
2021-03-30 13:17:42 +02:00
currentPath , err := os . Getwd ( )
if err != nil {
return err
}
atRoot := currentPath == filepath . Dir ( currentPath )
if atRoot {
// we should never really land here: the code that creates GitCommand should
// verify we're in a git directory
return errors . New ( "Must open lazygit in a git repository" )
}
2019-02-20 10:47:01 +02:00
}
2018-08-12 12:22:20 +02:00
}
2020-09-29 12:03:39 +02:00
// resolvePath takes a path containing a symlink and returns the true path
func resolvePath ( path string ) ( string , error ) {
l , err := os . Lstat ( path )
2019-03-02 11:00:26 +02:00
if err != nil {
return "" , err
}
2020-08-07 09:52:17 +02:00
2020-09-29 12:03:39 +02:00
if l . Mode ( ) & os . ModeSymlink == 0 {
return path , nil
2020-08-07 09:52:17 +02:00
}
2020-09-29 12:03:39 +02:00
return filepath . EvalSymlinks ( path )
2020-08-07 09:52:17 +02:00
}
2022-04-07 18:45:08 +02:00
func setupRepository ( openGitRepository func ( string , * gogit . PlainOpenOptions ) ( * gogit . Repository , error ) , options gogit . PlainOpenOptions , gitConfigParseErrorStr string ) ( * gogit . Repository , error ) {
2020-09-29 12:03:39 +02:00
unresolvedPath := env . GetGitDirEnv ( )
if unresolvedPath == "" {
var err error
unresolvedPath , err = os . Getwd ( )
2020-08-07 09:52:17 +02:00
if err != nil {
2020-09-29 12:03:39 +02:00
return nil , err
2020-03-28 03:52:45 +02:00
}
2019-11-04 10:47:25 +02:00
}
2020-09-29 12:03:39 +02:00
path , err := resolvePath ( unresolvedPath )
2019-02-18 12:29:43 +02:00
if err != nil {
2019-02-18 14:27:54 +02:00
return nil , err
2019-02-18 12:29:43 +02:00
}
2022-04-07 18:45:08 +02:00
repository , err := openGitRepository ( path , & options )
2019-02-24 04:51:52 +02:00
if err != nil {
2020-09-29 12:03:39 +02:00
if strings . Contains ( err . Error ( ) , ` unquoted '\' must be followed by new line ` ) {
2020-10-04 02:00:48 +02:00
return nil , errors . New ( gitConfigParseErrorStr )
2019-03-11 04:04:08 +02:00
}
2020-09-29 12:03:39 +02:00
return nil , err
2019-05-30 14:45:56 +02:00
}
2020-09-29 12:03:39 +02:00
return repository , err
2019-05-30 14:45:56 +02:00
}
2019-11-04 10:47:25 +02:00
2020-09-29 12:03:39 +02:00
func findDotGitDir ( stat func ( string ) ( os . FileInfo , error ) , readFile func ( filename string ) ( [ ] byte , error ) ) ( string , error ) {
if env . GetGitDirEnv ( ) != "" {
return env . GetGitDirEnv ( ) , nil
2019-11-04 10:47:25 +02:00
}
2020-09-29 12:03:39 +02:00
f , err := stat ( ".git" )
2019-11-04 10:47:25 +02:00
if err != nil {
2020-09-29 12:03:39 +02:00
return "" , err
2019-11-04 10:47:25 +02:00
}
2020-09-29 12:03:39 +02:00
if f . IsDir ( ) {
return ".git" , nil
2019-11-04 10:47:25 +02:00
}
2020-09-29 12:03:39 +02:00
fileBytes , err := readFile ( ".git" )
2020-03-01 12:00:44 +02:00
if err != nil {
2020-09-29 12:03:39 +02:00
return "" , err
2020-03-28 03:08:13 +02:00
}
2020-09-29 12:03:39 +02:00
fileContent := string ( fileBytes )
if ! strings . HasPrefix ( fileContent , "gitdir: " ) {
2022-04-07 18:45:08 +02:00
return "" , errors . New ( ".git is a file which suggests we are in a submodule or a worktree but the file's contents do not contain a gitdir pointing to the actual .git directory" )
2020-03-28 03:08:13 +02:00
}
2020-09-29 12:03:39 +02:00
return strings . TrimSpace ( strings . TrimPrefix ( fileContent , "gitdir: " ) ) , nil
2020-09-27 08:02:20 +02:00
}
2021-03-30 13:17:42 +02:00
func VerifyInGitRepo ( osCommand * oscommands . OSCommand ) error {
2022-01-05 02:57:32 +02:00
return osCommand . Cmd . New ( "git rev-parse --git-dir" ) . DontLog ( ) . Run ( )
2021-10-20 13:21:16 +02:00
}