2018-08-12 11:31:27 +02:00
package commands
import (
"fmt"
2019-02-19 14:36:29 +02:00
"io/ioutil"
2018-08-12 11:31:27 +02:00
"os"
2018-08-12 12:22:20 +02:00
"os/exec"
2019-11-05 03:42:07 +02:00
"path/filepath"
2019-11-17 03:07:36 +02:00
"regexp"
2018-08-12 11:50:55 +02:00
"strings"
2019-11-05 03:42:07 +02:00
"time"
2018-08-12 11:31:27 +02:00
2019-02-18 12:29:43 +02:00
"github.com/mgutz/str"
2019-02-11 12:30:27 +02:00
"github.com/go-errors/errors"
2019-02-18 12:29:43 +02:00
"github.com/jesseduffield/lazygit/pkg/config"
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:50:55 +02:00
gitconfig "github.com/tcnksm/go-gitconfig"
gogit "gopkg.in/src-d/go-git.v4"
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-04 06:16:19 +02:00
func verifyInGitRepo ( runCmd func ( string ) error ) error {
return runCmd ( "git status" )
2018-08-29 22:55:57 +02:00
}
2018-09-02 17:15:27 +02:00
func navigateToRepoRootDirectory ( stat func ( string ) ( os . FileInfo , error ) , chdir func ( string ) error ) error {
2018-08-29 22:55:57 +02:00
for {
2019-05-12 09:04:32 +02:00
_ , err := stat ( ".git" )
2018-08-29 22:55:57 +02:00
2019-05-03 09:02:57 +02:00
if err == nil {
2019-05-12 09:04:32 +02:00
return nil
2018-08-29 22:55:57 +02:00
}
2018-09-02 17:15:27 +02:00
if ! os . IsNotExist ( err ) {
2019-03-11 04:04:08 +02:00
return WrapError ( err )
2018-09-02 17:15:27 +02:00
}
2018-08-29 22:55:57 +02:00
2018-09-02 17:15:27 +02:00
if err = chdir ( ".." ) ; err != nil {
2019-03-11 04:04:08 +02:00
return WrapError ( err )
2018-08-29 22:55:57 +02:00
}
}
}
2018-09-02 17:15:27 +02:00
func setupRepositoryAndWorktree ( openGitRepository func ( string ) ( * gogit . Repository , error ) , sLocalize func ( string ) string ) ( repository * gogit . Repository , worktree * gogit . Worktree , err error ) {
repository , err = openGitRepository ( "." )
if err != nil {
if strings . Contains ( err . Error ( ) , ` unquoted '\' must be followed by new line ` ) {
return nil , nil , errors . New ( sLocalize ( "GitconfigParseErr" ) )
}
2018-08-29 22:55:57 +02:00
return
}
2018-09-02 17:15:27 +02:00
worktree , err = repository . Worktree ( )
if err != nil {
return
2018-08-29 22:55:57 +02:00
}
return
}
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
OSCommand * OSCommand
Worktree * gogit . Worktree
Repo * gogit . Repository
Tr * i18n . Localizer
Config config . AppConfigurer
getGlobalGitConfig func ( string ) ( string , error )
getLocalGitConfig func ( string ) ( string , error )
removeFile func ( string ) error
DotGitDir string
onSuccessfulContinue func ( ) error
2019-11-05 02:55:04 +02:00
PatchManager * PatchManager
2018-09-02 17:15:27 +02:00
}
// NewGitCommand it runs git commands
2019-02-18 12:29:43 +02:00
func NewGitCommand ( log * logrus . Entry , osCommand * OSCommand , tr * i18n . Localizer , config config . AppConfigurer ) ( * GitCommand , error ) {
2018-09-02 17:15:27 +02:00
var worktree * gogit . Worktree
var repo * gogit . Repository
fs := [ ] func ( ) error {
func ( ) error {
2018-09-04 06:16:19 +02:00
return verifyInGitRepo ( osCommand . RunCommand )
2018-09-02 17:15:27 +02:00
} ,
func ( ) error {
return navigateToRepoRootDirectory ( os . Stat , os . Chdir )
} ,
func ( ) error {
var err error
repo , worktree , err = setupRepositoryAndWorktree ( gogit . PlainOpen , tr . SLocalize )
return err
} ,
}
for _ , f := range fs {
if err := f ( ) ; err != nil {
return nil , err
}
}
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 {
2018-09-10 21:57:08 +02:00
Log : log ,
OSCommand : osCommand ,
Tr : tr ,
Worktree : worktree ,
Repo : repo ,
2019-02-18 12:29:43 +02:00
Config : config ,
2018-09-10 21:57:08 +02:00
getGlobalGitConfig : gitconfig . Global ,
getLocalGitConfig : gitconfig . Local ,
2018-09-20 01:48:56 +02:00
removeFile : os . RemoveAll ,
2019-05-12 09:04:32 +02:00
DotGitDir : dotGitDir ,
2019-11-05 09:10:47 +02:00
}
gitCommand . PatchManager = NewPatchManager ( log , gitCommand . ApplyPatch )
return gitCommand , nil
2018-09-02 17:15:27 +02:00
}
2019-05-12 09:04:32 +02:00
func findDotGitDir ( stat func ( string ) ( os . FileInfo , error ) , readFile func ( filename string ) ( [ ] byte , error ) ) ( string , error ) {
f , err := stat ( ".git" )
if err != nil {
return "" , err
}
if f . IsDir ( ) {
return ".git" , nil
}
fileBytes , err := readFile ( ".git" )
if err != nil {
return "" , err
}
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" )
}
return strings . TrimSpace ( strings . TrimPrefix ( fileContent , "gitdir: " ) ) , nil
}
2019-05-22 15:34:29 +02:00
// GetStashEntries stash entries
2018-09-17 13:02:30 +02:00
func ( c * GitCommand ) GetStashEntries ( ) [ ] * StashEntry {
2018-08-14 09:47:33 +02:00
rawString , _ := c . OSCommand . RunCommandWithOutput ( "git stash list --pretty='%gs'" )
2018-09-17 13:02:30 +02:00
stashEntries := [ ] * StashEntry { }
2018-08-12 13:04:47 +02:00
for i , line := range utils . SplitLines ( rawString ) {
2018-08-12 12:22:20 +02:00
stashEntries = append ( stashEntries , stashEntryFromLine ( line , i ) )
}
return stashEntries
}
2018-09-17 13:02:30 +02:00
func stashEntryFromLine ( line string , index int ) * StashEntry {
return & StashEntry {
2018-08-12 12:22:20 +02:00
Name : line ,
Index : index ,
DisplayString : line ,
}
}
// GetStashEntryDiff stash diff
func ( c * GitCommand ) GetStashEntryDiff ( index int ) ( string , error ) {
2018-08-14 09:47:33 +02:00
return c . OSCommand . RunCommandWithOutput ( "git stash show -p --color stash@{" + fmt . Sprint ( index ) + "}" )
2018-08-12 12:22:20 +02:00
}
// GetStatusFiles git status files
2018-09-17 13:02:30 +02:00
func ( c * GitCommand ) GetStatusFiles ( ) [ ] * File {
2018-08-12 13:04:47 +02:00
statusOutput , _ := c . GitStatus ( )
statusStrings := utils . SplitLines ( statusOutput )
2018-09-17 13:02:30 +02:00
files := [ ] * File { }
2018-08-12 12:22:20 +02:00
for _ , statusString := range statusStrings {
change := statusString [ 0 : 2 ]
stagedChange := change [ 0 : 1 ]
unstagedChange := statusString [ 1 : 2 ]
2018-08-28 11:12:35 +02:00
filename := c . OSCommand . Unquote ( statusString [ 3 : ] )
2018-09-06 23:16:56 +02:00
_ , untracked := map [ string ] bool { "??" : true , "A " : true , "AM" : true } [ change ]
2018-09-09 20:08:46 +02:00
_ , hasNoStagedChanges := map [ string ] bool { " " : true , "U" : true , "?" : true } [ stagedChange ]
2019-07-13 15:50:52 +02:00
hasMergeConflicts := change == "UU" || change == "AA" || change == "DU"
hasInlineMergeConflicts := change == "UU" || change == "AA"
2018-09-06 23:16:56 +02:00
2018-09-17 13:02:30 +02:00
file := & File {
2019-03-03 06:55:19 +02:00
Name : filename ,
DisplayString : statusString ,
HasStagedChanges : ! hasNoStagedChanges ,
HasUnstagedChanges : unstagedChange != " " ,
Tracked : ! untracked ,
Deleted : unstagedChange == "D" || stagedChange == "D" ,
2019-07-13 15:50:52 +02:00
HasMergeConflicts : hasMergeConflicts ,
HasInlineMergeConflicts : hasInlineMergeConflicts ,
2019-03-03 06:55:19 +02:00
Type : c . OSCommand . FileType ( filename ) ,
2019-05-30 14:45:56 +02:00
ShortStatus : change ,
2018-08-12 12:22:20 +02:00
}
2018-08-13 12:26:02 +02:00
files = append ( files , file )
2018-08-12 12:22:20 +02:00
}
2018-08-13 12:26:02 +02:00
return files
2018-08-12 12:22:20 +02:00
}
// StashDo modify stash
2018-08-14 09:47:33 +02:00
func ( c * GitCommand ) StashDo ( index int , method string ) error {
2018-08-28 20:18:34 +02:00
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git stash %s stash@{%d}" , method , index ) )
2018-08-12 12:22:20 +02:00
}
// StashSave save stash
2018-08-14 09:47:33 +02:00
// TODO: before calling this, check if there is anything to save
func ( c * GitCommand ) StashSave ( message string ) error {
2018-08-28 20:24:19 +02:00
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git stash save %s" , c . OSCommand . Quote ( message ) ) )
2018-08-12 12:22:20 +02:00
}
// MergeStatusFiles merge status files
2018-09-17 13:02:30 +02:00
func ( c * GitCommand ) MergeStatusFiles ( oldFiles , newFiles [ ] * File ) [ ] * File {
2018-08-13 12:26:02 +02:00
if len ( oldFiles ) == 0 {
return newFiles
2018-08-12 12:22:20 +02:00
}
2018-09-12 10:24:03 +02:00
appendedIndexes := [ ] int { }
2018-08-12 12:22:20 +02:00
2018-09-12 10:24:03 +02:00
// retain position of files we already could see
2018-09-17 13:02:30 +02:00
result := [ ] * File { }
2018-09-12 10:24:03 +02:00
for _ , oldFile := range oldFiles {
for newIndex , newFile := range newFiles {
2018-08-13 12:26:02 +02:00
if oldFile . Name == newFile . Name {
2018-09-12 10:24:03 +02:00
result = append ( result , newFile )
appendedIndexes = append ( appendedIndexes , newIndex )
2018-08-12 12:22:20 +02:00
break
}
}
2018-09-12 10:24:03 +02:00
}
2018-08-12 12:22:20 +02:00
2018-09-12 10:24:03 +02:00
// append any new files to the end
for index , newFile := range newFiles {
if ! includesInt ( appendedIndexes , index ) {
result = append ( result , newFile )
2018-08-12 12:22:20 +02:00
}
}
2018-09-12 10:24:03 +02:00
return result
}
func includesInt ( list [ ] int , a int ) bool {
for _ , b := range list {
if b == a {
return true
}
}
return false
2018-08-12 12:22:20 +02:00
}
2018-12-05 11:06:47 +02:00
// ResetAndClean removes all unstaged changes and removes all untracked files
func ( c * GitCommand ) ResetAndClean ( ) error {
2019-03-18 12:19:07 +02:00
if err := c . ResetHardHead ( ) ; err != nil {
2018-12-05 11:06:47 +02:00
return err
}
2019-03-18 12:19:07 +02:00
return c . RemoveUntrackedFiles ( )
2018-08-12 11:50:55 +02:00
}
2018-12-07 09:52:31 +02:00
func ( c * GitCommand ) GetCurrentBranchUpstreamDifferenceCount ( ) ( string , string ) {
2019-11-17 05:28:38 +02:00
return c . GetCommitDifferences ( "HEAD" , "HEAD@{u}" )
2018-12-07 09:52:31 +02:00
}
func ( c * GitCommand ) GetBranchUpstreamDifferenceCount ( branchName string ) ( string , string ) {
2019-11-17 05:28:38 +02:00
return c . GetCommitDifferences ( branchName , branchName + "@{u}" )
2018-12-07 09:52:31 +02:00
}
// GetCommitDifferences checks how many pushables/pullables there are for the
2018-08-12 11:50:55 +02:00
// current branch
2018-12-07 09:52:31 +02:00
func ( c * GitCommand ) GetCommitDifferences ( from , to string ) ( string , string ) {
command := "git rev-list %s..%s --count"
pushableCount , err := c . OSCommand . RunCommandWithOutput ( fmt . Sprintf ( command , to , from ) )
2018-08-12 11:50:55 +02:00
if err != nil {
return "?" , "?"
}
2018-12-07 09:52:31 +02:00
pullableCount , err := c . OSCommand . RunCommandWithOutput ( fmt . Sprintf ( command , from , to ) )
2018-08-12 11:50:55 +02:00
if err != nil {
return "?" , "?"
}
return strings . TrimSpace ( pushableCount ) , strings . TrimSpace ( pullableCount )
}
// RenameCommit renames the topmost commit with the given name
2018-08-14 09:47:33 +02:00
func ( c * GitCommand ) RenameCommit ( name string ) error {
2018-09-06 23:38:49 +02:00
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git commit --allow-empty --amend -m %s" , c . OSCommand . Quote ( name ) ) )
2018-08-12 11:50:55 +02:00
}
2019-02-24 00:42:24 +02:00
// RebaseBranch interactive rebases onto a branch
func ( c * GitCommand ) RebaseBranch ( branchName string ) error {
cmd , err := c . PrepareInteractiveRebaseCommand ( branchName , "" , false )
2018-11-29 18:57:28 +02:00
if err != nil {
return err
}
2019-02-24 00:42:24 +02:00
return c . OSCommand . RunPreparedCommand ( cmd )
2018-11-29 18:57:28 +02:00
}
2018-08-12 12:22:20 +02:00
// Fetch fetch git repo
2018-12-09 14:04:19 +02:00
func ( c * GitCommand ) Fetch ( unamePassQuestion func ( string ) string , canAskForCredentials bool ) error {
2018-12-02 15:58:18 +02:00
return c . OSCommand . DetectUnamePass ( "git fetch" , func ( question string ) string {
2018-12-09 14:04:19 +02:00
if canAskForCredentials {
2018-12-02 15:58:18 +02:00
return unamePassQuestion ( question )
2018-11-25 14:15:36 +02:00
}
2018-12-18 13:23:17 +02:00
return "\n"
2018-11-25 14:15:36 +02:00
} )
2018-08-12 11:50:55 +02:00
}
2018-08-12 12:22:20 +02:00
// ResetToCommit reset to commit
2019-04-02 10:53:16 +02:00
func ( c * GitCommand ) ResetToCommit ( sha string , strength string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git reset --%s %s" , strength , sha ) )
2018-08-12 11:50:55 +02:00
}
2018-08-12 12:22:20 +02:00
// NewBranch create new branch
2018-08-14 09:47:33 +02:00
func ( c * GitCommand ) NewBranch ( name string ) error {
2018-09-06 23:38:49 +02:00
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git checkout -b %s" , name ) )
2018-08-12 11:50:55 +02:00
}
2018-11-30 02:47:14 +02:00
// CurrentBranchName is a function.
2018-09-25 12:11:33 +02:00
func ( c * GitCommand ) CurrentBranchName ( ) ( string , error ) {
2018-11-14 10:08:42 +02:00
branchName , err := c . OSCommand . RunCommandWithOutput ( "git symbolic-ref --short HEAD" )
2019-11-17 03:07:36 +02:00
if err != nil || branchName == "HEAD\n" {
output , err := c . OSCommand . RunCommandWithOutput ( "git branch --contains" )
re := regexp . MustCompile ( CurrentBranchNameRegex )
match := re . FindStringSubmatch ( output )
branchName = match [ 1 ]
2018-11-14 10:08:42 +02:00
if err != nil {
return "" , err
}
2018-09-25 12:31:19 +02:00
}
2018-11-14 10:08:42 +02:00
return utils . TrimTrailingNewline ( branchName ) , nil
2018-09-25 12:11:33 +02:00
}
2018-08-12 12:22:20 +02:00
// DeleteBranch delete branch
2018-08-15 15:02:01 +02:00
func ( c * GitCommand ) DeleteBranch ( branch string , force bool ) error {
2018-09-06 23:38:49 +02:00
command := "git branch -d"
2018-08-15 15:02:01 +02:00
if force {
2018-09-06 23:38:49 +02:00
command = "git branch -D"
2018-08-15 15:02:01 +02:00
}
2018-09-06 23:38:49 +02:00
return c . OSCommand . RunCommand ( fmt . Sprintf ( "%s %s" , command , branch ) )
2018-08-12 11:50:55 +02:00
}
2018-08-12 12:22:20 +02:00
// ListStash list stash
2018-08-12 11:50:55 +02:00
func ( c * GitCommand ) ListStash ( ) ( string , error ) {
2018-08-14 09:47:33 +02:00
return c . OSCommand . RunCommandWithOutput ( "git stash list" )
2018-08-12 11:50:55 +02:00
}
2018-08-12 12:22:20 +02:00
// Merge merge
2018-08-14 09:47:33 +02:00
func ( c * GitCommand ) Merge ( branchName string ) error {
2018-09-06 23:38:49 +02:00
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git merge --no-edit %s" , branchName ) )
2018-08-12 11:50:55 +02:00
}
2018-08-12 12:22:20 +02:00
// AbortMerge abort merge
2018-08-14 09:47:33 +02:00
func ( c * GitCommand ) AbortMerge ( ) error {
return c . OSCommand . RunCommand ( "git merge --abort" )
2018-08-12 12:22:20 +02:00
}
2018-09-10 21:57:08 +02:00
// usingGpg tells us whether the user has gpg enabled so that we can know
2018-08-14 00:33:27 +02:00
// whether we need to run a subprocess to allow them to enter their password
2018-09-10 21:57:08 +02:00
func ( c * GitCommand ) usingGpg ( ) bool {
2018-09-12 07:50:49 +02:00
gpgsign , _ := c . getLocalGitConfig ( "commit.gpgsign" )
2018-08-14 00:33:27 +02:00
if gpgsign == "" {
2018-09-12 07:50:49 +02:00
gpgsign , _ = c . getGlobalGitConfig ( "commit.gpgsign" )
2018-08-14 00:33:27 +02:00
}
2018-09-10 21:57:08 +02:00
value := strings . ToLower ( gpgsign )
return value == "true" || value == "1" || value == "yes" || value == "on"
2018-08-14 00:33:27 +02:00
}
2018-09-10 22:01:52 +02:00
// Commit commits to git
2019-04-13 05:34:12 +02:00
func ( c * GitCommand ) Commit ( message string , flags string ) ( * exec . Cmd , error ) {
command := fmt . Sprintf ( "git commit %s -m %s" , flags , c . OSCommand . Quote ( message ) )
2019-02-24 00:42:24 +02:00
if c . usingGpg ( ) {
return c . OSCommand . PrepareSubProcess ( c . OSCommand . Platform . shell , c . OSCommand . Platform . shellArg , command ) , nil
2018-09-12 15:20:35 +02:00
}
2019-02-24 00:42:24 +02:00
return nil , c . OSCommand . RunCommand ( command )
}
// AmendHead amends HEAD with whatever is staged in your working tree
func ( c * GitCommand ) AmendHead ( ) ( * exec . Cmd , error ) {
2019-11-04 10:47:25 +02:00
command := "git commit --amend --no-edit --allow-empty"
2018-09-10 21:57:08 +02:00
if c . usingGpg ( ) {
2018-08-21 22:33:25 +02:00
return c . OSCommand . PrepareSubProcess ( c . OSCommand . Platform . shell , c . OSCommand . Platform . shellArg , command ) , nil
2018-08-12 11:50:55 +02:00
}
2018-09-10 22:25:02 +02:00
2018-08-14 09:47:33 +02:00
return nil , c . OSCommand . RunCommand ( command )
2018-08-12 11:50:55 +02:00
}
2018-09-10 22:38:25 +02:00
// Pull pulls from repo
2018-11-02 10:54:54 +02:00
func ( c * GitCommand ) Pull ( ask func ( string ) string ) error {
return c . OSCommand . DetectUnamePass ( "git pull --no-edit" , ask )
2018-08-12 11:50:55 +02:00
}
2018-09-10 22:38:08 +02:00
// Push pushes to a branch
2019-11-11 14:22:09 +02:00
func ( c * GitCommand ) Push ( branchName string , force bool , upstream string , ask func ( string ) string ) error {
2018-08-19 13:28:13 +02:00
forceFlag := ""
if force {
2019-11-11 14:22:09 +02:00
forceFlag = "--force-with-lease"
2018-08-19 13:28:13 +02:00
}
2018-09-10 22:38:08 +02:00
2019-11-11 14:22:09 +02:00
setUpstreamArg := ""
if upstream != "" {
setUpstreamArg = "--set-upstream " + upstream
}
2019-11-18 00:38:36 +02:00
cmd := fmt . Sprintf ( "git push --follow-tags %s %s" , forceFlag , setUpstreamArg )
2018-10-17 21:12:33 +02:00
return c . OSCommand . DetectUnamePass ( cmd , ask )
2018-08-12 11:50:55 +02:00
}
2018-09-12 22:28:49 +02:00
// CatFile obtains the content of a file
2018-08-19 12:13:29 +02:00
func ( c * GitCommand ) CatFile ( fileName string ) ( string , error ) {
2018-09-13 21:27:35 +02:00
return c . OSCommand . RunCommandWithOutput ( fmt . Sprintf ( "cat %s" , c . OSCommand . Quote ( fileName ) ) )
2018-08-12 12:22:20 +02:00
}
// StageFile stages a file
2018-08-19 12:13:29 +02:00
func ( c * GitCommand ) StageFile ( fileName string ) error {
2018-09-13 21:27:35 +02:00
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git add %s" , c . OSCommand . Quote ( fileName ) ) )
2018-08-12 12:22:20 +02:00
}
2018-08-25 00:51:47 +02:00
// StageAll stages all files
func ( c * GitCommand ) StageAll ( ) error {
return c . OSCommand . RunCommand ( "git add -A" )
}
// UnstageAll stages all files
func ( c * GitCommand ) UnstageAll ( ) error {
return c . OSCommand . RunCommand ( "git reset" )
}
2018-08-12 12:22:20 +02:00
// UnStageFile unstages a file
2018-08-19 12:13:29 +02:00
func ( c * GitCommand ) UnStageFile ( fileName string , tracked bool ) error {
2018-09-13 21:27:35 +02:00
command := "git rm --cached %s"
2018-08-12 12:22:20 +02:00
if tracked {
2018-09-13 21:27:35 +02:00
command = "git reset HEAD %s"
2018-08-12 12:22:20 +02:00
}
2019-02-20 10:47:01 +02:00
// renamed files look like "file1 -> file2"
fileNames := strings . Split ( fileName , " -> " )
for _ , name := range fileNames {
if err := c . OSCommand . RunCommand ( fmt . Sprintf ( command , c . OSCommand . Quote ( name ) ) ) ; err != nil {
return err
}
}
return nil
2018-08-12 12:22:20 +02:00
}
// GitStatus returns the plaintext short status of the repo
func ( c * GitCommand ) GitStatus ( ) ( string , error ) {
2019-05-30 14:45:56 +02:00
return c . OSCommand . RunCommandWithOutput ( "git status --untracked-files=all --porcelain" )
2018-08-12 12:22:20 +02:00
}
// IsInMergeState states whether we are still mid-merge
func ( c * GitCommand ) IsInMergeState ( ) ( bool , error ) {
2018-08-14 09:47:33 +02:00
output , err := c . OSCommand . RunCommandWithOutput ( "git status --untracked-files=all" )
2018-08-12 12:22:20 +02:00
if err != nil {
return false , err
}
return strings . Contains ( output , "conclude merge" ) || strings . Contains ( output , "unmerged paths" ) , nil
}
2019-03-02 11:00:26 +02:00
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
// and "interactive" for interactive rebase
func ( c * GitCommand ) RebaseMode ( ) ( string , error ) {
2019-05-12 09:04:32 +02:00
exists , err := c . OSCommand . FileExists ( fmt . Sprintf ( "%s/rebase-apply" , c . DotGitDir ) )
2019-03-02 11:00:26 +02:00
if err != nil {
return "" , err
}
if exists {
return "normal" , nil
}
2019-05-12 09:04:32 +02:00
exists , err = c . OSCommand . FileExists ( fmt . Sprintf ( "%s/rebase-merge" , c . DotGitDir ) )
2019-03-02 11:00:26 +02:00
if exists {
return "interactive" , err
} else {
return "" , err
}
2018-12-02 18:33:16 +02:00
}
2019-03-18 11:44:33 +02:00
// DiscardAllFileChanges directly
func ( c * GitCommand ) DiscardAllFileChanges ( file * File ) error {
2018-08-12 12:22:20 +02:00
// if the file isn't tracked, we assume you want to delete it
2019-02-20 11:01:29 +02:00
quotedFileName := c . OSCommand . Quote ( file . Name )
2019-07-14 12:29:36 +02:00
if file . HasStagedChanges || file . HasMergeConflicts {
2019-02-20 11:01:29 +02:00
if err := c . OSCommand . RunCommand ( fmt . Sprintf ( "git reset -- %s" , quotedFileName ) ) ; err != nil {
2018-08-18 12:14:44 +02:00
return err
}
}
2019-07-13 14:57:35 +02:00
2018-08-12 12:22:20 +02:00
if ! file . Tracked {
2018-09-16 22:03:56 +02:00
return c . removeFile ( file . Name )
2018-08-12 12:22:20 +02:00
}
2019-03-18 11:44:33 +02:00
return c . DiscardUnstagedFileChanges ( file )
}
// DiscardUnstagedFileChanges directly
func ( c * GitCommand ) DiscardUnstagedFileChanges ( file * File ) error {
quotedFileName := c . OSCommand . Quote ( file . Name )
2019-02-20 11:01:29 +02:00
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git checkout -- %s" , quotedFileName ) )
2018-08-12 12:22:20 +02:00
}
// Checkout checks out a branch, with --force if you set the force arg to true
2018-08-14 09:47:33 +02:00
func ( c * GitCommand ) Checkout ( branch string , force bool ) error {
2018-08-12 12:22:20 +02:00
forceArg := ""
if force {
forceArg = "--force "
}
2018-09-16 22:08:23 +02:00
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git checkout %s %s" , forceArg , branch ) )
2018-08-12 12:22:20 +02:00
}
2018-08-13 12:26:02 +02:00
// PrepareCommitSubProcess prepares a subprocess for `git commit`
2018-08-21 22:33:25 +02:00
func ( c * GitCommand ) PrepareCommitSubProcess ( ) * exec . Cmd {
2018-08-13 12:26:02 +02:00
return c . OSCommand . PrepareSubProcess ( "git" , "commit" )
2018-08-12 12:22:20 +02:00
}
2018-08-29 14:27:17 +02:00
// PrepareCommitAmendSubProcess prepares a subprocess for `git commit --amend --allow-empty`
func ( c * GitCommand ) PrepareCommitAmendSubProcess ( ) * exec . Cmd {
return c . OSCommand . PrepareSubProcess ( "git" , "commit" , "--amend" , "--allow-empty" )
}
2018-08-12 12:22:20 +02:00
// GetBranchGraph gets the color-formatted graph of the log for the given branch
// Currently it limits the result to 100 commits, but when we get async stuff
// working we can do lazy loading
func ( c * GitCommand ) GetBranchGraph ( branchName string ) ( string , error ) {
2018-09-16 22:12:03 +02:00
return c . OSCommand . RunCommandWithOutput ( fmt . Sprintf ( "git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 %s" , branchName ) )
2018-08-12 12:22:20 +02:00
}
2018-08-12 13:04:47 +02:00
2019-11-13 13:14:57 +02:00
func ( c * GitCommand ) GetUpstreamForBranch ( branchName string ) ( string , error ) {
output , err := c . OSCommand . RunCommandWithOutput ( fmt . Sprintf ( "git rev-parse --abbrev-ref --symbolic-full-name %s@{u}" , branchName ) )
return strings . TrimSpace ( output ) , err
}
2018-08-12 13:04:47 +02:00
// Ignore adds a file to the gitignore for the repo
2018-08-19 12:41:04 +02:00
func ( c * GitCommand ) Ignore ( filename string ) error {
return c . OSCommand . AppendLineToFile ( ".gitignore" , filename )
2018-08-12 13:04:47 +02:00
}
// Show shows the diff of a commit
2018-09-18 20:53:32 +02:00
func ( c * GitCommand ) Show ( sha string ) ( string , error ) {
2019-11-04 10:47:25 +02:00
show , err := c . OSCommand . RunCommandWithOutput ( fmt . Sprintf ( "git show --color --no-renames %s" , sha ) )
2018-12-08 07:54:54 +02:00
if err != nil {
return "" , err
}
// if this is a merge commit, we need to go a step further and get the diff between the two branches we merged
revList , err := c . OSCommand . RunCommandWithOutput ( fmt . Sprintf ( "git rev-list -1 --merges %s^...%s" , sha , sha ) )
if err != nil {
// turns out we get an error here when it's the first commit. We'll just return the original show
return show , nil
}
if len ( revList ) == 0 {
return show , nil
}
// we want to pull out 1a6a69a and 3b51d7c from this:
// commit ccc771d8b13d5b0d4635db4463556366470fd4f6
// Merge: 1a6a69a 3b51d7c
lines := utils . SplitLines ( show )
if len ( lines ) < 2 {
return show , nil
}
secondLineWords := strings . Split ( lines [ 1 ] , " " )
if len ( secondLineWords ) < 3 {
return show , nil
}
mergeDiff , err := c . OSCommand . RunCommandWithOutput ( fmt . Sprintf ( "git diff --color %s...%s" , secondLineWords [ 1 ] , secondLineWords [ 2 ] ) )
if err != nil {
return "" , err
}
return show + mergeDiff , nil
2018-08-12 13:04:47 +02:00
}
2018-10-12 14:06:03 +02:00
// GetRemoteURL returns current repo remote url
func ( c * GitCommand ) GetRemoteURL ( ) string {
url , _ := c . OSCommand . RunCommandWithOutput ( "git config --get remote.origin.url" )
return utils . TrimTrailingNewline ( url )
}
2018-10-17 14:20:15 +02:00
// CheckRemoteBranchExists Returns remote branch
func ( c * GitCommand ) CheckRemoteBranchExists ( branch * Branch ) bool {
_ , err := c . OSCommand . RunCommandWithOutput ( fmt . Sprintf (
"git show-ref --verify -- refs/remotes/origin/%s" ,
branch . Name ,
) )
return err == nil
}
2018-08-12 13:04:47 +02:00
// Diff returns the diff of a file
2019-10-30 11:23:25 +02:00
func ( c * GitCommand ) Diff ( file * File , plain bool , cached bool ) string {
2018-08-12 13:04:47 +02:00
cachedArg := ""
2018-09-18 21:09:51 +02:00
trackedArg := "--"
2018-12-02 10:57:01 +02:00
colorArg := "--color"
2019-02-20 10:47:01 +02:00
split := strings . Split ( file . Name , " -> " ) // in case of a renamed file we get the new filename
fileName := c . OSCommand . Quote ( split [ len ( split ) - 1 ] )
2019-10-30 11:23:25 +02:00
if cached {
2018-08-14 10:48:08 +02:00
cachedArg = "--cached"
2018-08-12 13:04:47 +02:00
}
if ! file . Tracked && ! file . HasStagedChanges {
2018-08-14 10:48:08 +02:00
trackedArg = "--no-index /dev/null"
2018-08-12 13:04:47 +02:00
}
2018-12-02 10:57:01 +02:00
if plain {
colorArg = ""
}
command := fmt . Sprintf ( "git diff %s %s %s %s" , colorArg , cachedArg , trackedArg , fileName )
2018-08-14 10:48:08 +02:00
2018-08-12 13:04:47 +02:00
// for now we assume an error means the file was deleted
2018-08-14 09:47:33 +02:00
s , _ := c . OSCommand . RunCommandWithOutput ( command )
2018-08-12 13:04:47 +02:00
return s
}
2018-12-02 10:57:01 +02:00
2019-11-05 09:44:46 +02:00
func ( c * GitCommand ) ApplyPatch ( patch string , flags ... string ) error {
2019-11-05 04:01:58 +02:00
c . Log . Warn ( patch )
2019-11-05 03:42:07 +02:00
filepath := filepath . Join ( c . Config . GetUserConfigDir ( ) , utils . GetCurrentRepoName ( ) , time . Now ( ) . Format ( time . StampNano ) + ".patch" )
if err := c . OSCommand . CreateFileWithContent ( filepath , patch ) ; err != nil {
2019-11-04 10:47:25 +02:00
return err
2018-12-02 10:57:01 +02:00
}
2019-11-05 09:44:46 +02:00
flagStr := ""
for _ , flag := range flags {
flagStr += " --" + flag
2019-10-30 11:23:25 +02:00
}
2019-11-05 09:44:46 +02:00
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git apply %s %s" , flagStr , c . OSCommand . Quote ( filepath ) ) )
2018-12-02 10:57:01 +02:00
}
2018-12-07 09:52:31 +02:00
2019-11-17 05:28:38 +02:00
func ( c * GitCommand ) FastForward ( branchName string , remoteName string , remoteBranchName string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git fetch %s %s:%s" , remoteName , remoteBranchName , branchName ) )
2018-12-07 09:52:31 +02:00
}
2019-02-16 12:01:17 +02:00
2019-03-03 03:44:10 +02:00
func ( c * GitCommand ) RunSkipEditorCommand ( command string ) error {
cmd := c . OSCommand . ExecutableFromString ( command )
cmd . Env = append (
2019-06-06 12:33:25 +02:00
cmd . Env ,
2019-03-03 03:44:10 +02:00
"LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY" ,
"EDITOR=" + c . OSCommand . GetLazygitPath ( ) ,
)
return c . OSCommand . RunExecutable ( cmd )
}
2019-02-19 14:36:29 +02:00
// GenericMerge takes a commandType of "merge" or "rebase" and a command of "abort", "skip" or "continue"
2019-02-16 12:01:17 +02:00
// By default we skip the editor in the case where a commit will be made
func ( c * GitCommand ) GenericMerge ( commandType string , command string ) error {
2019-11-04 10:47:25 +02:00
err := c . RunSkipEditorCommand (
2019-03-03 03:44:10 +02:00
fmt . Sprintf (
"git %s --%s" ,
commandType ,
command ,
) ,
)
2019-11-04 10:47:25 +02:00
if err != nil {
return err
}
// sometimes we need to do a sequence of things in a rebase but the user needs to
// fix merge conflicts along the way. When this happens we queue up the next step
// so that after the next successful rebase continue we can continue from where we left off
if commandType == "rebase" && command == "continue" && c . onSuccessfulContinue != nil {
f := c . onSuccessfulContinue
c . onSuccessfulContinue = nil
return f ( )
}
if command == "abort" {
c . onSuccessfulContinue = nil
}
return nil
2019-02-16 12:01:17 +02:00
}
2019-02-18 12:29:43 +02:00
2019-02-18 14:27:54 +02:00
func ( c * GitCommand ) RewordCommit ( commits [ ] * Commit , index int ) ( * exec . Cmd , error ) {
2019-04-07 01:10:58 +02:00
todo , sha , err := c . GenerateGenericRebaseTodo ( commits , index , "reword" )
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
}
2019-04-07 01:10:58 +02:00
return c . PrepareInteractiveRebaseCommand ( sha , todo , false )
2019-02-18 14:27:54 +02:00
}
func ( c * GitCommand ) MoveCommitDown ( commits [ ] * Commit , index int ) error {
// we must ensure that we have at least two commits after the selected one
if len ( commits ) <= index + 2 {
// assuming they aren't picking the bottom commit
2019-02-20 11:51:24 +02:00
return errors . New ( c . Tr . SLocalize ( "NoRoom" ) )
2019-02-18 12:29:43 +02:00
}
todo := ""
2019-02-18 14:27:54 +02:00
orderedCommits := append ( commits [ 0 : index ] , commits [ index + 1 ] , commits [ index ] )
for _ , commit := range orderedCommits {
2019-02-19 14:36:29 +02:00
todo = "pick " + commit . Sha + " " + commit . Name + "\n" + todo
2019-02-18 14:27:54 +02:00
}
2019-02-19 00:03:29 +02:00
cmd , err := c . PrepareInteractiveRebaseCommand ( commits [ index + 2 ] . Sha , todo , true )
2019-02-18 14:27:54 +02:00
if err != nil {
return err
}
return c . OSCommand . RunPreparedCommand ( cmd )
}
func ( c * GitCommand ) InteractiveRebase ( commits [ ] * Commit , index int , action string ) error {
2019-04-07 01:10:58 +02:00
todo , sha , err := c . GenerateGenericRebaseTodo ( commits , index , action )
2019-02-18 14:27:54 +02:00
if err != nil {
return err
}
2019-04-07 01:10:58 +02:00
cmd , err := c . PrepareInteractiveRebaseCommand ( sha , todo , true )
2019-02-18 14:27:54 +02:00
if err != nil {
return err
}
return c . OSCommand . RunPreparedCommand ( cmd )
}
2019-03-03 03:44:10 +02:00
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
// we tell git to run lazygit to edit the todo list, and we pass the client
// lazygit a todo string to write to the todo file
2019-02-19 14:36:29 +02:00
func ( c * GitCommand ) PrepareInteractiveRebaseCommand ( baseSha string , todo string , overrideEditor bool ) ( * exec . Cmd , error ) {
2019-03-03 03:44:10 +02:00
ex := c . OSCommand . GetLazygitPath ( )
2019-02-18 12:29:43 +02:00
debug := "FALSE"
2019-07-19 03:46:07 +02:00
if c . OSCommand . Config . GetDebug ( ) {
2019-02-18 12:29:43 +02:00
debug = "TRUE"
}
2019-11-05 02:17:52 +02:00
splitCmd := str . ToArgv ( fmt . Sprintf ( "git rebase --interactive --autostash --keep-empty --rebase-merges %s" , baseSha ) )
2019-02-18 12:29:43 +02:00
2019-03-02 04:08:09 +02:00
cmd := c . OSCommand . command ( splitCmd [ 0 ] , splitCmd [ 1 : ] ... )
2019-02-18 12:29:43 +02:00
2019-02-24 00:42:24 +02:00
gitSequenceEditor := ex
if todo == "" {
gitSequenceEditor = "true"
}
2019-02-18 12:29:43 +02:00
cmd . Env = os . Environ ( )
cmd . Env = append (
cmd . Env ,
2019-03-03 03:44:10 +02:00
"LAZYGIT_CLIENT_COMMAND=INTERACTIVE_REBASE" ,
2019-02-18 12:29:43 +02:00
"LAZYGIT_REBASE_TODO=" + todo ,
"DEBUG=" + debug ,
"LANG=en_US.UTF-8" , // Force using EN as language
"LC_ALL=en_US.UTF-8" , // Force using EN as language
2019-02-24 00:42:24 +02:00
"GIT_SEQUENCE_EDITOR=" + gitSequenceEditor ,
2019-02-18 12:29:43 +02:00
)
2019-02-19 14:36:29 +02:00
if overrideEditor {
cmd . Env = append ( cmd . Env , "EDITOR=" + ex )
}
2019-02-18 14:27:54 +02:00
return cmd , nil
}
func ( c * GitCommand ) HardReset ( baseSha string ) error {
return c . OSCommand . RunCommand ( "git reset --hard " + baseSha )
}
2019-02-19 14:36:29 +02:00
func ( c * GitCommand ) SoftReset ( baseSha string ) error {
return c . OSCommand . RunCommand ( "git reset --soft " + baseSha )
}
2019-04-07 01:10:58 +02:00
func ( c * GitCommand ) GenerateGenericRebaseTodo ( commits [ ] * Commit , actionIndex int , action string ) ( string , string , error ) {
baseIndex := actionIndex + 1
if len ( commits ) <= baseIndex {
return "" , "" , errors . New ( c . Tr . SLocalize ( "CannotRebaseOntoFirstCommit" ) )
}
if action == "squash" || action == "fixup" {
baseIndex ++
if len ( commits ) <= baseIndex {
return "" , "" , errors . New ( c . Tr . SLocalize ( "CannotSquashOntoSecondCommit" ) )
}
2019-02-18 12:29:43 +02:00
}
2019-02-18 14:27:54 +02:00
todo := ""
2019-04-07 01:10:58 +02:00
for i , commit := range commits [ 0 : baseIndex ] {
2019-02-18 14:27:54 +02:00
a := "pick"
2019-04-07 01:10:58 +02:00
if i == actionIndex {
2019-02-18 14:27:54 +02:00
a = action
2019-02-18 12:29:43 +02:00
}
2019-02-19 14:36:29 +02:00
todo = a + " " + commit . Sha + " " + commit . Name + "\n" + todo
2019-02-18 12:29:43 +02:00
}
2019-04-07 01:10:58 +02:00
return todo , commits [ baseIndex ] . Sha , nil
2019-02-18 12:29:43 +02:00
}
2019-02-19 14:36:29 +02:00
// AmendTo amends the given commit with whatever files are staged
func ( c * GitCommand ) AmendTo ( sha string ) error {
2019-04-07 03:35:34 +02:00
if err := c . CreateFixupCommit ( sha ) ; err != nil {
2019-02-19 14:36:29 +02:00
return err
}
2019-04-07 03:35:34 +02:00
return c . SquashAllAboveFixupCommits ( sha )
2019-02-19 14:36:29 +02:00
}
2019-02-20 11:51:24 +02:00
// EditRebaseTodo sets the action at a given index in the git-rebase-todo file
2019-02-19 14:36:29 +02:00
func ( c * GitCommand ) EditRebaseTodo ( index int , action string ) error {
2019-05-12 09:04:32 +02:00
fileName := fmt . Sprintf ( "%s/rebase-merge/git-rebase-todo" , c . DotGitDir )
2019-02-19 14:36:29 +02:00
bytes , err := ioutil . ReadFile ( fileName )
if err != nil {
return err
}
content := strings . Split ( string ( bytes ) , "\n" )
2019-03-02 11:57:18 +02:00
commitCount := c . getTodoCommitCount ( content )
2019-02-24 00:42:24 +02:00
// we have the most recent commit at the bottom whereas the todo file has
// it at the bottom, so we need to subtract our index from the commit count
contentIndex := commitCount - 1 - index
2019-02-19 14:36:29 +02:00
splitLine := strings . Split ( content [ contentIndex ] , " " )
content [ contentIndex ] = action + " " + strings . Join ( splitLine [ 1 : ] , " " )
result := strings . Join ( content , "\n" )
return ioutil . WriteFile ( fileName , [ ] byte ( result ) , 0644 )
}
2019-03-02 11:57:18 +02:00
func ( c * GitCommand ) getTodoCommitCount ( content [ ] string ) int {
// count lines that are not blank and are not comments
commitCount := 0
for _ , line := range content {
if line != "" && ! strings . HasPrefix ( line , "#" ) {
commitCount ++
}
}
return commitCount
}
2019-02-20 11:51:24 +02:00
// MoveTodoDown moves a rebase todo item down by one position
func ( c * GitCommand ) MoveTodoDown ( index int ) error {
2019-05-12 09:04:32 +02:00
fileName := fmt . Sprintf ( "%s/rebase-merge/git-rebase-todo" , c . DotGitDir )
2019-02-20 11:51:24 +02:00
bytes , err := ioutil . ReadFile ( fileName )
if err != nil {
return err
}
content := strings . Split ( string ( bytes ) , "\n" )
2019-03-02 11:57:18 +02:00
commitCount := c . getTodoCommitCount ( content )
contentIndex := commitCount - 1 - index
2019-02-20 11:51:24 +02:00
rearrangedContent := append ( content [ 0 : contentIndex - 1 ] , content [ contentIndex ] , content [ contentIndex - 1 ] )
rearrangedContent = append ( rearrangedContent , content [ contentIndex + 1 : ] ... )
result := strings . Join ( rearrangedContent , "\n" )
return ioutil . WriteFile ( fileName , [ ] byte ( result ) , 0644 )
}
2019-02-19 14:36:29 +02:00
// Revert reverts the selected commit by sha
func ( c * GitCommand ) Revert ( sha string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git revert %s" , sha ) )
}
2019-02-24 04:51:52 +02:00
2019-02-24 08:34:19 +02:00
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
func ( c * GitCommand ) CherryPickCommits ( commits [ ] * Commit ) error {
2019-02-24 04:51:52 +02:00
todo := ""
2019-02-24 08:34:19 +02:00
for _ , commit := range commits {
todo = "pick " + commit . Sha + " " + commit . Name + "\n" + todo
2019-02-24 04:51:52 +02:00
}
cmd , err := c . PrepareInteractiveRebaseCommand ( "HEAD" , todo , false )
if err != nil {
return err
}
return c . OSCommand . RunPreparedCommand ( cmd )
}
2019-03-09 16:42:10 +02:00
2019-03-11 04:04:08 +02:00
// GetCommitFiles get the specified commit files
2019-11-04 10:47:25 +02:00
func ( c * GitCommand ) GetCommitFiles ( commitSha string , patchManager * PatchManager ) ( [ ] * CommitFile , error ) {
cmd := fmt . Sprintf ( "git show --pretty= --name-only --no-renames %s" , commitSha )
2019-03-11 04:04:08 +02:00
files , err := c . OSCommand . RunCommandWithOutput ( cmd )
if err != nil {
return nil , err
}
commitFiles := make ( [ ] * CommitFile , 0 )
for _ , file := range strings . Split ( strings . TrimRight ( files , "\n" ) , "\n" ) {
2019-11-04 10:47:25 +02:00
status := UNSELECTED
if patchManager != nil && patchManager . CommitSha == commitSha {
status = patchManager . GetFileStatus ( file )
}
2019-03-11 04:04:08 +02:00
commitFiles = append ( commitFiles , & CommitFile {
2019-03-16 01:15:46 +02:00
Sha : commitSha ,
2019-03-11 04:04:08 +02:00
Name : file ,
DisplayString : file ,
2019-11-04 10:47:25 +02:00
Status : status ,
2019-03-11 04:04:08 +02:00
} )
}
return commitFiles , nil
2019-03-09 16:42:10 +02:00
}
// ShowCommitFile get the diff of specified commit file
2019-11-04 10:47:25 +02:00
func ( c * GitCommand ) ShowCommitFile ( commitSha , fileName string , plain bool ) ( string , error ) {
colorArg := "--color"
if plain {
colorArg = ""
}
cmd := fmt . Sprintf ( "git show --no-renames %s %s -- %s" , colorArg , commitSha , fileName )
2019-03-09 16:42:10 +02:00
return c . OSCommand . RunCommandWithOutput ( cmd )
}
2019-03-11 00:53:46 +02:00
// CheckoutFile checks out the file for the given commit
func ( c * GitCommand ) CheckoutFile ( commitSha , fileName string ) error {
cmd := fmt . Sprintf ( "git checkout %s %s" , commitSha , fileName )
return c . OSCommand . RunCommand ( cmd )
}
2019-03-11 04:04:08 +02:00
// DiscardOldFileChanges discards changes to a file from an old commit
func ( c * GitCommand ) DiscardOldFileChanges ( commits [ ] * Commit , commitIndex int , fileName string ) error {
2019-11-04 10:47:25 +02:00
if err := c . BeginInteractiveRebaseForCommit ( commits , commitIndex ) ; err != nil {
2019-03-11 04:04:08 +02:00
return err
}
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
if err := c . OSCommand . RunCommand ( fmt . Sprintf ( "git cat-file -e HEAD^:%s" , fileName ) ) ; err != nil {
2019-03-18 11:44:33 +02:00
if err := c . OSCommand . Remove ( fileName ) ; err != nil {
2019-03-11 04:04:08 +02:00
return err
}
if err := c . StageFile ( fileName ) ; err != nil {
return err
}
2019-07-19 04:00:02 +02:00
} else if err := c . CheckoutFile ( "HEAD^" , fileName ) ; err != nil {
return err
2019-03-11 04:04:08 +02:00
}
// amend the commit
2019-11-04 10:47:25 +02:00
cmd , err := c . AmendHead ( )
2019-03-11 04:04:08 +02:00
if cmd != nil {
2019-03-12 10:20:19 +02:00
return errors . New ( "received unexpected pointer to cmd" )
2019-03-11 04:04:08 +02:00
}
if err != nil {
return err
}
// continue
return c . GenericMerge ( "rebase" , "continue" )
}
2019-03-18 12:19:07 +02:00
// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .`
func ( c * GitCommand ) DiscardAnyUnstagedFileChanges ( ) error {
return c . OSCommand . RunCommand ( "git checkout -- ." )
}
// RemoveUntrackedFiles runs `git clean -fd`
func ( c * GitCommand ) RemoveUntrackedFiles ( ) error {
return c . OSCommand . RunCommand ( "git clean -fd" )
}
// ResetHardHead runs `git reset --hard HEAD`
func ( c * GitCommand ) ResetHardHead ( ) error {
return c . OSCommand . RunCommand ( "git reset --hard HEAD" )
}
2019-03-18 12:40:32 +02:00
// ResetSoftHead runs `git reset --soft HEAD`
func ( c * GitCommand ) ResetSoftHead ( ) error {
return c . OSCommand . RunCommand ( "git reset --soft HEAD" )
}
2019-03-23 15:46:08 +02:00
// DiffCommits show diff between commits
func ( c * GitCommand ) DiffCommits ( sha1 , sha2 string ) ( string , error ) {
cmd := fmt . Sprintf ( "git diff --color %s %s" , sha1 , sha2 )
return c . OSCommand . RunCommandWithOutput ( cmd )
}
2019-04-07 03:35:34 +02:00
// CreateFixupCommit creates a commit that fixes up a previous commit
func ( c * GitCommand ) CreateFixupCommit ( sha string ) error {
cmd := fmt . Sprintf ( "git commit --fixup=%s" , sha )
return c . OSCommand . RunCommand ( cmd )
}
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
func ( c * GitCommand ) SquashAllAboveFixupCommits ( sha string ) error {
return c . RunSkipEditorCommand (
fmt . Sprintf (
"git rebase --interactive --autostash --autosquash %s^" ,
sha ,
) ,
)
}
2019-05-30 14:45:56 +02:00
// StashSaveStagedChanges stashes only the currently staged changes. This takes a few steps
// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
func ( c * GitCommand ) StashSaveStagedChanges ( message string ) error {
if err := c . OSCommand . RunCommand ( "git stash --keep-index" ) ; err != nil {
return err
}
if err := c . StashSave ( message ) ; err != nil {
return err
}
if err := c . OSCommand . RunCommand ( "git stash apply stash@{1}" ) ; err != nil {
return err
}
if err := c . OSCommand . PipeCommands ( "git stash show -p" , "git apply -R" ) ; err != nil {
return err
}
if err := c . OSCommand . RunCommand ( "git stash drop stash@{1}" ) ; err != nil {
return err
}
// if you had staged an untracked file, that will now appear as 'AD' in git status
// meaning it's deleted in your working tree but added in your index. Given that it's
// now safely stashed, we need to remove it.
files := c . GetStatusFiles ( )
for _ , file := range files {
if file . ShortStatus == "AD" {
if err := c . UnStageFile ( file . Name , false ) ; err != nil {
return err
}
}
}
return nil
}
2019-11-04 10:47:25 +02:00
// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current
// commit and pick all others. After this you'll want to call `c.GenericMerge("rebase", "continue")`
func ( c * GitCommand ) BeginInteractiveRebaseForCommit ( commits [ ] * Commit , commitIndex int ) error {
if len ( commits ) - 1 < commitIndex {
return errors . New ( "index outside of range of commits" )
}
// we can make this GPG thing possible it just means we need to do this in two parts:
// one where we handle the possibility of a credential request, and the other
// where we continue the rebase
if c . usingGpg ( ) {
return errors . New ( c . Tr . SLocalize ( "DisabledForGPG" ) )
}
todo , sha , err := c . GenerateGenericRebaseTodo ( commits , commitIndex , "edit" )
if err != nil {
return err
}
cmd , err := c . PrepareInteractiveRebaseCommand ( sha , todo , true )
if err != nil {
return err
}
if err := c . OSCommand . RunPreparedCommand ( cmd ) ; err != nil {
return err
}
return nil
}
2019-11-13 12:16:24 +02:00
func ( c * GitCommand ) SetUpstreamBranch ( upstream string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git branch -u %s" , upstream ) )
}
2019-11-17 03:02:39 +02:00
func ( c * GitCommand ) AddRemote ( name string , url string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git remote add %s %s" , name , url ) )
}
func ( c * GitCommand ) RemoveRemote ( name string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git remote remove %s" , name ) )
}
2019-11-17 04:21:38 +02:00
func ( c * GitCommand ) IsHeadDetached ( ) bool {
err := c . OSCommand . RunCommand ( "git symbolic-ref -q HEAD" )
return err != nil
}
2019-11-17 04:47:47 +02:00
func ( c * GitCommand ) DeleteRemoteBranch ( remoteName string , branchName string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git push %s --delete %s" , remoteName , branchName ) )
}
2019-11-17 05:50:12 +02:00
func ( c * GitCommand ) SetBranchUpstream ( remoteName string , remoteBranchName string , branchName string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git branch --set-upstream-to=%s/%s %s" , remoteName , remoteBranchName , branchName ) )
}
2019-11-17 09:15:32 +02:00
func ( c * GitCommand ) RenameRemote ( oldRemoteName string , newRemoteName string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git remote rename %s %s" , oldRemoteName , newRemoteName ) )
}
func ( c * GitCommand ) UpdateRemoteUrl ( remoteName string , updatedUrl string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git remote set-url %s %s" , remoteName , updatedUrl ) )
}
2019-11-18 00:38:36 +02:00
func ( c * GitCommand ) CreateLightweightTag ( tagName string , commitSha string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git tag %s %s" , tagName , commitSha ) )
}
func ( c * GitCommand ) ShowTag ( tagName string ) ( string , error ) {
return c . OSCommand . RunCommandWithOutput ( fmt . Sprintf ( "git tag -n99 %s" , tagName ) )
}
func ( c * GitCommand ) DeleteTag ( tagName string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git tag -d %s" , tagName ) )
}
func ( c * GitCommand ) PushTag ( remoteName string , tagName string ) error {
return c . OSCommand . RunCommand ( fmt . Sprintf ( "git push %s %s" , remoteName , tagName ) )
}