2018-08-12 19:31:27 +10:00
package commands
import (
2019-02-19 23:36:29 +11:00
"io/ioutil"
2018-08-12 19:31:27 +10:00
"os"
2019-11-05 12:42:07 +11:00
"path/filepath"
2018-08-12 19:50:55 +10:00
"strings"
2019-02-18 21:29:43 +11:00
2019-02-11 21:30:27 +11:00
"github.com/go-errors/errors"
2020-10-06 20:50:54 +11:00
gogit "github.com/jesseduffield/go-git/v5"
2021-10-23 09:52:19 +11:00
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
2022-01-02 10:21:32 +11:00
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
2020-09-29 19:10:57 +10:00
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
2020-08-15 11:18:40 +10:00
"github.com/jesseduffield/lazygit/pkg/commands/patch"
2021-12-29 11:37:15 +11:00
"github.com/jesseduffield/lazygit/pkg/common"
2020-09-27 16:17:26 +10:00
"github.com/jesseduffield/lazygit/pkg/env"
2018-08-12 21:04:47 +10:00
"github.com/jesseduffield/lazygit/pkg/utils"
2018-08-12 19:31:27 +10:00
)
2022-01-02 10:34:33 +11:00
// GitCommand is our main git interface
type GitCommand struct {
* common . Common
Repo * gogit . Repository
Loaders Loaders
Cmd oscommands . ICmdObjBuilder
Submodule * SubmoduleCommands
Tag * TagCommands
WorkingTree * WorkingTreeCommands
File * FileCommands
Branch * BranchCommands
Commit * CommitCommands
Rebase * RebaseCommands
Stash * StashCommands
Status * StatusCommands
Config * ConfigCommands
Patch * PatchCommands
Remote * RemoteCommands
Sync * SyncCommands
}
2019-11-17 12:07:36 +11:00
2022-01-02 10:21:32 +11:00
type Loaders struct {
Commits * loaders . CommitLoader
Branches * loaders . BranchLoader
Files * loaders . FileLoader
CommitFiles * loaders . CommitFileLoader
Remotes * loaders . RemoteLoader
ReflogCommits * loaders . ReflogCommitLoader
Stash * loaders . StashLoader
Tags * loaders . TagLoader
}
2021-10-23 09:52:19 +11:00
func NewGitCommand (
2021-12-29 11:37:15 +11:00
cmn * common . Common ,
2021-10-23 09:52:19 +11:00
osCommand * oscommands . OSCommand ,
gitConfig git_config . IGitConfig ,
) ( * GitCommand , error ) {
2020-09-27 15:36:04 +10:00
if err := navigateToRepoRootDirectory ( os . Stat , os . Chdir ) ; err != nil {
return nil , err
}
2022-01-02 10:34:33 +11:00
repo , err := setupRepository ( gogit . PlainOpen , cmn . Tr . GitconfigParseErr )
if err != nil {
2020-09-27 15:36:04 +10:00
return nil , err
2018-09-02 17:15:27 +02:00
}
2019-05-12 17:04:32 +10:00
dotGitDir , err := findDotGitDir ( os . Stat , ioutil . ReadFile )
if err != nil {
return nil , err
}
2022-01-02 10:34:33 +11:00
return NewGitCommandAux (
cmn ,
osCommand ,
gitConfig ,
dotGitDir ,
repo ,
) , nil
}
2021-12-29 14:33:38 +11:00
2022-01-02 10:34:33 +11:00
func NewGitCommandAux (
cmn * common . Common ,
osCommand * oscommands . OSCommand ,
gitConfig git_config . IGitConfig ,
dotGitDir string ,
repo * gogit . Repository ,
) * GitCommand {
cmd := NewGitCmdObjBuilder ( cmn . Log , osCommand . Cmd )
2019-11-05 18:10:47 +11:00
2022-01-07 14:45:18 +11: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-02 10:34:33 +11:00
configCommands := NewConfigCommands ( cmn , gitConfig )
statusCommands := NewStatusCommands ( cmn , osCommand , repo , dotGitDir )
fileLoader := loaders . NewFileLoader ( cmn , cmd , configCommands )
remoteCommands := NewRemoteCommands ( cmn , cmd )
branchCommands := NewBranchCommands ( cmn , cmd )
syncCommands := NewSyncCommands ( cmn , cmd )
tagCommands := NewTagCommands ( cmn , cmd )
commitCommands := NewCommitCommands ( cmn , cmd )
fileCommands := NewFileCommands ( cmn , cmd , configCommands , osCommand )
submoduleCommands := NewSubmoduleCommands ( cmn , cmd , dotGitDir )
workingTreeCommands := NewWorkingTreeCommands ( cmn , cmd , submoduleCommands , osCommand , fileLoader )
rebaseCommands := NewRebaseCommands (
cmn ,
cmd ,
osCommand ,
commitCommands ,
workingTreeCommands ,
configCommands ,
dotGitDir ,
)
stashCommands := NewStashCommands ( cmn , cmd , osCommand , fileLoader , workingTreeCommands )
// TODO: have patch manager take workingTreeCommands in its entirety
patchManager := patch . NewPatchManager ( cmn . Log , workingTreeCommands . ApplyPatch , workingTreeCommands . ShowFileDiff )
patchCommands := NewPatchCommands ( cmn , cmd , rebaseCommands , commitCommands , configCommands , statusCommands , patchManager )
return & GitCommand {
2022-01-07 15:01:07 +11:00
Common : cmn ,
2022-01-02 10:34:33 +11:00
Repo : repo ,
Cmd : cmd ,
Submodule : submoduleCommands ,
Tag : tagCommands ,
WorkingTree : workingTreeCommands ,
File : fileCommands ,
Branch : branchCommands ,
Commit : commitCommands ,
Rebase : rebaseCommands ,
Config : configCommands ,
Stash : stashCommands ,
Status : statusCommands ,
Patch : patchCommands ,
Remote : remoteCommands ,
Sync : syncCommands ,
Loaders : Loaders {
Commits : loaders . NewCommitLoader ( cmn , cmd , dotGitDir , branchCommands . CurrentBranchName , statusCommands . RebaseMode ) ,
Branches : loaders . NewBranchLoader ( cmn , branchCommands . GetRawBranches , branchCommands . CurrentBranchName ) ,
Files : fileLoader ,
CommitFiles : loaders . NewCommitFileLoader ( cmn , cmd ) ,
Remotes : loaders . NewRemoteLoader ( cmn , cmd , repo . Remotes ) ,
ReflogCommits : loaders . NewReflogCommitLoader ( cmn , cmd ) ,
Stash : loaders . NewStashLoader ( cmn , cmd ) ,
Tags : loaders . NewTagLoader ( cmn , cmd ) ,
} ,
2022-01-02 10:21:32 +11:00
}
2018-09-02 17:15:27 +02:00
}
2020-09-29 20:03:39 +10: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 08:47:14 +10:00
}
2018-08-12 19:50:55 +10:00
2020-09-29 20:03:39 +10:00
return nil
2018-11-29 17:57:28 +01:00
}
2020-09-29 20:03:39 +10: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 21:18:38 +10:00
2020-09-29 20:03:39 +10:00
for {
_ , err := stat ( ".git" )
2020-08-11 21:18:38 +10:00
2020-09-29 20:03:39 +10:00
if err == nil {
return nil
2018-11-25 13:15:36 +01:00
}
2018-08-12 19:50:55 +10:00
2020-09-29 20:03:39 +10:00
if ! os . IsNotExist ( err ) {
return utils . WrapError ( err )
2020-03-26 20:29:35 +11:00
}
2018-09-10 21:57:08 +02:00
2020-09-29 20:03:39 +10:00
if err = chdir ( ".." ) ; err != nil {
return utils . WrapError ( err )
2019-02-20 19:47:01 +11:00
}
2021-03-30 22:17:42 +11: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 19:47:01 +11:00
}
2018-08-12 20:22:20 +10:00
}
2020-09-29 20:03:39 +10: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 20:00:26 +11:00
if err != nil {
return "" , err
}
2020-08-07 17:52:17 +10:00
2020-09-29 20:03:39 +10:00
if l . Mode ( ) & os . ModeSymlink == 0 {
return path , nil
2020-08-07 17:52:17 +10:00
}
2020-09-29 20:03:39 +10:00
return filepath . EvalSymlinks ( path )
2020-08-07 17:52:17 +10:00
}
2020-10-04 11:00:48 +11:00
func setupRepository ( openGitRepository func ( string ) ( * gogit . Repository , error ) , gitConfigParseErrorStr string ) ( * gogit . Repository , error ) {
2020-09-29 20:03:39 +10:00
unresolvedPath := env . GetGitDirEnv ( )
if unresolvedPath == "" {
var err error
unresolvedPath , err = os . Getwd ( )
2020-08-07 17:52:17 +10:00
if err != nil {
2020-09-29 20:03:39 +10:00
return nil , err
2020-03-28 12:52:45 +11:00
}
2019-11-04 19:47:25 +11:00
}
2020-09-29 20:03:39 +10:00
path , err := resolvePath ( unresolvedPath )
2019-02-18 21:29:43 +11:00
if err != nil {
2019-02-18 23:27:54 +11:00
return nil , err
2019-02-18 21:29:43 +11:00
}
2020-09-29 20:03:39 +10:00
repository , err := openGitRepository ( path )
2019-02-24 13:51:52 +11:00
if err != nil {
2020-09-29 20:03:39 +10:00
if strings . Contains ( err . Error ( ) , ` unquoted '\' must be followed by new line ` ) {
2020-10-04 11:00:48 +11:00
return nil , errors . New ( gitConfigParseErrorStr )
2019-03-11 13:04:08 +11:00
}
2020-09-29 20:03:39 +10:00
return nil , err
2019-05-30 22:45:56 +10:00
}
2020-09-29 20:03:39 +10:00
return repository , err
2019-05-30 22:45:56 +10:00
}
2019-11-04 19:47:25 +11:00
2020-09-29 20:03:39 +10: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 19:47:25 +11:00
}
2020-09-29 20:03:39 +10:00
f , err := stat ( ".git" )
2019-11-04 19:47:25 +11:00
if err != nil {
2020-09-29 20:03:39 +10:00
return "" , err
2019-11-04 19:47:25 +11:00
}
2020-09-29 20:03:39 +10:00
if f . IsDir ( ) {
return ".git" , nil
2019-11-04 19:47:25 +11:00
}
2020-09-29 20:03:39 +10:00
fileBytes , err := readFile ( ".git" )
2020-03-01 21:00:44 +11:00
if err != nil {
2020-09-29 20:03:39 +10:00
return "" , err
2020-03-28 12:08:13 +11:00
}
2020-09-29 20:03:39 +10:00
fileContent := string ( fileBytes )
if ! strings . HasPrefix ( fileContent , "gitdir: " ) {
return "" , errors . New ( ".git is a file which suggests we are in a submodule but the file's contents do not contain a gitdir pointing to the actual .git directory" )
2020-03-28 12:08:13 +11:00
}
2020-09-29 20:03:39 +10:00
return strings . TrimSpace ( strings . TrimPrefix ( fileContent , "gitdir: " ) ) , nil
2020-09-27 16:02:20 +10:00
}
2021-03-30 22:17:42 +11:00
func VerifyInGitRepo ( osCommand * oscommands . OSCommand ) error {
2022-01-05 11:57:32 +11:00
return osCommand . Cmd . New ( "git rev-parse --git-dir" ) . DontLog ( ) . Run ( )
2021-10-20 22:21:16 +11:00
}