summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--branches_panel.go142
-rw-r--r--files_panel.go424
-rw-r--r--gitcommands.go638
-rw-r--r--gui.go263
-rw-r--r--main.go78
-rw-r--r--merge_panel.go260
-rw-r--r--newFile1
-rw-r--r--newFile21
-rw-r--r--stash_panel.go114
-rwxr-xr-xtest/generate_basic_repo.sh37
-rw-r--r--view_helpers.go289
12 files changed, 1191 insertions, 1057 deletions
diff --git a/.gitignore b/.gitignore
index a1a4f660d..5210291e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ commands.log
extra/lgit.rb
notes/go.notes
TODO.notes
+TODO.md
diff --git a/branches_panel.go b/branches_panel.go
index 12f2394a6..8b84ee908 100644
--- a/branches_panel.go
+++ b/branches_panel.go
@@ -1,17 +1,21 @@
package main
import (
- "fmt"
+ "fmt"
- "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/gocui"
)
func handleBranchPress(g *gocui.Gui, v *gocui.View) error {
- branch := getSelectedBranch(v)
- if output, err := gitCheckout(branch.Name, false); err != nil {
- createErrorPanel(g, output)
- }
- return refreshSidePanels(g)
+ index := getItemPosition(v)
+ if index == 0 {
+ return createErrorPanel(g, "You have already checked out this branch")
+ }
+ branch := getSelectedBranch(v)
+ if output, err := gitCheckout(branch.Name, false); err != nil {
+ createErrorPanel(g, output)
+ }
+ return refreshSidePanels(g)
}
func handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
@@ -25,87 +29,87 @@ func handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
}
func handleCheckoutByName(g *gocui.Gui, v *gocui.View) error {
- createPromptPanel(g, v, "Branch Name:", func(g *gocui.Gui, v *gocui.View) error {
- if output, err := gitCheckout(trimmedContent(v), false); err != nil {
- return createErrorPanel(g, output)
- }
- return refreshSidePanels(g)
- })
- return nil
+ createPromptPanel(g, v, "Branch Name:", func(g *gocui.Gui, v *gocui.View) error {
+ if output, err := gitCheckout(trimmedContent(v), false); err != nil {
+ return createErrorPanel(g, output)
+ }
+ return refreshSidePanels(g)
+ })
+ return nil
}
func handleNewBranch(g *gocui.Gui, v *gocui.View) error {
- branch := state.Branches[0]
- createPromptPanel(g, v, "New Branch Name (Branch is off of "+branch.Name+")", func(g *gocui.Gui, v *gocui.View) error {
- if output, err := gitNewBranch(trimmedContent(v)); err != nil {
- return createErrorPanel(g, output)
- }
- refreshSidePanels(g)
- return handleBranchSelect(g, v)
- })
- return nil
+ branch := state.Branches[0]
+ createPromptPanel(g, v, "New Branch Name (Branch is off of "+branch.Name+")", func(g *gocui.Gui, v *gocui.View) error {
+ if output, err := gitNewBranch(trimmedContent(v)); err != nil {
+ return createErrorPanel(g, output)
+ }
+ refreshSidePanels(g)
+ return handleBranchSelect(g, v)
+ })
+ return nil
}
func handleMerge(g *gocui.Gui, v *gocui.View) error {
- checkedOutBranch := state.Branches[0]
- selectedBranch := getSelectedBranch(v)
- defer refreshSidePanels(g)
- if checkedOutBranch.Name == selectedBranch.Name {
- return createErrorPanel(g, "You cannot merge a branch into itself")
- }
- if output, err := gitMerge(selectedBranch.Name); err != nil {
- return createErrorPanel(g, output)
- }
- return nil
+ checkedOutBranch := state.Branches[0]
+ selectedBranch := getSelectedBranch(v)
+ defer refreshSidePanels(g)
+ if checkedOutBranch.Name == selectedBranch.Name {
+ return createErrorPanel(g, "You cannot merge a branch into itself")
+ }
+ if output, err := gitMerge(selectedBranch.Name); err != nil {
+ return createErrorPanel(g, output)
+ }
+ return nil
}
func getSelectedBranch(v *gocui.View) Branch {
- lineNumber := getItemPosition(v)
- return state.Branches[lineNumber]
+ lineNumber := getItemPosition(v)
+ return state.Branches[lineNumber]
}
func renderBranchesOptions(g *gocui.Gui) error {
- return renderOptionsMap(g, map[string]string{
- "space": "checkout",
- "f": "force checkout",
- "m": "merge",
- "c": "checkout by name",
- "n": "checkout new branch",
- })
+ return renderOptionsMap(g, map[string]string{
+ "space": "checkout",
+ "f": "force checkout",
+ "m": "merge",
+ "c": "checkout by name",
+ "n": "new branch",
+ })
}
// may want to standardise how these select methods work
func handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
- if err := renderBranchesOptions(g); err != nil {
- return err
- }
- // This really shouldn't happen: there should always be a master branch
- if len(state.Branches) == 0 {
- return renderString(g, "main", "No branches for this repo")
- }
- go func() {
- branch := getSelectedBranch(v)
- diff, _ := getBranchDiff(branch.Name, branch.BaseBranch)
- renderString(g, "main", diff)
- }()
- return nil
+ if err := renderBranchesOptions(g); err != nil {
+ return err
+ }
+ // This really shouldn't happen: there should always be a master branch
+ if len(state.Branches) == 0 {
+ return renderString(g, "main", "No branches for this repo")
+ }
+ go func() {
+ branch := getSelectedBranch(v)
+ diff, _ := getBranchDiff(branch.Name, branch.BaseBranch)
+ renderString(g, "main", diff)
+ }()
+ return nil
}
// refreshStatus is called at the end of this because that's when we can
// be sure there is a state.Branches array to pick the current branch from
func refreshBranches(g *gocui.Gui) error {
- g.Update(func(g *gocui.Gui) error {
- v, err := g.View("branches")
- if err != nil {
- panic(err)
- }
- state.Branches = getGitBranches()
- v.Clear()
- for _, branch := range state.Branches {
- fmt.Fprintln(v, branch.DisplayString)
- }
- resetOrigin(v)
- return refreshStatus(g)
- })
- return nil
+ g.Update(func(g *gocui.Gui) error {
+ v, err := g.View("branches")
+ if err != nil {
+ panic(err)
+ }
+ state.Branches = getGitBranches()
+ v.Clear()
+ for _, branch := range state.Branches {
+ fmt.Fprintln(v, branch.DisplayString)
+ }
+ resetOrigin(v)
+ return refreshStatus(g)
+ })
+ return nil
}
diff --git a/files_panel.go b/files_panel.go
index cb15e8b3b..f29cbb75a 100644
--- a/files_panel.go
+++ b/files_panel.go
@@ -2,65 +2,65 @@ package main
import (
- // "io"
- // "io/ioutil"
+ // "io"
+ // "io/ioutil"
- // "strings"
+ // "strings"
- "errors"
- "strings"
+ "errors"
+ "strings"
- "github.com/fatih/color"
- "github.com/jesseduffield/gocui"
+ "github.com/fatih/color"
+ "github.com/jesseduffield/gocui"
)
var (
- // ErrNoFiles : when there are no modified files in the repo
- ErrNoFiles = errors.New("No changed files")
+ // ErrNoFiles : when there are no modified files in the repo
+ ErrNoFiles = errors.New("No changed files")
)
func stagedFiles(files []GitFile) []GitFile {
- result := make([]GitFile, 0)
- for _, file := range files {
- if file.HasStagedChanges {
- result = append(result, file)
- }
- }
- return result
+ result := make([]GitFile, 0)
+ for _, file := range files {
+ if file.HasStagedChanges {
+ result = append(result, file)
+ }
+ }
+ return result
}
func handleFilePress(g *gocui.Gui, v *gocui.View) error {
- file, err := getSelectedFile(g)
- if err != nil {
- return err
- }
+ file, err := getSelectedFile(g)
+ if err != nil {
+ return err
+ }
- if file.HasUnstagedChanges {
- stageFile(file.Name)
- } else {
- unStageFile(file.Name)
- }
+ if file.HasUnstagedChanges {
+ stageFile(file.Name)
+ } else {
+ unStageFile(file.Name, file.Tracked)
+ }
- if err := refreshFiles(g); err != nil {
- return err
- }
- if err := handleFileSelect(g, v); err != nil {
- return err
- }
+ if err := refreshFiles(g); err != nil {
+ return err
+ }
+ if err := handleFileSelect(g, v); err != nil {
+ return err
+ }
- return nil
+ return nil
}
func getSelectedFile(g *gocui.Gui) (GitFile, error) {
- if len(state.GitFiles) == 0 {
- return GitFile{}, ErrNoFiles
- }
- filesView, err := g.View("files")
- if err != nil {
- panic(err)
- }
- lineNumber := getItemPosition(filesView)
- return state.GitFiles[lineNumber], nil
+ if len(state.GitFiles) == 0 {
+ return GitFile{}, ErrNoFiles
+ }
+ filesView, err := g.View("files")
+ if err != nil {
+ panic(err)
+ }
+ lineNumber := getItemPosition(filesView)
+ return state.GitFiles[lineNumber], nil
}
func handleFileRemove(g *gocui.Gui, v *gocui.View) error {
@@ -83,93 +83,96 @@ func handleFileRemove(g *gocui.Gui, v *gocui.View) error {
}
func handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
- file, err := getSelectedFile(g)
- if err != nil {
- return err
- }
- if file.Tracked {
- return createErrorPanel(g, "Cannot ignore tracked files")
- }
- gitIgnore(file.Name)
- return refreshFiles(g)
+ file, err := getSelectedFile(g)
+ if err != nil {
+ return err
+ }
+ if file.Tracked {
+ return createErrorPanel(g, "Cannot ignore tracked files")
+ }
+ gitIgnore(file.Name)
+ return refreshFiles(g)
}
func renderfilesOptions(g *gocui.Gui, gitFile *GitFile) error {
- optionsMap := map[string]string{
- "tab": "next panel",
- "S": "stash files",
- "c": "commit changes",
- "o": "open",
- "s": "open in sublime",
- "i": "ignore",
- "d": "delete",
- "space": "toggle staged",
- }
- if state.HasMergeConflicts {
- optionsMap["a"] = "abort merge"
- optionsMap["m"] = "resolve merge conflicts"
- }
- if gitFile == nil {
- return renderOptionsMap(g, optionsMap)
- }
- if gitFile.Tracked {
- optionsMap["d"] = "checkout"
- }
- return renderOptionsMap(g, optionsMap)
+ optionsMap := map[string]string{
+ "tab": "next panel",
+ "S": "stash files",
+ "c": "commit changes",
+ "o": "open",
+ "s": "open in sublime",
+ "v": "open in vscode",
+ "i": "ignore",
+ "d": "delete",
+ "space": "toggle staged",
+ }
+ if state.HasMergeConflicts {
+ optionsMap["a"] = "abort merge"
+ optionsMap["m"] = "resolve merge conflicts"
+ }
+ if gitFile == nil {
+ return renderOptionsMap(g, optionsMap)
+ }
+ if gitFile.Tracked {
+ optionsMap["d"] = "checkout"
+ }
+ return renderOptionsMap(g, optionsMap)
}
func handleFileSelect(g *gocui.Gui, v *gocui.View) error {
- gitFile, err := getSelectedFile(g)
- if err != nil {
- if err != ErrNoFiles {
- return err
- }
- renderString(g, "main", "No changed files")
- colorLog(color.FgRed, "error")
- return renderfilesOptions(g, nil)
- }
- renderfilesOptions(g, &gitFile)
- var content string
- if gitFile.HasMergeConflicts {
- return refreshMergePanel(g)
- }
+ gitFile, err := getSelectedFile(g)
+ if err != nil {
+ if err != ErrNoFiles {
+ return err
+ }
+ renderString(g, "main", "No changed files")
+ return renderfilesOptions(g, nil)
+ }
+ renderfilesOptions(g, &gitFile)
+ var content string
+ if gitFile.HasMergeConflicts {
+ return refreshMergePanel(g)
+ }
- content = getDiff(gitFile)
- return renderString(g, "main", content)
+ content = getDiff(gitFile)
+ return renderString(g, "main", content)
}
func handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
- if len(stagedFiles(state.GitFiles)) == 0 && !state.HasMergeConflicts {
- return createErrorPanel(g, "There are no staged files to commit")
- }
- createPromptPanel(g, filesView, "Commit message", func(g *gocui.Gui, v *gocui.View) error {
- message := trimmedContent(v)
- if message == "" {
- return createErrorPanel(g, "You cannot commit without a commit message")
- }
- if err := gitCommit(message); err != nil {
- panic(err)
- }
- refreshFiles(g)
- return refreshCommits(g)
- })
- return nil
+ if len(stagedFiles(state.GitFiles)) == 0 && !state.HasMergeConflicts {
+ return createErrorPanel(g, "There are no staged files to commit")
+ }
+ createPromptPanel(g, filesView, "Commit message", func(g *gocui.Gui, v *gocui.View) error {
+ message := trimmedContent(v)
+ if message == "" {
+ return createErrorPanel(g, "You cannot commit without a commit message")
+ }
+ if err := gitCommit(message); err != nil {
+ panic(err)
+ }
+ refreshFiles(g)
+ return refreshCommits(g)
+ })
+ return nil
}
func genericFileOpen(g *gocui.Gui, v *gocui.View, open func(string) (string, error)) error {
- file, err := getSelectedFile(g)
- if err != nil {
- return err
- }
- _, err = open(file.Name)
- return err
+ file, err := getSelectedFile(g)
+ if err != nil {
+ return err
+ }
+ _, err = open(file.Name)
+ return err
}
func handleFileOpen(g *gocui.Gui, v *gocui.View) error {
- return genericFileOpen(g, v, openFile)
+ return genericFileOpen(g, v, openFile)
}
func handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error {
- return genericFileOpen(g, v, sublimeOpenFile)
+ return genericFileOpen(g, v, sublimeOpenFile)
+}
+func handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error {
+ return genericFileOpen(g, v, vsCodeOpenFile)
}
func handleRefreshFiles(g *gocui.Gui, v *gocui.View) error {
@@ -177,128 +180,129 @@ func handleRefreshFiles(g *gocui.Gui, v *gocui.View) error {
}
func refreshStateGitFiles() {
- // get files to stage
- gitFiles := getGitStatusFiles()
- state.GitFiles = mergeGitStatusFiles(state.GitFiles, gitFiles)
- updateHasMergeConflictStatus()
+ // get files to stage
+ gitFiles := getGitStatusFiles()
+ state.GitFiles = mergeGitStatusFiles(state.GitFiles, gitFiles)
+ updateHasMergeConflictStatus()
}
func updateHasMergeConflictStatus() error {
- merging, err := isInMergeState()
- if err != nil {
- return err
- }
- state.HasMergeConflicts = merging
- return nil
+ merging, err := isInMergeState()
+ if err != nil {
+ return err
+ }
+ state.HasMergeConflicts = merging
+ return nil
}
func renderGitFile(gitFile GitFile, filesView *gocui.View) {
- // potentially inefficient to be instantiating these color
- // objects with each render
- red := color.New(color.FgRed)
- green := color.New(color.FgGreen)
- if !gitFile.Tracked {
- red.Fprintln(filesView, gitFile.DisplayString)
- return
- }
- green.Fprint(filesView, gitFile.DisplayString[0:1])
- red.Fprint(filesView, gitFile.DisplayString[1:3])
- if gitFile.HasUnstagedChanges {
- red.Fprintln(filesView, gitFile.Name)
- } else {
- green.Fprintln(filesView, gitFile.Name)
- }
+ // potentially inefficient to be instantiating these color
+ // objects with each render
+ red := color.New(color.FgRed)
+ green := color.New(color.FgGreen)
+ if !gitFile.Tracked && !gitFile.HasStagedChanges {
+ red.Fprintln(filesView, gitFile.DisplayString)
+ return
+ }
+ green.Fprint(filesView, gitFile.DisplayString[0:1])
+ red.Fprint(filesView, gitFile.DisplayString[1:3])
+ if gitFile.HasUnstagedChanges {
+ red.Fprintln(filesView, gitFile.Name)
+ } else {
+ green.Fprintln(filesView, gitFile.Name)
+ }
}
func catSelectedFile(g *gocui.Gui) (string, error) {
- item, err := getSelectedFile(g)
- if err != nil {
- if err != ErrNoFiles {
- return "", err
- }
- return "", renderString(g, "main", "No file to display")
- }
- cat, err := catFile(item.Name)
- if err != nil {
- panic(err)
- }
- return cat, nil
+ item, err := getSelectedFile(g)
+ if err != nil {
+ if err != ErrNoFiles {
+ return "", err
+ }
+ return "", renderString(g, "main", "No file to display")
+ }
+ cat, err := catFile(item.Name)
+ if err != nil {
+ panic(err)
+ }
+ return cat, nil
}
func refreshFiles(g *gocui.Gui) error {
- filesView, err := g.View("files")
- if err != nil {
- return err
- }
- refreshStateGitFiles()
- filesView.Clear()
- for _, gitFile := range state.GitFiles {
- renderGitFile(gitFile, filesView)
- }
- correctCursor(filesView)
- if filesView == g.CurrentView() {
- handleFileSelect(g, filesView)
- }
- return nil
+ filesView, err := g.View("files")
+ if err != nil {
+ return err
+ }
+ refreshStateGitFiles()
+ filesView.Clear()
+ for _, gitFile := range state.GitFiles {
+ renderGitFile(gitFile, filesView)
+ }
+ correctCursor(filesView)
+ if filesView == g.CurrentView() {
+ handleFileSelect(g, filesView)
+ }
+ return nil
}
func pullFiles(g *gocui.Gui, v *gocui.View) error {
- devLog("pulling...")
- createMessagePanel(g, v, "", "Pulling...")
- go func() {
- if output, err := gitPull(); err != nil {
- createErrorPanel(g, output)
- } else {
- closeConfirmationPrompt(g)
- refreshCommits(g)
- refreshFiles(g)
- refreshStatus(g)
- devLog("pulled.")
- }
- }()
- return nil
+ devLog("pulling...")
+ createMessagePanel(g, v, "", "Pulling...")
+ go func() {
+ if output, err := gitPull(); err != nil {
+ createErrorPanel(g, output)
+ } else {
+ closeConfirmationPrompt(g)
+ refreshCommits(g)
+ refreshStatus(g)
+ devLog("pulled.")
+ }
+ refreshFiles(g)
+ }()
+ return nil
}
func pushFiles(g *gocui.Gui, v *gocui.View) error {
- devLog("pushing...")
- createMessagePanel(g, v, "", "Pushing...")
- go func() {
- if output, err := gitPush(); err != nil {
- createErrorPanel(g, output)
- } else {
- closeConfirmationPrompt(g)
- refreshCommits(g)
- refreshStatus(g)
- devLog("pushed.")
- }
- }()
- return nil
+ devLog("pushing...")
+ createMessagePanel(g, v, "", "Pushing...")
+ go func() {
+ if output, err := gitPush(); err != nil {
+ createErrorPanel(g, output)
+ } else {
+ closeConfirmationPrompt(g)
+ refreshCommits(g)
+ refreshStatus(g)
+ devLog("pushed.")
+ }
+ }()
+ return nil
}
func handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
- mergeView, err := g.View("main")
- if err != nil {
- return err
- }
- file, err := getSelectedFile(g)
- if err != nil {
- if err != ErrNoFiles {
- return err
- }
- return nil
- }
- if !file.HasMergeConflicts {
- return nil
- }
- switchFocus(g, v, mergeView)
- return refreshMergePanel(g)
+ mergeView, err := g.View("main")
+ if err != nil {
+ return err
+ }
+ file, err := getSelectedFile(g)
+ if err != nil {
+ if err != ErrNoFiles {
+ return err
+ }
+ return nil
+ }
+ if !file.HasMergeConflicts {
+ return nil
+ }
+ switchFocus(g, v, mergeView)
+ return refreshMergePanel(g)
}
func handleAbortMerge(g *gocui.Gui, v *gocui.View) error {
- output, err := gitAbortMerge()
- if err != nil {
- return createErrorPanel(g, output)
- }
- createMessagePanel(g, v, "", "Merge aborted")
- return refreshFiles(g)
+ output, err := gitAbortMerge()
+ if err != nil {
+ return createErrorPanel(g, output)
+ }
+ createMessagePanel(g, v, "", "Merge aborted")
+ refreshStatus(g)
+ return refreshFiles(g)
}
diff --git a/gitcommands.go b/gitcommands.go
index 7d2651eea..38da938b2 100644
--- a/gitcommands.go
+++ b/gitcommands.go
@@ -2,268 +2,317 @@ package main
import (
- // "log"
- "errors"
- "fmt"
- "os"
- "os/exec"
- "strings"
- "time"
-
- "github.com/fatih/color"
+ // "log"
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+
+ "github.com/fatih/color"
)
// GitFile : A staged/unstaged file
// TODO: decide whether to give all of these the Git prefix
type GitFile struct {
- Name string
- HasStagedChanges bool
- HasUnstagedChanges bool
- Tracked bool
- Deleted bool
- HasMergeConflicts bool
- DisplayString string
+ Name string
+ HasStagedChanges bool
+ HasUnstagedChanges bool
+ Tracked bool
+ Deleted bool
+ HasMergeConflicts bool
+ DisplayString string
}
// Branch : A git branch
type Branch struct {
- Name string
- Type string
- BaseBranch string
- DisplayString string
+ Name string
+ Type string
+ BaseBranch string
+ DisplayString string
}
// Commit : A git commit
type Commit struct {
- Sha string
- Name string
- Pushed bool
- DisplayString string
+ Sha string
+ Name string
+ Pushed bool
+ DisplayString string
}
// StashEntry : A git stash entry
type StashEntry struct {
- Index int
- Name string
- DisplayString string
+ Index int
+ Name string
+ DisplayString string
}
// 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
+ 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
+ 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
+ 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
+ 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 runDirectCommand(command string) (string, error) {
- timeStart := time.Now()
+ timeStart := time.Now()
- commandLog(command)
- cmdOut, err := exec.Command("bash", "-c", command).CombinedOutput()
- devLog("run direct command time for command: ", command, time.Now().Sub(timeStart))
+ commandLog(command)
+ cmdOut, err := exec.
+ Command("bash", "-c", command).
+ CombinedOutput()
+ devLog("run direct command time for command: ", command, time.Now().Sub(timeStart))
- return string(cmdOut), err
+ return string(cmdOut), err
}
func branchStringParts(branchString string) (string, string) {
- splitBranchName := strings.Split(branchString, "\t")
- return splitBranchName[0], splitBranchName[1]
+ // 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]
}
// branchPropertiesFromName : returns branch type, base, and color
func branchPropertiesFromName(name string) (string, string, color.Attribute) {
- if strings.Contains(name, "feature/") {
- return "feature", "develop", color.FgGreen
- } else if strings.Contains(name, "bugfix/") {
- return "bugfix", "develop", color.FgYellow
- } else if strings.Contains(name, "hotfix/") {
- return "hotfix", "master", color.FgRed
- }
- return "other", name, color.FgWhite
+ if strings.Contains(name, "feature/") {
+ return "feature", "develop", color.FgGreen
+ } else if strings.Contains(name, "bugfix/") {
+ return "bugfix", "develop", color.FgYellow
+ } else if strings.Contains(name, "hotfix/") {
+ return "hotfix", "master", color.FgRed
+ }
+ return "other", name, color.FgWhite
}
func coloredString(str string, colour *color.Color) string {
- return colour.SprintFunc()(fmt.Sprint(str))
+ return colour.SprintFunc()(fmt.Sprint(str))
}
func withPadding(str string, padding int) string {
- return str + strings.Repeat(" ", padding-len(str))
+ if padding-len(str) < 0 {
+ return str
+ }
+ return str + strings.Repeat(" ", padding-len(str))
}
func branchFromLine(line string, index int) Branch {
- recency, name := branchStringParts(line)
- branchType, branchBase, colourAttr := branchPropertiesFromName(name)
- if index == 0 {
- recency = " *"
- }
- colour := color.New(colourAttr)
- displayString := withPadding(recency, 4) + coloredString(name, colour)
- return Branch{
- Name: name,
- Type: branchType,
- BaseBranch: branchBase,
- DisplayString: displayString,
- }
+ recency, name := branchStringParts(line)
+ branchType, branchBase, colourAttr := branchPropertiesFromName(name)
+ if index == 0 {
+ recency = " *"
+ }
+ colour := color.New(colourAttr)
+ displayString := withPadding(recency, 4) + coloredString(name, colour)
+ return Branch{
+ Name: name,
+ Type: branchType,
+ BaseBranch: branchBase,
+ DisplayString: displayString,
+ }
}
func getGitBranches() []Branch {
- branches := make([]Branch, 0)
- // check if there are any branches
- branchCheck, _ := runDirectCommand("git branch")
- if branchCheck == "" {
- return branches
- }
- rawString, _ := runDirectCommand(getBranchesCommand)
- branchLines := splitLines(rawString)
- if len(branchLines) == 0 {
- // sometimes the getBranchesCommand command returns nothing, in which case
- // we assume you've just init'd or cloned the repo and you've got master
- // checked out
- branches = append(branches, branchFromLine(" *\tmaster", 0))
- }
- for i, line := range branchLines {
- branches = append(branches, branchFromLine(line, i))
- }
- return branches
+ branches := make([]Branch, 0)
+ // check if there are any branches
+ branchCheck, _ := runDirectCommand("git branch")
+ if branchCheck == "" {
+ return append(branches, branchFromLine("master", 0))
+ }
+ rawString, _ := runDirectCommand(getBranchesCommand)
+ branchLines := splitLines(rawString)
+ for i, line := range branchLines {
+ branches = append(branches, branchFromLine(line, i))
+ }
+ branches = getAndMergeFetchedBranches(branches)
+ return branches
+}
+
+func branchAlreadyStored(branchLine string, branches []Branch) bool {
+ for _, branch := range branches {
+ if branch.Name == branchLine {
+ return true
+ }
+ }
+ return false
+}
+
+// here branches contains all the branches that we've checked out, along with
+// the recency. In this function we