mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-03-05 15:15:49 +02:00
Merge branch 'master' into feature/use-dep
This commit is contained in:
commit
eabf07248a
28
README.md
28
README.md
@ -17,6 +17,21 @@ Then just call `lazygit` in your terminal inside a git repository.
|
||||
|
||||
If you want, you can also add an alias for this with `echo "alias lg='lazygit'" >> ~/.zshrc` (or whichever rc file you're using).
|
||||
|
||||
Please note:
|
||||
On MacOS you may have to add `~/go/bin` to your $PATH variable.
|
||||
|
||||
|
||||
### Ubuntu
|
||||
Packages for Ubuntu 14.04 and up are available via Launchpad PPA.
|
||||
|
||||
They are built daily, straight from master branch.
|
||||
|
||||
```sh
|
||||
sudo add-apt-repository ppa:lazygit-team/daily
|
||||
sudo apt-get update
|
||||
sudo apt-get install lazygit
|
||||
```
|
||||
|
||||
## Cool features
|
||||
- Adding files easily
|
||||
- Resolving merge conflicts
|
||||
@ -31,5 +46,18 @@ If you want, you can also add an alias for this with `echo "alias lg='lazygit'"
|
||||
### Viewing commit diffs
|
||||

|
||||
|
||||
## Docs
|
||||
[Keybindings](https://github.com/jesseduffield/lazygit/blob/master/docs/Keybindings.md)
|
||||
|
||||
## Milestones
|
||||
- [ ] Easy Installation (homebrew, release binaries)
|
||||
- [ ] Configurable Keybindings
|
||||
- [ ] Configurable Color Themes
|
||||
- [ ] Spawning Subprocesses (help needed - have a look at https://github.com/jesseduffield/lazygit/pull/18)
|
||||
- [ ] i18n
|
||||
|
||||
## Contributing
|
||||
I'll find a good template for contributing and then add it to the repo (or if somebody has a suggestion please put up a PR)
|
||||
|
||||
## Work in progress
|
||||
This is still a work in progress so there's still bugs to iron out and as this is my first project in Go the code could no doubt use an increase in quality, but I'll be improving on it whenever I find the time. If you have any feedback feel free to [raise an issue](https://github.com/jesseduffield/lazygit/issues)/[submit a PR](https://github.com/jesseduffield/lazygit/pulls).
|
||||
|
@ -90,7 +90,7 @@ func handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
go func() {
|
||||
branch := getSelectedBranch(v)
|
||||
diff, _ := getBranchDiff(branch.Name, branch.BaseBranch)
|
||||
diff, _ := getBranchGraph(branch.Name, branch.BaseBranch)
|
||||
renderString(g, "main", diff)
|
||||
}()
|
||||
return nil
|
||||
|
54
docs/Keybindings.md
Normal file
54
docs/Keybindings.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Keybindings:
|
||||
|
||||
## Global:
|
||||
|
||||
← → ↑ ↓: navigate
|
||||
PgUp/PgDn: scroll diff panel (use fn+up/down on osx)
|
||||
q: quit
|
||||
p: pull
|
||||
shift+P: push
|
||||
|
||||
## Files Panel:
|
||||
|
||||
space: toggle staged
|
||||
c: commit changes
|
||||
shift+S: stash files
|
||||
o: open (osx only)
|
||||
s: open in sublime (requires 'subl' command)
|
||||
v: open in vscode (requires 'code' command)
|
||||
i: add to .gitignore
|
||||
d: delete if untracked checkout if tracked (aka go away)
|
||||
shift+R: refresh files
|
||||
|
||||
## Branches Panel:
|
||||
|
||||
space: checkout branch
|
||||
f: force checkout branch
|
||||
m: merge into currently checked out branch
|
||||
c: checkout by name
|
||||
n: new branch
|
||||
|
||||
## Commits Panel:
|
||||
|
||||
s: squash down (only available for topmost commit)
|
||||
r: rename commit
|
||||
g: reset to this commit
|
||||
|
||||
## Stash Panel:
|
||||
|
||||
space: apply
|
||||
k: pop
|
||||
d: drop
|
||||
|
||||
## Popup Panel:
|
||||
|
||||
esc: close/cancel
|
||||
enter: confirm
|
||||
|
||||
## Resolving Merge Conflicts (Diff Panel):
|
||||
|
||||
← →: navigate conflicts
|
||||
↑ ↓: select hunk
|
||||
space: pick hunk
|
||||
b: pick both hunks
|
||||
z: undo (only available while still inside diff panel)
|
@ -178,11 +178,15 @@ func getGitBranches() []Branch {
|
||||
if branchCheck == "" {
|
||||
return append(branches, branchFromLine("master", 0))
|
||||
}
|
||||
rawString, _ := runDirectCommand(getBranchesCommand)
|
||||
if rawString, err := runDirectCommand(getBranchesCommand); err == nil {
|
||||
branchLines := splitLines(rawString)
|
||||
for i, line := range branchLines {
|
||||
branches = append(branches, branchFromLine(line, i))
|
||||
}
|
||||
} else {
|
||||
// TODO: DRY this up
|
||||
branches = append(branches, branchFromLine(gitCurrentBranchName(), 0))
|
||||
}
|
||||
branches = getAndMergeFetchedBranches(branches)
|
||||
return branches
|
||||
}
|
||||
@ -327,10 +331,12 @@ func sublimeOpenFile(filename string) (string, error) {
|
||||
return runCommand("subl " + filename)
|
||||
}
|
||||
|
||||
func getBranchDiff(branch string, baseBranch string) (string, error) {
|
||||
func getBranchGraph(branch string, baseBranch string) (string, error) {
|
||||
return runCommand("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branch)
|
||||
|
||||
return runCommand("git log -p -30 --color --no-merges " + branch)
|
||||
// return runCommand("git diff --color " + baseBranch + "..." + branch)
|
||||
// Leaving this guy commented out in case there's backlash from the design
|
||||
// change and I want to make this configurable
|
||||
// return runCommand("git log -p -30 --color --no-merges " + branch)
|
||||
}
|
||||
|
||||
func verifyInGitRepo() {
|
||||
@ -465,7 +471,7 @@ func gitPush() (string, error) {
|
||||
}
|
||||
|
||||
func gitSquashPreviousTwoCommits(message string) (string, error) {
|
||||
return runDirectCommand("git reset --soft head^ && git commit --amend -m \"" + message + "\"")
|
||||
return runDirectCommand("git reset --soft HEAD^ && git commit --amend -m \"" + message + "\"")
|
||||
}
|
||||
|
||||
func gitRenameCommit(message string) (string, error) {
|
||||
@ -522,7 +528,7 @@ func gitCurrentBranchName() string {
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return branchName
|
||||
return strings.TrimSpace(branchName)
|
||||
}
|
||||
|
||||
const getBranchesCommand = `set -e
|
||||
|
186
gui.go
186
gui.go
@ -70,135 +70,67 @@ func handleRefresh(g *gocui.Gui, v *gocui.View) error {
|
||||
return refreshSidePanels(g)
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone, nextView); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowLeft, gocui.ModNone, previousView); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", 'q', gocui.ModNone, quit); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyPgup, gocui.ModNone, scrollUpMain); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyPgdn, gocui.ModNone, scrollDownMain); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", 'P', gocui.ModNone, pushFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", 'p', gocui.ModNone, pullFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", 'R', gocui.ModNone, handleRefresh); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", 'c', gocui.ModNone, handleCommitPress); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", gocui.KeySpace, gocui.ModNone, handleFilePress); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", 'd', gocui.ModNone, handleFileRemove); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", 'm', gocui.ModNone, handleSwitchToMerge); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", 'o', gocui.ModNone, handleFileOpen); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", 's', gocui.ModNone, handleSublimeFileOpen); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", 'v', gocui.ModNone, handleVsCodeFileOpen); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", 'i', gocui.ModNone, handleIgnoreFile); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", 'r', gocui.ModNone, handleRefreshFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", 'S', gocui.ModNone, handleStashSave); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("files", 'a', gocui.ModNone, handleAbortMerge); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", gocui.KeyArrowUp, gocui.ModNone, handleSelectTop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", gocui.KeyEsc, gocui.ModNone, handleEscapeMerge); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", gocui.KeyArrowDown, gocui.ModNone, handleSelectBottom); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", gocui.KeySpace, gocui.ModNone, handlePickHunk); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", 'b', gocui.ModNone, handlePickBothHunks); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", gocui.KeyArrowLeft, gocui.ModNone, handleSelectPrevConflict); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", gocui.KeyArrowRight, gocui.ModNone, handleSelectNextConflict); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", 'z', gocui.ModNone, handlePopFileSnapshot); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("branches", gocui.KeySpace, gocui.ModNone, handleBranchPress); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("branches", 'c', gocui.ModNone, handleCheckoutByName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("branches", 'F', gocui.ModNone, handleForceCheckout); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("branches", 'n', gocui.ModNone, handleNewBranch); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("branches", 'm', gocui.ModNone, handleMerge); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("commits", 's', gocui.ModNone, handleCommitSquashDown); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("commits", 'r', gocui.ModNone, handleRenameCommit); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("commits", 'g', gocui.ModNone, handleResetToCommit); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("stash", gocui.KeySpace, gocui.ModNone, handleStashApply); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: come up with a better keybinding (p/P used for pushing/pulling which
|
||||
// I'd like to be global. Perhaps all global keybindings should use a modifier
|
||||
// like command? But then there's gonna be hotkey conflicts with the terminal
|
||||
if err := g.SetKeybinding("stash", 'k', gocui.ModNone, handleStashPop); err != nil {
|
||||
return err
|
||||
// Binding - a keybinding mapping a key and modifier to a handler. The keypress
|
||||
// is only handled if the given view has focus, or handled globally if the view
|
||||
// is ""
|
||||
type Binding struct {
|
||||
ViewName string
|
||||
Handler func(*gocui.Gui, *gocui.View) error
|
||||
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
|
||||
Modifier gocui.Modifier
|
||||
}
|
||||
|
||||
return g.SetKeybinding("stash", 'd', gocui.ModNone, handleStashDrop)
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
bindings := []Binding{
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView},
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView},
|
||||
Binding{ViewName: "", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView},
|
||||
Binding{ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: quit},
|
||||
Binding{ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: quit},
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown},
|
||||
Binding{ViewName: "", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp},
|
||||
Binding{ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: scrollUpMain},
|
||||
Binding{ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: scrollDownMain},
|
||||
Binding{ViewName: "", Key: 'P', Modifier: gocui.ModNone, Handler: pushFiles},
|
||||
Binding{ViewName: "", Key: 'p', Modifier: gocui.ModNone, Handler: pullFiles},
|
||||
Binding{ViewName: "", Key: 'R', Modifier: gocui.ModNone, Handler: handleRefresh},
|
||||
Binding{ViewName: "files", Key: 'c', Modifier: gocui.ModNone, Handler: handleCommitPress},
|
||||
Binding{ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleFilePress},
|
||||
Binding{ViewName: "files", Key: 'd', Modifier: gocui.ModNone, Handler: handleFileRemove},
|
||||
Binding{ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: handleSwitchToMerge},
|
||||
Binding{ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: handleFileOpen},
|
||||
Binding{ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: handleSublimeFileOpen},
|
||||
Binding{ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: handleVsCodeFileOpen},
|
||||
Binding{ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: handleIgnoreFile},
|
||||
Binding{ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: handleRefreshFiles},
|
||||
Binding{ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: handleStashSave},
|
||||
Binding{ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: handleAbortMerge},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom},
|
||||
Binding{ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleEscapeMerge},
|
||||
Binding{ViewName: "main", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handlePickHunk},
|
||||
Binding{ViewName: "main", Key: 'b', Modifier: gocui.ModNone, Handler: handlePickBothHunks},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: handleSelectPrevConflict},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: handleSelectNextConflict},
|
||||
Binding{ViewName: "main", Key: 'z', Modifier: gocui.ModNone, Handler: handlePopFileSnapshot},
|
||||
Binding{ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleBranchPress},
|
||||
Binding{ViewName: "branches", Key: 'c', Modifier: gocui.ModNone, Handler: handleCheckoutByName},
|
||||
Binding{ViewName: "branches", Key: 'F', Modifier: gocui.ModNone, Handler: handleForceCheckout},
|
||||
Binding{ViewName: "branches", Key: 'n', Modifier: gocui.ModNone, Handler: handleNewBranch},
|
||||
Binding{ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: handleMerge},
|
||||
Binding{ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: handleCommitSquashDown},
|
||||
Binding{ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: handleRenameCommit},
|
||||
Binding{ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: handleResetToCommit},
|
||||
Binding{ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply},
|
||||
Binding{ViewName: "stash", Key: 'k', Modifier: gocui.ModNone, Handler: handleStashPop},
|
||||
Binding{ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop},
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
if err := g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
|
@ -18,7 +18,7 @@ func findConflicts(content string) ([]conflict, error) {
|
||||
conflicts := make([]conflict, 0)
|
||||
var newConflict conflict
|
||||
for i, line := range splitLines(content) {
|
||||
if line == "<<<<<<< HEAD" {
|
||||
if line == "<<<<<<< HEAD" || line == "<<<<<<< MERGE_HEAD" || line == "<<<<<<< Updated upstream" {
|
||||
newConflict = conflict{start: i}
|
||||
} else if line == "=======" {
|
||||
newConflict.middle = i
|
||||
|
Loading…
x
Reference in New Issue
Block a user