diff options
author | Andrei Miulescu <lusu777@gmail.com> | 2018-08-12 20:22:20 +1000 |
---|---|---|
committer | Andrei Miulescu <lusu777@gmail.com> | 2018-08-12 20:22:20 +1000 |
commit | e65ddd7b6facfaf3ebc8b022f26066bdf96a28b0 (patch) | |
tree | 768715f979c50cabaec6b665829da12670ebd89b /pkg/commands | |
parent | c01bc094423cf8a0a861ee8b3bd5432cd958339d (diff) |
Move some commands around
Diffstat (limited to 'pkg/commands')
-rw-r--r-- | pkg/commands/git.go | 257 | ||||
-rw-r--r-- | pkg/commands/gitcommands.go | 449 | ||||
-rw-r--r-- | pkg/commands/os.go | 67 |
3 files changed, 314 insertions, 459 deletions
diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 23a7d90f9..1afe4ae8a 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -1,8 +1,10 @@ package commands import ( + "errors" "fmt" "os" + "os/exec" "strings" "time" @@ -45,6 +47,114 @@ func (c *GitCommand) GitIgnore(filename string) { } } +// GetStashEntries stash entryies +func (c *GitCommand) GetStashEntries() []StashEntry { + stashEntries := make([]StashEntry, 0) + rawString, _ := c.OSCommand.RunDirectCommand("git stash list --pretty='%gs'") + for i, line := range splitLines(rawString) { + stashEntries = append(stashEntries, stashEntryFromLine(line, i)) + } + return stashEntries +} + +func stashEntryFromLine(line string, index int) StashEntry { + return StashEntry{ + Name: line, + Index: index, + DisplayString: line, + } +} + +// GetStashEntryDiff stash diff +func (c *GitCommand) GetStashEntryDiff(index int) (string, error) { + return runCommand("git stash show -p --color stash@{" + fmt.Sprint(index) + "}") +} + +func includes(array []string, str string) bool { + for _, arrayStr := range array { + if arrayStr == str { + return true + } + } + return false +} + +// GetStatusFiles git status files +func (c *GitCommand) GetStatusFiles() []GitFile { + statusOutput, _ := getGitStatus() + statusStrings := splitLines(statusOutput) + gitFiles := make([]GitFile, 0) + + for _, statusString := range statusStrings { + change := statusString[0:2] + stagedChange := change[0:1] + unstagedChange := statusString[1:2] + filename := statusString[3:] + tracked := !f([]string{"??", "A "}, change) + gitFile := GitFile{ + Name: filename, + DisplayString: statusString, + HasStagedChanges: !includes([]string{" ", "U", "?"}, stagedChange), + HasUnstagedChanges: unstagedChange != " ", + Tracked: tracked, + Deleted: unstagedChange == "D" || stagedChange == "D", + HasMergeConflicts: change == "UU", + } + gitFiles = append(gitFiles, gitFile) + } + objectLog(gitFiles) + return gitFiles +} + +// StashDo modify stash +func (c *GitCommand) StashDo(index int, method string) (string, error) { + return c.OSCommand.RunCommand("git stash " + method + " stash@{" + fmt.Sprint(index) + "}") +} + +// StashSave save stash +func (c *GitCommand) StashSave(message string) (string, error) { + output, err := c.OSCommand.RunCommand("git stash save \"" + message + "\"") + if err != nil { + return output, err + } + // if there are no local changes to save, the exit code is 0, but we want + // to raise an error + if output == "No local changes to save\n" { + return output, errors.New(output) + } + return output, nil +} + +// MergeStatusFiles merge status files +func (c *GitCommand) MergeStatusFiles(oldGitFiles, newGitFiles []GitFile) []GitFile { + if len(oldGitFiles) == 0 { + return newGitFiles + } + + appendedIndexes := make([]int, 0) + + // retain position of files we already could see + result := make([]GitFile, 0) + for _, oldGitFile := range oldGitFiles { + for newIndex, newGitFile := range newGitFiles { + if oldGitFile.Name == newGitFile.Name { + result = append(result, newGitFile) + appendedIndexes = append(appendedIndexes, newIndex) + break + } + } + } + + // append any new files to the end + for index, newGitFile := range newGitFiles { + if !includesInt(appendedIndexes, index) { + result = append(result, newGitFile) + } + } + + return result +} + func (c *GitCommand) verifyInGitRepo() { if output, err := c.OSCommand.RunCommand("git status"); err != nil { fmt.Println(output) @@ -127,35 +237,54 @@ func (c *GitCommand) RenameCommit(name string) (string, error) { return c.OSCommand.runDirectCommand("git commit --allow-empty --amend -m \"" + name + "\"") } +// Fetch fetch git repo func (c *GitCommand) Fetch() (string, error) { return c.OSCommand.runDirectCommand("git fetch") } +// ResetToCommit reset to commit func (c *GitCommand) ResetToCommit(sha string) (string, error) { return c.OSCommand.runDirectCommand("git reset " + sha) } +// NewBranch create new branch func (c *GitCommand) NewBranch(name string) (string, error) { return c.OSCommand.runDirectCommand("git checkout -b " + name) } +// DeleteBranch delete branch func (c *GitCommand) DeleteBranch(branch string) (string, error) { return runCommand("git branch -d " + branch) } +// ListStash list stash func (c *GitCommand) ListStash() (string, error) { return c.OSCommand.runDirectCommand("git stash list") } +// Merge merge func (c *GitCommand) Merge(branchName string) (string, error) { return c.OSCommand.runDirectCommand("git merge --no-edit " + branchName) } +// AbortMerge abort merge func (c *GitCommand) AbortMerge() (string, error) { return c.OSCommand.runDirectCommand("git merge --abort") } -func gitCommit(g *gocui.Gui, message string) (string, error) { +func runSubProcess(g *gocui.Gui, cmdName string, commandArgs ...string) { + subprocess = exec.Command(cmdName, commandArgs...) + subprocess.Stdin = os.Stdin + subprocess.Stdout = os.Stdout + subprocess.Stderr = os.Stderr + + g.Update(func(g *gocui.Gui) error { + return ErrSubprocess + }) +} + +// GitCommit commit to git +func (c *GitCommand) GitCommit(g *gocui.Gui, message string) (string, error) { gpgsign, _ := gitconfig.Global("commit.gpgsign") if gpgsign != "" { runSubProcess(g, "git", "commit") @@ -179,19 +308,25 @@ func gitCommit(g *gocui.Gui, message string) (string, error) { return "", nil } -func gitPull() (string, error) { - return runDirectCommand("git pull --no-edit") +// GitPull pull from repo +func (c *GitCommand) GitPull() (string, error) { + return c.OSCommand.RunCommand("git pull --no-edit") } -func gitPush() (string, error) { - return runDirectCommand("git push -u origin " + state.Branches[0].Name) +// GitPush push to a branch +func (c *GitCommand) GitPush() (string, error) { + return c.OSCommand.RunDirectCommand("git push -u origin " + state.Branches[0].Name) } -func gitSquashPreviousTwoCommits(message string) (string, error) { +// SquashPreviousTwoCommits squashes a commit down to the one below it +// retaining the message of the higher commit +func (c *GitCommand) SquashPreviousTwoCommits(message string) (string, error) { return runDirectCommand("git reset --soft HEAD^ && git commit --amend -m \"" + message + "\"") } -func gitSquashFixupCommit(branchName string, shaValue string) (string, error) { +// SquashFixupCommit squashes a 'FIXUP' commit into the commit beneath it, +// retaining the commit message of the lower commit +func (c *GitCommand) SquashFixupCommit(branchName string, shaValue string) (string, error) { var err error commands := []string{ "git checkout -q " + shaValue, @@ -202,7 +337,7 @@ func gitSquashFixupCommit(branchName string, shaValue string) (string, error) { ret := "" for _, command := range commands { devLog(command) - output, err := runDirectCommand(command) + output, err := c.OSCommand.runDirectCommand(command) ret += output if err != nil { devLog(ret) @@ -212,8 +347,110 @@ func gitSquashFixupCommit(branchName string, shaValue string) (string, error) { if err != nil { // We are already in an error state here so we're just going to append // the output of these commands - ret += runDirectCommandIgnoringError("git branch -d " + shaValue) - ret += runDirectCommandIgnoringError("git checkout " + branchName) + output, _ = c.OSCommand.RunDirectCommand("git branch -d " + shaValue) + ret += output + output, _ = c.OSCommand.RunDirectCommand("git checkout " + branchName) + ret += output } return ret, err } + +// CatFile obtain the contents of a file +func (c *GitCommand) CatFile(file string) (string, error) { + return c.OSCommand.runDirectCommand("cat " + file) +} + +// StageFile stages a file +func (c *GitCommand) StageFile(file string) error { + _, err := c.OSCommand.runCommand("git add " + file) + return err +} + +// UnStageFile unstages a file +func (c *GitCommand) UnStageFile(file string, tracked bool) error { + var command string + if tracked { + command = "git reset HEAD " + } else { + command = "git rm --cached " + } + _, err := c.OSCommand.runCommand(command + file) + return err +} + +// GitStatus returns the plaintext short status of the repo +func (c *GitCommand) GitStatus() (string, error) { + return c.OSCommand.runCommand("git status --untracked-files=all --short") +} + +// IsInMergeState states whether we are still mid-merge +func (c *GitCommand) IsInMergeState() (bool, error) { + output, err := c.OSCommand.runCommand("git status --untracked-files=all") + if err != nil { + return false, err + } + return strings.Contains(output, "conclude merge") || strings.Contains(output, "unmerged paths"), nil +} + +// RemoveFile directly +func (c *GitCommand) RemoveFile(file GitFile) error { + // if the file isn't tracked, we assume you want to delete it + if !file.Tracked { + _, err := c.OSCommand.runCommand("rm -rf ./" + file.Name) + return err + } + // if the file is tracked, we assume you want to just check it out + _, err := c.OSCommand.runCommand("git checkout " + file.Name) + return err +} + +// Checkout checks out a branch, with --force if you set the force arg to true +func (c *GitCommand) Checkout(branch string, force bool) (string, error) { + forceArg := "" + if force { + forceArg = "--force " + } + return c.OSCommand.runCommand("git checkout " + forceArg + branch) +} + +// AddPatch runs a subprocess for adding a patch by patch +// this will eventually be swapped out for a better solution inside the Gui +func (c *GitCommand) AddPatch(g *gocui.Gui, filename string) { + runSubProcess(g, "git", "add", "--patch", filename) +} + +// 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) { + return c.OSCommand.runCommand("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branchName) +} + +// map (from https://gobyexample.com/collection-functions) +func map(vs []string, f func(string) string) []string { + vsm := make([]string, len(vs)) + for i, v := range vs { + vsm[i] = f(v) + } + return vsm +} + +func includesString(list []string, a string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +// not sure how to genericise this because []interface{} doesn't accept e.g. +// []int arguments +func includesInt(list []int, a int) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} diff --git a/pkg/commands/gitcommands.go b/pkg/commands/gitcommands.go deleted file mode 100644 index f6eb4a357..000000000 --- a/pkg/commands/gitcommands.go +++ /dev/null @@ -1,449 +0,0 @@ -package commands - -import ( - - // "log" - "errors" - "fmt" - "os" - "os/exec" - "strings" - "time" - - "github.com/jesseduffield/gocui" - gitconfig "github.com/tcnksm/go-gitconfig" - git "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing/object" -) - -var ( - // ErrNoOpenCommand : When we don't know which command to use to open a file - ErrNoOpenCommand = errors.New("Unsure what command to use to open this file") -) - -// Map (from https://gobyexample.com/collection-functions) -func Map(vs []string, f func(string) string) []string { - vsm := make([]string, len(vs)) - for i, v := range vs { - vsm[i] = f(v) - } - return vsm -} - -func includesString(list []string, a string) bool { - for _, b := range list { - if b == a { - return true - } - } - return false -} - -// not sure how to genericise this because []interface{} doesn't accept e.g. -// []int arguments -func includesInt(list []int, a int) bool { - for _, b := range list { - if b == a { - return true - } - } - return false -} - -func mergeGitStatusFiles(oldGitFiles, newGitFiles []GitFile) []GitFile { - if len(oldGitFiles) == 0 { - return newGitFiles - } - - appendedIndexes := make([]int, 0) - - // retain position of files we already could see - result := make([]GitFile, 0) - for _, oldGitFile := range oldGitFiles { - for newIndex, newGitFile := range newGitFiles { - if oldGitFile.Name == newGitFile.Name { - result = append(result, newGitFile) - appendedIndexes = append(appendedIndexes, newIndex) - break - } - } - } - - // append any new files to the end - for index, newGitFile := range newGitFiles { - if !includesInt(appendedIndexes, index) { - result = append(result, newGitFile) - } - } - - return result -} - -// only to be used when you're already in an error state -func runDirectCommandIgnoringError(command string) string { - output, _ := runDirectCommand(command) - return output -} - -func runDirectCommand(command string) (string, error) { - commandLog(command) - - cmdOut, err := exec. - Command(state.Platform.shell, state.Platform.shellArg, command). - CombinedOutput() - return sanitisedCommandOutput(cmdOut, err) -} - -func branchStringParts(branchString string) (string, string) { - // expect string to be something like '4w master` - splitBranchName := strings.Split(branchString, "\t") - // if we have no \t then we have no recency, so just output that as blank - if len(splitBranchName) == 1 { - return "", branchString - } - return splitBranchName[0], splitBranchName[1] -} - -// TODO: DRY up this function and getGitBranches -func getGitStashEntries() []StashEntry { - stashEntries := make([]StashEntry, 0) - rawString, _ := runDirectCommand("git stash list --pretty='%gs'") - for i, line := range splitLines(rawString) { - stashEntries = append(stashEntries, stashEntryFromLine(line, i)) - } - return stashEntries -} - -func stashEntryFromLine(line string, index int) StashEntry { - return StashEntry{ - Name: line, - Index: index, - DisplayString: line, - } -} - -func getStashEntryDiff(index int) (string, error) { - return runCommand("git stash show -p --color stash@{" + fmt.Sprint(index) + "}") -} - -func includes(array []string, str string) bool { - for _, arrayStr := range array { - if arrayStr == str { - return true - } - } - return false -} - -func getGitStatusFiles() []GitFile { - statusOutput, _ := getGitStatus() - statusStrings := splitLines(statusOutput) - gitFiles := make([]GitFile, 0) - - for _, statusString := range statusStrings { - change := statusString[0:2] - stagedChange := change[0:1] - unstagedChange := statusString[1:2] - filename := statusString[3:] - tracked := !includes([]string{"??", "A "}, change) - gitFile := GitFile{ - Name: filename, - DisplayString: statusString, - HasStagedChanges: !includes([]string{" ", "U", "?"}, stagedChange), - HasUnstagedChanges: unstagedChange != " ", - Tracked: tracked, - Deleted: unstagedChange == "D" || stagedChange == "D", - HasMergeConflicts: change == "UU", - } - gitFiles = append(gitFiles, gitFile) - } - objectLog(gitFiles) - return gitFiles -} - -func gitStashDo(index int, method string) (string, error) { - return runCommand("git stash " + method + " stash@{" + fmt.Sprint(index) + "}") -} - -func gitStashSave(message string) (string, error) { - output, err := runCommand("git stash save \"" + message + "\"") - if err != nil { - return output, err - } - // if there are no local changes to save, the exit code is 0, but we want - // to raise an error - if output == "No local changes to save\n" { - return output, errors.New(output) - } - return output, nil -} - -func gitCheckout(branch string, force bool) (string, error) { - forceArg := "" - if force { - forceArg = "--force " - } - return runCommand("git checkout " + forceArg + branch) -} - -func sanitisedCommandOutput(output []byte, err error) (string, error) { - outputString := string(output) - if outputString == "" && err != nil { - return err.Error(), err - } - return outputString, err -} - -func runCommand(command string) (string, error) { - commandLog(command) - splitCmd := strings.Split(command, " ") - cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput() - return sanitisedCommandOutput(cmdOut, err) -} - -func vsCodeOpenFile(g *gocui.Gui, filename string) (string, error) { - return runCommand("code -r " + filename) -} - -func sublimeOpenFile(g *gocui.Gui, filename string) (string, error) { - return runCommand("subl " + filename) -} - -func openFile(g *gocui.Gui, filename string) (string, error) { - cmdName, cmdTrail, err := getOpenCommand() - if err != nil { - return "", err - } - return runCommand(cmdName + " " + filename + cmdTrail) -} - -func getOpenCommand() (string, string, error) { - //NextStep open equivalents: xdg-open (linux), cygstart (cygwin), open (OSX) - trailMap := map[string]string{ - "xdg-open": " &>/dev/null &", - "cygstart": "", - "open": "", - } - for name, trail := range trailMap { - if out, _ := runCommand("which " + name); out != "exit status 1" { - return name, trail, nil - } - } - return "", "", ErrNoOpenCommand -} - -func gitAddPatch(g *gocui.Gui, filename string) { - runSubProcess(g, "git", "add", "--patch", filename) -} - -func editFile(g *gocui.Gui, filename string) (string, error) { - editor, _ := gitconfig.Global("core.editor") - if editor == "" { - editor = os.Getenv("VISUAL") - } - if editor == "" { - editor = os.Getenv("EDITOR") - } - if editor == "" { - if _, err := runCommand("which vi"); err == nil { - editor = "vi" - } - } - if editor == "" { - return "", createErrorPanel(g, "No editor defined in $VISUAL, $EDITOR, or git config.") - } - runSubProcess(g, editor, filename) - return "", nil -} - -func runSubProcess(g *gocui.Gui, cmdName string, commandArgs ...string) { - subprocess = exec.Command(cmdName, commandArgs...) - subprocess.Stdin = os.Stdin - subprocess.Stdout = os.Stdout - subprocess.Stderr = os.Stderr - - g.Update(func(g *gocui.Gui) error { - return ErrSubprocess - }) -} - -func getBranchGraph(branch string) (string, error) { - return runCommand("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branch) -} - -func verifyInGitRepo() { - if output, err := runCommand("git status"); err != nil { - fmt.Println(output) - os.Exit(1) - } -} - -func getCommits() []Commit { - pushables := git.GetCommitsToPush() - log := getLog() - commits := make([]Commit, 0) - // now we can split it up and turn it into commits - lines := splitLines(log) - for _, line := range lines { - splitLine := strings.Split(line, " ") - sha := splitLine[0] - pushed := includesString(pushables, sha) - commits = append(commits, Commit{ - Sha: sha, - Name: strings.Join(splitLine[1:], " "), - Pushed: pushed, - DisplayString: strings.Join(splitLine, " "), - }) - } - return commits -} - -func getLog() string { - // currently limiting to 30 for performance reasons - // TODO: add lazyloading when you scroll down - result, err := runDirectCommand("git log --oneline -30") - if err != nil { - // assume if there is an error there are no commits yet for this branch - return "" - } - return result -} - -func gitIgnore(filename string) { - if _, err := runDirectCommand("echo '" + filename + "' >> .gitignore"); err != nil { - panic(err) - } -} - -func gitShow(sha string) string { - result, err := runDirectCommand("git show --color " + sha) - if err != nil { - panic(err) - } - return result -} - -func getDiff(file GitFile) string { - cachedArg := "" - if file.HasStagedChanges && !file.HasUnstagedChanges { - cachedArg = "--cached " - } - deletedArg := "" - if file.Deleted { - deletedArg = "-- " - } - trackedArg := "" - if !file.Tracked && !file.HasStagedChanges { - trackedArg = "--no-index /dev/null " - } - command := "git diff --color " + cachedArg + deletedArg + trackedArg + file.Name - // for now we assume an error means the file was deleted - s, _ := runCommand(command) - return s -} - -func catFile(file string) (string, error) { - return runDirectCommand("cat " + file) -} - -func stageFile(file string) error { - _, err := runCommand("git add " + file) - return err -} - -func unStageFile(file string, tracked bool) error { - var command string - if tracked { - command = "git reset HEAD " - } else { - command = "git rm --cached " - } - _, err := runCommand(command + file) - return err -} - -func getGitStatus() (string, error) { - return runCommand("git status --untracked-files=all --short") -} - -func isInMergeState() (bool, error) { - output, err := runCommand("git status --untracked-files=all") - if err != nil { - return false, err - } - return strings.Contains(output, "conclude merge") || strings.Contains(output, "unmerged paths"), nil -} - -func removeFile(file GitFile) error { - // if the file isn't tracked, we assume you want to delete it - if !file.Tracked { - _, err := runCommand("rm -rf ./" + file.Name) - return err - } - // if the file is tracked, we assume you want to just check it out - _, err := runCommand("git checkout " + file.Name) - return err -} - -func gitCommit(g *gocui.Gui, message string) (string, error) { - gpgsign, _ := gitconfig.Global("commit.gpgsign") - if gpgsign != "" { - runSubProcess(g, "git", "commit") - return "", nil - } - userName, err := gitconfig.Username() - if userName == "" { - return "", errNoUsername - } - userEmail, err := gitconfig.Email() - _, err = w.Commit(message, &git.CommitOptions{ - Author: &object.Signature{ - Name: userName, - Email: userEmail, - When: time.Now(), - }, - }) - if err != nil { - return err.Error(), err - } - return "", nil -} - -func gitPull() (string, error) { - return runDirectCommand("git pull --no-edit") -} - -func gitPush() (string, error) { - return runDirectCommand("git push -u origin " + state.Branches[0].Name) -} - -func gitSquashPreviousTwoCommits(message string) (string, error) { - return runDirectCommand("git reset --soft HEAD^ && git commit --amend -m \"" + message + "\"") -} - -func gitSquashFixupCommit(branchName string, shaValue string) (string, error) { - var err error - commands := []string{ - "git checkout -q " + shaValue, - "git reset --soft " + shaValue + "^", - "git commit --amend -C " + shaValue + "^", - "git rebase --onto HEAD " + shaValue + " " + branchName, - } - ret := "" - for _, command := range commands { - devLog(command) - output, err := runDirectCommand(command) - ret += output - if err != nil { - devLog(ret) - break - } - } - if err != nil { - // We are already in an error state here so we're just going to append - // the output of these commands - ret += runDirectCommandIgnoringError("git branch -d " + shaValue) - ret += runDirectCommandIgnoringError("git checkout " + branchName) - } - return ret, err -} diff --git a/pkg/commands/os.go b/pkg/commands/os.go index c36990aff..4e7c136a9 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -1,11 +1,20 @@ package commands import ( + "errors" + "os" "os/exec" "runtime" "strings" "github.com/Sirupsen/logrus" + "github.com/jesseduffield/gocui" + gitconfig "github.com/tcnksm/go-gitconfig" +) + +var ( + // ErrNoOpenCommand : When we don't know which command to use to open a file + ErrNoOpenCommand = errors.New("Unsure what command to use to open this file") ) // Platform stores the os state @@ -75,3 +84,61 @@ func getPlatform() platform { } } } + +func (c *OSCommand) getOpenCommand() (string, string, error) { + //NextStep open equivalents: xdg-open (linux), cygstart (cygwin), open (OSX) + trailMap := map[string]string{ + "xdg-open": " &>/dev/null &", + "cygstart": "", + "open": "", + } + for name, trail := range trailMap { + if out, _ := c.runCommand("which " + name); out != "exit status 1" { + return name, trail, nil + } + } + return "", "", ErrNoOpenCommand +} + +// VsCodeOpenFile opens the file in code, with the -r flag to open in the +// current window +func (c *OSCommand) VsCodeOpenFile(g *gocui.Gui, filename string) (string, error) { + return c.runCommand("code -r " + filename) +} + +// SublimeOpenFile opens the filein sublime +// may be deprecated in the future +func (c *OSCommand) SublimeOpenFile(g *gocui.Gui, filename string) (string, error) { + return c.runCommand("subl " + filename) +} + +// OpenFile opens a file with the given +func (c *OSCommand) OpenFile(g *gocui.Gui, filename string) (string, error) { + cmdName, cmdTrail, err := getOpenCommand() + if err != nil { + return "", err + } + return c.runCommand(cmdName + " " + filename + cmdTrail) +} + +// EditFile opens a file in a subprocess using whatever editor is available, +// falling back to core.editor, VISUAL, EDITOR, then vi +func (c *OSCommand) editFile(g *gocui.Gui, filename string) (string, error) { + editor, _ := gitconfig.Global("core.editor") + if editor == "" { + editor = os.Getenv("VISUAL") + } + if editor == "" { + editor = os.Getenv("EDITOR") + } + if editor == "" { + if _, err := c.OSCommand.runCommand("which vi"); err == nil { + editor = "vi" + } + } + if editor == "" { + return "", createErrorPanel(g, "No editor defined in $VISUAL, $EDITOR, or git config.") + } + runSubProcess(g, editor, filename) + return "", nil +} |