1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-04-21 12:16:54 +02:00

merge with develop

This commit is contained in:
Jesse Duffield 2018-07-21 16:06:38 +10:00
commit a47c889cbb
12 changed files with 1178 additions and 1044 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ commands.log
extra/lgit.rb extra/lgit.rb
notes/go.notes notes/go.notes
TODO.notes TODO.notes
TODO.md

View File

@ -7,6 +7,10 @@ import (
) )
func handleBranchPress(g *gocui.Gui, v *gocui.View) error { func handleBranchPress(g *gocui.Gui, v *gocui.View) error {
index := getItemPosition(v)
if index == 0 {
return createErrorPanel(g, "You have already checked out this branch")
}
branch := getSelectedBranch(v) branch := getSelectedBranch(v)
if output, err := gitCheckout(branch.Name, false); err != nil { if output, err := gitCheckout(branch.Name, false); err != nil {
createErrorPanel(g, output) createErrorPanel(g, output)
@ -70,7 +74,7 @@ func renderBranchesOptions(g *gocui.Gui) error {
"f": "force checkout", "f": "force checkout",
"m": "merge", "m": "merge",
"c": "checkout by name", "c": "checkout by name",
"n": "checkout new branch", "n": "new branch",
}) })
} }

View File

@ -38,7 +38,7 @@ func handleFilePress(g *gocui.Gui, v *gocui.View) error {
if file.HasUnstagedChanges { if file.HasUnstagedChanges {
stageFile(file.Name) stageFile(file.Name)
} else { } else {
unStageFile(file.Name) unStageFile(file.Name, file.Tracked)
} }
if err := refreshFiles(g); err != nil { if err := refreshFiles(g); err != nil {
@ -101,6 +101,7 @@ func renderfilesOptions(g *gocui.Gui, gitFile *GitFile) error {
"c": "commit changes", "c": "commit changes",
"o": "open", "o": "open",
"s": "open in sublime", "s": "open in sublime",
"v": "open in vscode",
"i": "ignore", "i": "ignore",
"d": "delete", "d": "delete",
"space": "toggle staged", "space": "toggle staged",
@ -125,7 +126,6 @@ func handleFileSelect(g *gocui.Gui, v *gocui.View) error {
return err return err
} }
renderString(g, "main", "No changed files") renderString(g, "main", "No changed files")
colorLog(color.FgRed, "error")
return renderfilesOptions(g, nil) return renderfilesOptions(g, nil)
} }
renderfilesOptions(g, &gitFile) renderfilesOptions(g, &gitFile)
@ -171,6 +171,9 @@ func handleFileOpen(g *gocui.Gui, v *gocui.View) error {
func handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error { 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 { func handleRefreshFiles(g *gocui.Gui, v *gocui.View) error {
return refreshFiles(g) return refreshFiles(g)
@ -197,7 +200,7 @@ func renderGitFile(gitFile GitFile, filesView *gocui.View) {
// objects with each render // objects with each render
red := color.New(color.FgRed) red := color.New(color.FgRed)
green := color.New(color.FgGreen) green := color.New(color.FgGreen)
if !gitFile.Tracked { if !gitFile.Tracked && !gitFile.HasStagedChanges {
red.Fprintln(filesView, gitFile.DisplayString) red.Fprintln(filesView, gitFile.DisplayString)
return return
} }
@ -251,10 +254,10 @@ func pullFiles(g *gocui.Gui, v *gocui.View) error {
} else { } else {
closeConfirmationPrompt(g) closeConfirmationPrompt(g)
refreshCommits(g) refreshCommits(g)
refreshFiles(g)
refreshStatus(g) refreshStatus(g)
devLog("pulled.") devLog("pulled.")
} }
refreshFiles(g)
}() }()
return nil return nil
} }
@ -300,5 +303,6 @@ func handleAbortMerge(g *gocui.Gui, v *gocui.View) error {
return createErrorPanel(g, output) return createErrorPanel(g, output)
} }
createMessagePanel(g, v, "", "Merge aborted") createMessagePanel(g, v, "", "Merge aborted")
refreshStatus(g)
return refreshFiles(g) return refreshFiles(g)
} }

View File

@ -110,14 +110,21 @@ func runDirectCommand(command string) (string, error) {
timeStart := time.Now() timeStart := time.Now()
commandLog(command) commandLog(command)
cmdOut, err := exec.Command("bash", "-c", command).CombinedOutput() cmdOut, err := exec.
Command("bash", "-c", command).
CombinedOutput()
devLog("run direct command time for command: ", command, time.Now().Sub(timeStart)) 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) { func branchStringParts(branchString string) (string, string) {
// expect string to be something like '4w master`
splitBranchName := strings.Split(branchString, "\t") 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] return splitBranchName[0], splitBranchName[1]
} }
@ -138,6 +145,9 @@ func coloredString(str string, colour *color.Color) string {
} }
func withPadding(str string, padding int) string { func withPadding(str string, padding int) string {
if padding-len(str) < 0 {
return str
}
return str + strings.Repeat(" ", padding-len(str)) return str + strings.Repeat(" ", padding-len(str))
} }
@ -162,19 +172,39 @@ func getGitBranches() []Branch {
// check if there are any branches // check if there are any branches
branchCheck, _ := runDirectCommand("git branch") branchCheck, _ := runDirectCommand("git branch")
if branchCheck == "" { if branchCheck == "" {
return branches return append(branches, branchFromLine("master", 0))
} }
rawString, _ := runDirectCommand(getBranchesCommand) rawString, _ := runDirectCommand(getBranchesCommand)
branchLines := splitLines(rawString) 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 { for i, line := range branchLines {
branches = append(branches, branchFromLine(line, i)) 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 append the branches that are in our heads
// directory i.e. things we've fetched but haven't necessarily checked out.
// Worth mentioning this has nothing to do with the 'git merge' operation
func getAndMergeFetchedBranches(branches []Branch) []Branch {
rawString, _ := runDirectCommand(getHeadsCommand)
branchLines := splitLines(rawString)
for _, line := range branchLines {
if branchAlreadyStored(line, branches) {
continue
}
branches = append(branches, branchFromLine(line, len(branches)))
}
return branches return branches
} }
@ -200,27 +230,42 @@ func getStashEntryDiff(index int) (string, error) {
return runCommand("git stash show -p --color stash@{" + fmt.Sprint(index) + "}") 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 { func getGitStatusFiles() []GitFile {
statusOutput, _ := getGitStatus() statusOutput, _ := getGitStatus()
statusStrings := splitLines(statusOutput) statusStrings := splitLines(statusOutput)
gitFiles := make([]GitFile, 0) gitFiles := make([]GitFile, 0)
for _, statusString := range statusStrings { for _, statusString := range statusStrings {
stagedChange := statusString[0:1] change := statusString[0:2]
stagedChange := change[0:1]
unstagedChange := statusString[1:2] unstagedChange := statusString[1:2]
filename := statusString[3:] filename := statusString[3:]
tracked := statusString[0:2] != "??" tracked := !includes([]string{"??", "A "}, change)
gitFile := GitFile{ gitFile := GitFile{
Name: filename, Name: filename,
DisplayString: statusString, DisplayString: statusString,
HasStagedChanges: tracked && stagedChange != " " && stagedChange != "U", HasStagedChanges: !includes([]string{" ", "U", "?"}, stagedChange),
HasUnstagedChanges: !tracked || unstagedChange != " ", HasUnstagedChanges: unstagedChange != " ",
Tracked: tracked, Tracked: tracked,
Deleted: unstagedChange == "D" || stagedChange == "D", Deleted: unstagedChange == "D" || stagedChange == "D",
HasMergeConflicts: statusString[0:2] == "UU", HasMergeConflicts: change == "UU",
} }
devLog("tracked", gitFile.Tracked)
devLog("hasUnstagedChanges", gitFile.HasUnstagedChanges)
devLog("HasStagedChanges", gitFile.HasStagedChanges)
devLog("DisplayString", gitFile.DisplayString)
gitFiles = append(gitFiles, gitFile) gitFiles = append(gitFiles, gitFile)
} }
devLog(gitFiles)
return gitFiles return gitFiles
} }
@ -262,6 +307,10 @@ func openFile(filename string) (string, error) {
return runCommand("open " + filename) return runCommand("open " + filename)
} }
func vsCodeOpenFile(filename string) (string, error) {
return runCommand("code -r " + filename)
}
func sublimeOpenFile(filename string) (string, error) { func sublimeOpenFile(filename string) (string, error) {
return runCommand("subl " + filename) return runCommand("subl " + filename)
} }
@ -326,7 +375,7 @@ func gitShow(sha string) string {
func getDiff(file GitFile) string { func getDiff(file GitFile) string {
cachedArg := "" cachedArg := ""
if file.HasStagedChanges { if file.HasStagedChanges && !file.HasUnstagedChanges {
cachedArg = "--cached " cachedArg = "--cached "
} }
deletedArg := "" deletedArg := ""
@ -334,10 +383,10 @@ func getDiff(file GitFile) string {
deletedArg = "-- " deletedArg = "-- "
} }
trackedArg := "" trackedArg := ""
if !file.Tracked { if !file.Tracked && !file.HasStagedChanges {
trackedArg = "--no-index /dev/null " trackedArg = "--no-index /dev/null "
} }
command := "git diff --color " + cachedArg + deletedArg + trackedArg + file.Name command := "git diff -b --color " + cachedArg + deletedArg + trackedArg + file.Name
// for now we assume an error means the file was deleted // for now we assume an error means the file was deleted
s, _ := runCommand(command) s, _ := runCommand(command)
return s return s
@ -352,8 +401,15 @@ func stageFile(file string) error {
return err return err
} }
func unStageFile(file string) error { func unStageFile(file string, tracked bool) error {
_, err := runCommand("git reset HEAD " + file) var command string
if tracked {
command = "git reset HEAD "
} else {
command = "git rm --cached "
}
devLog(command)
_, err := runCommand(command + file)
return err return err
} }
@ -446,7 +502,7 @@ func gitCommitsToPush() []string {
} }
func gitCurrentBranchName() string { func gitCurrentBranchName() string {
branchName, err := runDirectCommand("git rev-parse --abbrev-ref HEAD") branchName, err := runDirectCommand("git symbolic-ref --short HEAD")
// if there is an error, assume there are no branches yet // if there is an error, assume there are no branches yet
if err != nil { if err != nil {
return "" return ""
@ -467,6 +523,26 @@ git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD | {
printf "%s\t%s\n" "$date" "$branch" printf "%s\t%s\n" "$date" "$branch"
fi fi
fi fi
done | sed 's/ days /d /g' | sed 's/ weeks /w /g' | sed 's/ hours /h /g' | sed 's/ minutes /m /g' | sed 's/ seconds /m /g' | sed 's/ago//g' | tr -d ' ' done \
| sed 's/ days /d /g' \
| sed 's/ day /d /g' \
| sed 's/ weeks /w /g' \
| sed 's/ week /w /g' \
| sed 's/ hours /h /g' \
| sed 's/ hour /h /g' \
| sed 's/ minutes /m /g' \
| sed 's/ minute /m /g' \
| sed 's/ seconds /s /g' \
| sed 's/ second /s /g' \
| sed 's/ago//g' \
| tr -d ' '
} }
` `
const getHeadsCommand = `git show-ref \
| grep 'refs/heads/\|refs/remotes/origin/' \
| sed 's/.*refs\/heads\///g' \
| sed 's/.*refs\/remotes\/origin\///g' \
| grep -v '^HEAD$' \
| sort \
| uniq`

1
gui.go
View File

@ -7,6 +7,7 @@ import (
"log" "log"
"time" "time"
// "strings" // "strings"
"github.com/golang-collections/collections/stack" "github.com/golang-collections/collections/stack"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"

10
main.go
View File

@ -48,6 +48,15 @@ func localLog(colour color.Attribute, path string, objects ...interface{}) {
} }
} }
func navigateToRepoRootDirectory() {
_, err := os.Stat(".git")
for os.IsNotExist(err) {
devLog("going up a directory to find the root")
os.Chdir("..")
_, err = os.Stat(".git")
}
}
func main() { func main() {
debuggingPointer := flag.Bool("debug", false, "a boolean") debuggingPointer := flag.Bool("debug", false, "a boolean")
flag.Parse() flag.Parse()
@ -55,5 +64,6 @@ func main() {
devLog("\n\n\n\n\n\n\n\n\n\n") devLog("\n\n\n\n\n\n\n\n\n\n")
startTime = time.Now() startTime = time.Now()
verifyInGitRepo() verifyInGitRepo()
navigateToRepoRootDirectory()
run() run()
} }

1
newFile Normal file
View File

@ -0,0 +1 @@
newFile

1
newFile2 Normal file
View File

@ -0,0 +1 @@
newFile

37
test/generate_basic_repo.sh Executable file
View File

@ -0,0 +1,37 @@
#!/bin/bash
# this script will make a repo with a master and develop branch, where we end up
# on the master branch and if we try and merge master we get a merge conflict
# call this command from the test directory:
# ./generate_basic_repo.sh; cd testrepo; gg; cd ..
# -e means exit if something fails
# -x means print out simple commands before running them
set -ex
reponame="testrepo"
rm -rf ${reponame}
mkdir ${reponame}
cd ${reponame}
git init
echo "Here is a story that has been told throuhg the ages" >> file1
git add file1
git commit -m "first commit"
git checkout -b develop
echo "once upon a time there was a dog" >> file1
git add file1
git commit -m "first commit on develop"
git checkout master
echo "once upon a time there was a cat" >> file1
git add file1
git commit -m "first commit on develop"
git merge develop # should have a merge conflict here

View File

@ -36,7 +36,6 @@ func nextView(g *gocui.Gui, v *gocui.View) error {
focusedView, err := g.View(focusedViewName) focusedView, err := g.View(focusedViewName)
if err != nil { if err != nil {
panic(err) panic(err)
return err
} }
return switchFocus(g, v, focusedView) return switchFocus(g, v, focusedView)
} }