2018-08-12 11:31:27 +02:00
package commands
import (
2019-02-19 14:36:29 +02:00
"io/ioutil"
2018-08-12 11:31:27 +02:00
"os"
2019-11-05 03:42:07 +02:00
"path/filepath"
2018-08-12 11:50:55 +02:00
"strings"
2021-04-05 13:08:33 +02:00
"time"
2019-02-18 12:29:43 +02:00
2019-02-11 12:30:27 +02:00
"github.com/go-errors/errors"
2020-10-06 11:50:54 +02:00
gogit "github.com/jesseduffield/go-git/v5"
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"
2019-02-18 12:29:43 +02:00
"github.com/jesseduffield/lazygit/pkg/config"
2020-09-27 08:17:26 +02:00
"github.com/jesseduffield/lazygit/pkg/env"
2018-08-28 10:01:53 +02:00
"github.com/jesseduffield/lazygit/pkg/i18n"
2018-08-12 13:04:47 +02:00
"github.com/jesseduffield/lazygit/pkg/utils"
2018-08-25 00:51:47 +02:00
"github.com/sirupsen/logrus"
2018-08-12 11:31:27 +02:00
)
2019-11-17 03:07:36 +02:00
// this takes something like:
// * (HEAD detached at 264fc6f5)
// remotes
// and returns '264fc6f5' as the second match
const CurrentBranchNameRegex = ` (?m)^\*.*?([^ ]*?)\)?$ `
2018-09-02 17:15:27 +02:00
// GitCommand is our main git interface
type GitCommand struct {
2019-11-04 10:47:25 +02:00
Log * logrus . Entry
2020-09-29 11:10:57 +02:00
OSCommand * oscommands . OSCommand
2019-11-04 10:47:25 +02:00
Repo * gogit . Repository
2020-10-04 02:00:48 +02:00
Tr * i18n . TranslationSet
2019-11-04 10:47:25 +02:00
Config config . AppConfigurer
DotGitDir string
onSuccessfulContinue func ( ) error
2020-08-15 03:18:40 +02:00
PatchManager * patch . PatchManager
2021-10-23 00:52:19 +02:00
GitConfig git_config . IGitConfig
2020-05-15 13:26:02 +02:00
// Push to current determines whether the user has configured to push to the remote branch of the same name as the current or not
PushToCurrent bool
2018-09-02 17:15:27 +02:00
}
// NewGitCommand it runs git commands
2021-10-23 00:52:19 +02:00
func NewGitCommand (
log * logrus . Entry ,
osCommand * oscommands . OSCommand ,
tr * i18n . TranslationSet ,
config config . AppConfigurer ,
gitConfig git_config . IGitConfig ,
) ( * GitCommand , error ) {
2018-09-02 17:15:27 +02:00
var repo * gogit . Repository
2021-10-23 00:52:19 +02:00
pushToCurrent := gitConfig . Get ( "push.default" ) == "current"
2020-05-15 13:26:02 +02:00
2020-09-27 07:36:04 +02:00
if err := navigateToRepoRootDirectory ( os . Stat , os . Chdir ) ; err != nil {
return nil , err
}
2021-10-23 00:52:19 +02:00
var err error
2020-10-04 02:00:48 +02:00
if repo , err = setupRepository ( gogit . PlainOpen , tr . GitconfigParseErr ) ; err != nil {
2020-09-27 07:36:04 +02:00
return nil , err
2018-09-02 17:15:27 +02:00
}
2019-05-12 09:04:32 +02:00
dotGitDir , err := findDotGitDir ( os . Stat , ioutil . ReadFile )
if err != nil {
return nil , err
}
2019-11-05 09:10:47 +02:00
gitCommand := & GitCommand {
2021-10-23 00:52:19 +02:00
Log : log ,
OSCommand : osCommand ,
Tr : tr ,
Repo : repo ,
Config : config ,
DotGitDir : dotGitDir ,
PushToCurrent : pushToCurrent ,
GitConfig : gitConfig ,
2019-11-05 09:10:47 +02:00
}
2020-08-22 10:29:09 +02:00
gitCommand . PatchManager = patch . NewPatchManager ( log , gitCommand . ApplyPatch , gitCommand . ShowFileDiff )
2019-11-05 09:10:47 +02:00
return gitCommand , nil
2018-09-02 17:15:27 +02:00
}
2021-04-10 08:01:46 +02:00
func ( c * GitCommand ) WithSpan ( span string ) * GitCommand {
2021-04-10 08:25:45 +02:00
// sometimes .WithSpan(span) will be called where span actually is empty, in
// which case we don't need to log anything so we can just return early here
// with the original struct
if span == "" {
return c
}
2021-04-10 08:01:46 +02:00
newGitCommand := & GitCommand { }
* newGitCommand = * c
newGitCommand . OSCommand = c . OSCommand . WithSpan ( span )
2021-04-10 08:05:21 +02:00
// NOTE: unlike the other things here which create shallow clones, this will
// actually update the PatchManager on the original struct to have the new span.
// This means each time we call ApplyPatch in PatchManager, we need to ensure
// we've called .WithSpan() ahead of time with the new span value
2021-04-10 08:01:46 +02:00
newGitCommand . PatchManager . ApplyPatch = newGitCommand . ApplyPatch
2021-04-10 08:05:21 +02:00
2021-04-10 08:01:46 +02:00
return newGitCommand
}
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
}
2020-10-04 02:00:48 +02:00
func setupRepository ( openGitRepository func ( string ) ( * gogit . Repository , error ) , 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
}
2020-09-29 12:03:39 +02:00
repository , err := openGitRepository ( path )
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: " ) {
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 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 {
return osCommand . RunCommand ( "git rev-parse --git-dir" )
}
2021-04-05 13:08:33 +02:00
func ( c * GitCommand ) RunCommand ( formatString string , formatArgs ... interface { } ) error {
_ , err := c . RunCommandWithOutput ( formatString , formatArgs ... )
return err
}
func ( c * GitCommand ) RunCommandWithOutput ( formatString string , formatArgs ... interface { } ) ( string , error ) {
// TODO: have this retry logic in other places we run the command
waitTime := 50 * time . Millisecond
retryCount := 5
attempt := 0
for {
output , err := c . OSCommand . RunCommandWithOutput ( formatString , formatArgs ... )
if err != nil {
// if we have an error based on the index lock, we should wait a bit and then retry
if strings . Contains ( output , ".git/index.lock" ) {
c . Log . Error ( output )
c . Log . Info ( "index.lock prevented command from running. Retrying command after a small wait" )
attempt ++
time . Sleep ( waitTime )
if attempt < retryCount {
continue
}
}
}
return output , err
}
}
2021-10-20 13:21:16 +02:00
func ( c * GitCommand ) NewCmdObjFromStr ( cmdStr string ) oscommands . ICmdObj {
return c . OSCommand . NewCmdObjFromStr ( cmdStr ) . AddEnvVars ( "GIT_OPTIONAL_LOCKS=0" )
}