From 62fc831407239172f183f7f491222a3aad9d2afa Mon Sep 17 00:00:00 2001 From: mjarkk Date: Mon, 13 Aug 2018 15:33:45 +0200 Subject: Added a view basic translation functions + tranlation file --- Gopkg.toml | 2 +- gui.go | 12 ++++++------ i18n.go | 40 ++++++++++++++++++++++++++++++++++++++++ i18n/nl.toml | 19 +++++++++++++++++++ 4 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 i18n.go create mode 100644 i18n/nl.toml diff --git a/Gopkg.toml b/Gopkg.toml index a47fe5e93..16fd76083 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -39,4 +39,4 @@ [[constraint]] name = "gopkg.in/src-d/go-git.v4" - revision = "43d17e14b714665ab5bc2ecc220b6740779d733f" + revision = "43d17e14b714665ab5bc2ecc220b6740779d733f" \ No newline at end of file diff --git a/gui.go b/gui.go index 589dabea3..f41f2891b 100644 --- a/gui.go +++ b/gui.go @@ -157,7 +157,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Status" + v.Title = ShortLocalize("StatusTitle", "Status") v.FgColor = gocui.ColorWhite } @@ -167,7 +167,7 @@ func layout(g *gocui.Gui) error { return err } filesView.Highlight = true - filesView.Title = "Files" + filesView.Title = ShortLocalize("FilesTitle", "Files") v.FgColor = gocui.ColorWhite } @@ -175,7 +175,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Branches" + v.Title = ShortLocalize("BranchesTitle", "Branches") v.FgColor = gocui.ColorWhite } @@ -183,7 +183,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Commits" + v.Title = ShortLocalize("CommitsTitle", "Commits") v.FgColor = gocui.ColorWhite } @@ -191,7 +191,7 @@ func layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = "Stash" + v.Title = ShortLocalize("StashTitle", "Stash") v.FgColor = gocui.ColorWhite } @@ -210,7 +210,7 @@ func layout(g *gocui.Gui) error { return err } g.SetViewOnBottom("commitMessage") - commitMessageView.Title = "Commit message" + commitMessageView.Title = ShortLocalize("CommitMessage", "Commit message") commitMessageView.FgColor = gocui.ColorWhite commitMessageView.Editable = true } diff --git a/i18n.go b/i18n.go new file mode 100644 index 000000000..a82c29255 --- /dev/null +++ b/i18n.go @@ -0,0 +1,40 @@ +package main + +import ( + "github.com/BurntSushi/toml" + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/text/language" +) + +// the function to setup the localizer +func getlocalizer() *i18n.Localizer { + + // TODO: currently the system language issn't detected + // I'm not sure how to detect it + var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} + i18nObject.RegisterUnmarshalFunc("toml", toml.Unmarshal) + i18nObject.MustLoadMessageFile("i18n/nl.toml") + return i18n.NewLocalizer(i18nObject) +} + +// setup the localizer for later use +var localizer = getlocalizer() + +// MustLocalize handels the translations +// expects i18n.LocalizeConfig as input: https://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n#Localizer.MustLocalize +// output: translated string +func MustLocalize(config *i18n.LocalizeConfig) string { + return localizer.MustLocalize(config) +} + +// ShortLocalize is for 1 line localizations +// ID: The id that is used in the .toml translation files +// Other: the default message it needs to return if there is no translation found or the system is english +func ShortLocalize(ID string, Other string) string { + return MustLocalize(&i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: ID, + Other: Other, + }, + }) +} diff --git a/i18n/nl.toml b/i18n/nl.toml new file mode 100644 index 000000000..7d2d3a9a1 --- /dev/null +++ b/i18n/nl.toml @@ -0,0 +1,19 @@ +# The dutch translation for this program + +[FilesTitle] +other = "Bestanden" + +[BranchesTitle] +other = "Branches" + +[CommitsTitle] +other = "Commits" + +[StashTitle] +other = "Stash" + +[CommitMessage] +other = "Commit Bericht" + +[StatusTitle] +other = "Status" \ No newline at end of file -- cgit v1.2.3 From d9959eb998007fac65b76b61796214dc08903cba Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Mon, 13 Aug 2018 20:46:53 +0200 Subject: fixed @jesseduffield comment #137 --- i18n.go | 27 +++++++++++++++++++++++++-- i18n/nl.toml | 19 ------------------- 2 files changed, 25 insertions(+), 21 deletions(-) delete mode 100644 i18n/nl.toml diff --git a/i18n.go b/i18n.go index a82c29255..04f6c3e35 100644 --- a/i18n.go +++ b/i18n.go @@ -11,9 +11,32 @@ func getlocalizer() *i18n.Localizer { // TODO: currently the system language issn't detected // I'm not sure how to detect it - var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} + var i18nObject = &i18n.Bundle{DefaultLanguage: language.Dutch} i18nObject.RegisterUnmarshalFunc("toml", toml.Unmarshal) - i18nObject.MustLoadMessageFile("i18n/nl.toml") + + // Dutch translation for some words + i18nObject.AddMessages(language.Dutch, + &i18n.Message{ + ID: "FilesTitle", + Other: "Bestanden", + }, &i18n.Message{ + ID: "BranchesTitle", + Other: "Branches", + }, &i18n.Message{ + ID: "CommitsTitle", + Other: "Commits", + }, &i18n.Message{ + ID: "StashTitle", + Other: "Stash", + }, &i18n.Message{ + ID: "CommitMessage", + Other: "Commit Bericht", + }, &i18n.Message{ + ID: "StatusTitle", + Other: "Status", + }, + ) + return i18n.NewLocalizer(i18nObject) } diff --git a/i18n/nl.toml b/i18n/nl.toml deleted file mode 100644 index 7d2d3a9a1..000000000 --- a/i18n/nl.toml +++ /dev/null @@ -1,19 +0,0 @@ -# The dutch translation for this program - -[FilesTitle] -other = "Bestanden" - -[BranchesTitle] -other = "Branches" - -[CommitsTitle] -other = "Commits" - -[StashTitle] -other = "Stash" - -[CommitMessage] -other = "Commit Bericht" - -[StatusTitle] -other = "Status" \ No newline at end of file -- cgit v1.2.3 From 65eb3780a00c7c31d5995b68b1a1c34cc3eea93f Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Mon, 13 Aug 2018 20:48:49 +0200 Subject: fixed typo --- i18n.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n.go b/i18n.go index 04f6c3e35..dfa2a8175 100644 --- a/i18n.go +++ b/i18n.go @@ -11,7 +11,7 @@ func getlocalizer() *i18n.Localizer { // TODO: currently the system language issn't detected // I'm not sure how to detect it - var i18nObject = &i18n.Bundle{DefaultLanguage: language.Dutch} + var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} i18nObject.RegisterUnmarshalFunc("toml", toml.Unmarshal) // Dutch translation for some words -- cgit v1.2.3 From f2dfcb6e12d78f3e7b890d5bd43be7b032e1df88 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Mon, 13 Aug 2018 22:00:44 +0200 Subject: Added more messages and text issue: #137 --- files_panel.go | 26 +++++++++++++------------- i18n.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/files_panel.go b/files_panel.go index 4de8caca7..0957f6f71 100644 --- a/files_panel.go +++ b/files_panel.go @@ -129,21 +129,21 @@ func handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { func renderfilesOptions(g *gocui.Gui, gitFile *GitFile) error { optionsMap := map[string]string{ - "← → ↑ ↓": "navigate", - "S": "stash files", - "c": "commit changes", - "o": "open", - "i": "ignore", - "d": "delete", - "space": "toggle staged", - "R": "refresh", - "t": "add patch", - "e": "edit", - "PgUp/PgDn": "scroll", + "← → ↑ ↓": ShortLocalize("navigate", "navigate"), + "S": ShortLocalize("stashFiles", "stash files"), + "c": ShortLocalize("commitChanges", "commit changes"), + "o": ShortLocalize("open", "open"), + "i": ShortLocalize("ignore", "ignore"), + "d": ShortLocalize("delete", "delete"), + "space": ShortLocalize("toggleStaged", "toggle staged"), + "R": ShortLocalize("refresh", "refresh"), + "t": ShortLocalize("addPatch", "add patch"), + "e": ShortLocalize("edit", "edit"), + "PgUp/PgDn": ShortLocalize("scroll", "scroll"), } if state.HasMergeConflicts { - optionsMap["a"] = "abort merge" - optionsMap["m"] = "resolve merge conflicts" + optionsMap["a"] = ShortLocalize("abortMerge", "abort merge") + optionsMap["m"] = ShortLocalize("resolveMergeConflicts", "resolve merge conflicts") } if gitFile == nil { return renderOptionsMap(g, optionsMap) diff --git a/i18n.go b/i18n.go index dfa2a8175..346b370fa 100644 --- a/i18n.go +++ b/i18n.go @@ -14,6 +14,10 @@ func getlocalizer() *i18n.Localizer { var i18nObject = &i18n.Bundle{DefaultLanguage: language.English} i18nObject.RegisterUnmarshalFunc("toml", toml.Unmarshal) + // To add more translations do: + // AddMessages(tag language.Tag, messages ...*Message) + // https://godoc.org/github.com/nicksnyder/go-i18n/v2/i18n#Bundle.AddMessages + // Dutch translation for some words i18nObject.AddMessages(language.Dutch, &i18n.Message{ @@ -31,10 +35,51 @@ func getlocalizer() *i18n.Localizer { }, &i18n.Message{ ID: "CommitMessage", Other: "Commit Bericht", + }, &i18n.Message{ + ID: "CommitChanges", + Other: "Commit Veranderingen", }, &i18n.Message{ ID: "StatusTitle", Other: "Status", + }, &i18n.Message{ + ID: "navigate", + Other: "navigeer", + }, &i18n.Message{ + ID: "stashFiles", + Other: "stash-bestanden", + }, &i18n.Message{ + ID: "open", + Other: "open", + }, &i18n.Message{ + ID: "ignore", + Other: "negeren", + }, &i18n.Message{ + ID: "delete", + Other: "verwijderen", + }, &i18n.Message{ + ID: "toggleStaged", + Other: "toggle staged", + }, &i18n.Message{ + ID: "refresh", + Other: "verversen", + }, &i18n.Message{ + ID: "addPatch", + Other: "verandering toevoegen", + }, &i18n.Message{ + ID: "edit", + Other: "veranderen", + }, &i18n.Message{ + ID: "scroll", + Other: "scroll", + }, &i18n.Message{ + ID: "abortMerge", + Other: "samenvoegen afbreken", + }, &i18n.Message{ + ID: "resolveMergeConflicts", + Other: "verhelp samenvoegen fouten", }, + + // ) return i18n.NewLocalizer(i18nObject) -- cgit v1.2.3 From dfafb988713a79664139d15ad471736a5a4b1b90 Mon Sep 17 00:00:00 2001 From: Mark Kopenga Date: Tue, 14 Aug 2018 11:05:26 +0200 Subject: tried to update to latest master --- .gitignore | 3 +- Gopkg.lock | 25 +- Gopkg.toml | 2 +- VERSION | 2 +- branch.go | 35 - branch_list_builder.go | 134 --- branches_panel.go | 135 --- commit_message_panel.go | 45 - commits_panel.go | 176 ---- confirmation_panel.go | 155 ---- docs/Keybindings.md | 11 +- files_panel.go | 362 -------- gitcommands.go | 529 ------------ gui.go | 300 ------- i18n.go | 108 --- keybindings.go | 91 -- main.go | 94 +- merge_panel.go | 263 ------ pkg/app/app.go | 71 ++ pkg/commands/branch.go | 39 + pkg/commands/git.go | 497 +++++++++++ pkg/commands/git_structs.go | 36 + pkg/commands/os.go | 174 ++++ pkg/config/app_config.go | 45 + pkg/git/branch_list_builder.go | 160 ++++ pkg/gui/branches_panel.go | 141 +++ pkg/gui/commit_message_panel.go | 50 ++ pkg/gui/commits_panel.go | 176 ++++ pkg/gui/confirmation_panel.go | 149 ++++ pkg/gui/files_panel.go | 400 +++++++++ pkg/gui/gui.go | 331 +++++++ pkg/gui/keybindings.go | 93 ++ pkg/gui/merge_panel.go | 260 ++++++ pkg/gui/stash_panel.go | 97 +++ pkg/gui/status_panel.go | 42 + pkg/gui/view_helpers.go | 245 ++++++ pkg/i18n/i18n.go | 108 +++ pkg/utils/utils.go | 65 ++ stash_panel.go | 93 -- status_panel.go | 41 - test/repos/gpg.sh | 14 + test/repos/pre_commit_hook.sh | 2 +- utils.go | 54 -- vendor/github.com/Sirupsen/logrus/LICENSE | 21 + vendor/github.com/Sirupsen/logrus/alt_exit.go | 64 ++ vendor/github.com/Sirupsen/logrus/doc.go | 26 + vendor/github.com/Sirupsen/logrus/entry.go | 300 +++++++ vendor/github.com/Sirupsen/logrus/exported.go | 201 +++++ vendor/github.com/Sirupsen/logrus/formatter.go | 51 ++ vendor/github.com/Sirupsen/logrus/hooks.go | 34 + .../github.com/Sirupsen/logrus/json_formatter.go | 89 ++ vendor/github.com/Sirupsen/logrus/logger.go | 337 ++++++++ vendor/github.com/Sirupsen/logrus/logrus.go | 143 ++++ vendor/github.com/Sirupsen/logrus/terminal_bsd.go | 10 + .../Sirupsen/logrus/terminal_check_appengine.go | 11 + .../Sirupsen/logrus/terminal_check_notappengine.go | 19 + .../github.com/Sirupsen/logrus/terminal_linux.go | 14 + .../github.com/Sirupsen/logrus/text_formatter.go | 195 +++++ vendor/github.com/Sirupsen/logrus/writer.go | 62 ++ vendor/github.com/jesseduffield/gocui/gui.go | 3 + vendor/github.com/mgutz/str/LICENSE | 21 + vendor/github.com/mgutz/str/doc.go | 19 + vendor/github.com/mgutz/str/funcsAO.go | 337 ++++++++ vendor/github.com/mgutz/str/funcsPZ.go | 534 ++++++++++++ .../golang.org/x/crypto/ssh/terminal/terminal.go | 951 +++++++++++++++++++++ vendor/golang.org/x/crypto/ssh/terminal/util.go | 114 +++ .../golang.org/x/crypto/ssh/terminal/util_bsd.go | 12 + .../golang.org/x/crypto/ssh/terminal/util_linux.go | 10 + .../golang.org/x/crypto/ssh/terminal/util_plan9.go | 58 ++ .../x/crypto/ssh/terminal/util_solaris.go | 124 +++ .../x/crypto/ssh/terminal/util_windows.go | 103 +++ view_helpers.go | 235 ----- 72 files changed, 7101 insertions(+), 2850 deletions(-) delete mode 100644 branch.go delete mode 100644 branch_list_builder.go delete mode 100644 branches_panel.go delete mode 100644 commit_message_panel.go delete mode 100644 commits_panel.go delete mode 100644 confirmation_panel.go delete mode 100644 files_panel.go delete mode 100644 gitcommands.go delete mode 100644 gui.go delete mode 100644 i18n.go delete mode 100644 keybindings.go delete mode 100644 merge_panel.go create mode 100644 pkg/app/app.go create mode 100644 pkg/commands/branch.go create mode 100644 pkg/commands/git.go create mode 100644 pkg/commands/git_structs.go create mode 100644 pkg/commands/os.go create mode 100644 pkg/config/app_config.go create mode 100644 pkg/git/branch_list_builder.go create mode 100644 pkg/gui/branches_panel.go create mode 100644 pkg/gui/commit_message_panel.go create mode 100644 pkg/gui/commits_panel.go create mode 100644 pkg/gui/confirmation_panel.go create mode 100644 pkg/gui/files_panel.go create mode 100644 pkg/gui/gui.go create mode 100644 pkg/gui/keybindings.go create mode 100644 pkg/gui/merge_panel.go create mode 100644 pkg/gui/stash_panel.go create mode 100644 pkg/gui/status_panel.go create mode 100644 pkg/gui/view_helpers.go create mode 100644 pkg/i18n/i18n.go create mode 100644 pkg/utils/utils.go delete mode 100644 stash_panel.go delete mode 100644 status_panel.go create mode 100755 test/repos/gpg.sh delete mode 100644 utils.go create mode 100644 vendor/github.com/Sirupsen/logrus/LICENSE create mode 100644 vendor/github.com/Sirupsen/logrus/alt_exit.go create mode 100644 vendor/github.com/Sirupsen/logrus/doc.go create mode 100644 vendor/github.com/Sirupsen/logrus/entry.go create mode 100644 vendor/github.com/Sirupsen/logrus/exported.go create mode 100644 vendor/github.com/Sirupsen/logrus/formatter.go create mode 100644 vendor/github.com/Sirupsen/logrus/hooks.go create mode 100644 vendor/github.com/Sirupsen/logrus/json_formatter.go create mode 100644 vendor/github.com/Sirupsen/logrus/logger.go create mode 100644 vendor/github.com/Sirupsen/logrus/logrus.go create mode 100644 vendor/github.com/Sirupsen/logrus/terminal_bsd.go create mode 100644 vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go create mode 100644 vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go create mode 100644 vendor/github.com/Sirupsen/logrus/terminal_linux.go create mode 100644 vendor/github.com/Sirupsen/logrus/text_formatter.go create mode 100644 vendor/github.com/Sirupsen/logrus/writer.go create mode 100644 vendor/github.com/mgutz/str/LICENSE create mode 100644 vendor/github.com/mgutz/str/doc.go create mode 100644 vendor/github.com/mgutz/str/funcsAO.go create mode 100644 vendor/github.com/mgutz/str/funcsPZ.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/terminal.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util_linux.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go create mode 100644 vendor/golang.org/x/crypto/ssh/terminal/util_windows.go delete mode 100644 view_helpers.go diff --git a/.gitignore b/.gitignore index dac0b46b6..3d59e16c5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ extra/lgit.rb notes/go.notes TODO.notes TODO.md -test/testrepo/ -test/repos/repo \ No newline at end of file +test/repos/repo diff --git a/Gopkg.lock b/Gopkg.lock index af729a2cb..7b167c7d8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,14 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + digest = "1:b2339e83ce9b5c4f79405f949429a7f68a9a904fed903c672aac1e7ceb7f5f02" + name = "github.com/Sirupsen/logrus" + packages = ["."] + pruneopts = "NUT" + revision = "3e01752db0189b9157070a0e1668a620f9a85da2" + version = "v1.0.6" + [[projects]] digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" name = "github.com/davecgh/go-spew" @@ -50,11 +58,11 @@ [[projects]] branch = "master" - digest = "1:e9b2b07a20f19d886267876b72ba15f2cbdeeeadd18030a4ce174b864e97c39e" + digest = "1:c9a848b0484a72da2dae28957b4f67501fe27fa38bc73f4713e454353c0a4a60" name = "github.com/jesseduffield/gocui" packages = ["."] pruneopts = "NUT" - revision = "8cecad864fb0b099a5f55bf1c97fbc1daca103e0" + revision = "432b7f6215f81ef1aaa1b2d9b69887822923cf79" [[projects]] digest = "1:8021af4dcbd531ae89433c8c3a6520e51064114aaf8eb1724c3cf911c497c9ba" @@ -88,6 +96,14 @@ revision = "9e777a8366cce605130a531d2cd6363d07ad7317" version = "v0.0.2" +[[projects]] + digest = "1:a25c9a6b41e100f4ce164db80260f2b687095ba9d8b46a1d6072d3686cc020db" + name = "github.com/mgutz/str" + packages = ["."] + pruneopts = "NUT" + revision = "968bf66e3da857419e4f6e71b2d5c9ae95682dc4" + version = "v1.2.0" + [[projects]] branch = "master" digest = "1:a4df73029d2c42fabcb6b41e327d2f87e685284ec03edf76921c267d9cfc9c23" @@ -151,7 +167,7 @@ [[projects]] branch = "master" - digest = "1:c76f8b24a4d9b99b502fb7b61ad769125075cb570efff9b9b73e6c428629532d" + digest = "1:dfcb1b2db354cafa48fc3cdafe4905a08bec4a9757919ab07155db0ca23855b4" name = "golang.org/x/crypto" packages = [ "cast5", @@ -170,6 +186,7 @@ "ssh", "ssh/agent", "ssh/knownhosts", + "ssh/terminal", ] pruneopts = "NUT" revision = "de0752318171da717af4ce24d0a2e8626afaeb11" @@ -282,10 +299,12 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/Sirupsen/logrus", "github.com/davecgh/go-spew/spew", "github.com/fatih/color", "github.com/golang-collections/collections/stack", "github.com/jesseduffield/gocui", + "github.com/mgutz/str", "github.com/tcnksm/go-gitconfig", "gopkg.in/src-d/go-git.v4", "gopkg.in/src-d/go-git.v4/plumbing", diff --git a/Gopkg.toml b/Gopkg.toml index 16fd76083..a47fe5e93 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -39,4 +39,4 @@ [[constraint]] name = "gopkg.in/src-d/go-git.v4" - revision = "43d17e14b714665ab5bc2ecc220b6740779d733f" \ No newline at end of file + revision = "43d17e14b714665ab5bc2ecc220b6740779d733f" diff --git a/VERSION b/VERSION index 388e9d45b..bcb7cc884 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.1.55 \ No newline at end of file +v0.1.58 \ No newline at end of file diff --git a/branch.go b/branch.go deleted file mode 100644 index 78c2e55aa..000000000 --- a/branch.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "strings" - - "github.com/fatih/color" -) - -// Branch : A git branch -type Branch struct { - Name string - Recency string -} - -func (b *Branch) getDisplayString() string { - return withPadding(b.Recency, 4) + coloredString(b.Name, b.getColor()) -} - -func (b *Branch) getColor() color.Attribute { - switch b.getType() { - case "feature": - return color.FgGreen - case "bugfix": - return color.FgYellow - case "hotfix": - return color.FgRed - default: - return color.FgWhite - } -} - -// expected to return feature/bugfix/hotfix or blank string -func (b *Branch) getType() string { - return strings.Split(b.Name, "/")[0] -} diff --git a/branch_list_builder.go b/branch_list_builder.go deleted file mode 100644 index 1d4dc338d..000000000 --- a/branch_list_builder.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - "regexp" - "strings" - - "gopkg.in/src-d/go-git.v4/plumbing" -) - -// context: -// we want to only show 'safe' branches (ones that haven't e.g. been deleted) -// which `git branch -a` gives us, but we also want the recency data that -// git reflog gives us. -// So we get the HEAD, then append get the reflog branches that intersect with -// our safe branches, then add the remaining safe branches, ensuring uniqueness -// along the way - -type branchListBuilder struct{} - -func newBranchListBuilder() *branchListBuilder { - return &branchListBuilder{} -} - -func (b *branchListBuilder) obtainCurrentBranch() Branch { - // I used go-git for this, but that breaks if you've just done a git init, - // even though you're on 'master' - branchName, _ := runDirectCommand("git symbolic-ref --short HEAD") - return Branch{Name: strings.TrimSpace(branchName), Recency: " *"} -} - -func (*branchListBuilder) obtainReflogBranches() []Branch { - branches := make([]Branch, 0) - rawString, err := runDirectCommand("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD") - if err != nil { - return branches - } - - branchLines := splitLines(rawString) - for _, line := range branchLines { - timeNumber, timeUnit, branchName := branchInfoFromLine(line) - timeUnit = abbreviatedTimeUnit(timeUnit) - branch := Branch{Name: branchName, Recency: timeNumber + timeUnit} - branches = append(branches, branch) - } - return branches -} - -func (b *branchListBuilder) obtainSafeBranches() []Branch { - branches := make([]Branch, 0) - - bIter, err := r.Branches() - if err != nil { - panic(err) - } - err = bIter.ForEach(func(b *plumbing.Reference) error { - name := b.Name().Short() - branches = append(branches, Branch{Name: name}) - return nil - }) - - return branches -} - -func (b *branchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []Branch, included bool) []Branch { - for _, newBranch := range newBranches { - if included == branchIncluded(newBranch.Name, existingBranches) { - finalBranches = append(finalBranches, newBranch) - } - } - return finalBranches -} - -func sanitisedReflogName(reflogBranch Branch, safeBranches []Branch) string { - for _, safeBranch := range safeBranches { - if strings.ToLower(safeBranch.Name) == strings.ToLower(reflogBranch.Name) { - return safeBranch.Name - } - } - return reflogBranch.Name -} - -func (b *branchListBuilder) build() []Branch { - branches := make([]Branch, 0) - head := b.obtainCurrentBranch() - safeBranches := b.obtainSafeBranches() - if len(safeBranches) == 0 { - return append(branches, head) - } - reflogBranches := b.obtainReflogBranches() - reflogBranches = uniqueByName(append([]Branch{head}, reflogBranches...)) - for i, reflogBranch := range reflogBranches { - reflogBranches[i].Name = sanitisedReflogName(reflogBranch, safeBranches) - } - - branches = b.appendNewBranches(branches, reflogBranches, safeBranches, true) - branches = b.appendNewBranches(branches, safeBranches, branches, false) - - return branches -} - -func uniqueByName(branches []Branch) []Branch { - finalBranches := make([]Branch, 0) - for _, branch := range branches { - if branchIncluded(branch.Name, finalBranches) { - continue - } - finalBranches = append(finalBranches, branch) - } - return finalBranches -} - -// A line will have the form '10 days ago master' so we need to strip out the -// useful information from that into timeNumber, timeUnit, and branchName -func branchInfoFromLine(line string) (string, string, string) { - r := regexp.MustCompile("\\|.*\\s") - line = r.ReplaceAllString(line, " ") - words := strings.Split(line, " ") - return words[0], words[1], words[3] -} - -func abbreviatedTimeUnit(timeUnit string) string { - r := regexp.MustCompile("s$") - timeUnit = r.ReplaceAllString(timeUnit, "") - timeUnitMap := map[string]string{ - "hour": "h", - "minute": "m", - "second": "s", - "week": "w", - "year": "y", - "day": "d", - "month": "m", - } - return timeUnitMap[timeUnit] -} diff --git a/branches_panel.go b/branches_panel.go deleted file mode 100644 index a73d28bb3..000000000 --- a/branches_panel.go +++ /dev/null @@ -1,135 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/gocui" -) - -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) - if output, err := gitCheckout(branch.Name, false); err != nil { - createErrorPanel(g, output) - } - return refreshSidePanels(g) -} - -func handleForceCheckout(g *gocui.Gui, v *gocui.View) error { - branch := getSelectedBranch(v) - return createConfirmationPanel(g, v, "Force Checkout Branch", "Are you sure you want force checkout? You will lose all local changes", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitCheckout(branch.Name, true); err != nil { - createErrorPanel(g, output) - } - return refreshSidePanels(g) - }, nil) -} - -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 -} - -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 -} - -func handleDeleteBranch(g *gocui.Gui, v *gocui.View) error { - checkedOutBranch := state.Branches[0] - selectedBranch := getSelectedBranch(v) - if checkedOutBranch.Name == selectedBranch.Name { - return createErrorPanel(g, "You cannot delete the checked out branch!") - } - return createConfirmationPanel(g, v, "Delete Branch", "Are you sure you want delete the branch "+selectedBranch.Name+" ?", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitDeleteBranch(selectedBranch.Name); err != nil { - return createErrorPanel(g, output) - } - return refreshSidePanels(g) - }, 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 -} - -func getSelectedBranch(v *gocui.View) Branch { - 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": "new branch", - "d": "delete branch", - "← → ↑ ↓": "navigate", - }) -} - -// 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, err := getBranchGraph(branch.Name) - if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") { - diff = "There is no tracking for this branch" - } - 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.getDisplayString()) - } - resetOrigin(v) - return refreshStatus(g) - }) - return nil -} diff --git a/commit_message_panel.go b/commit_message_panel.go deleted file mode 100644 index baef870cf..000000000 --- a/commit_message_panel.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import "github.com/jesseduffield/gocui" - -func handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { - message := trimmedContent(v) - if message == "" { - return createErrorPanel(g, "You cannot commit without a commit message") - } - if output, err := gitCommit(g, message); err != nil { - if err == errNoUsername { - return createErrorPanel(g, err.Error()) - } - return createErrorPanel(g, output) - } - refreshFiles(g) - v.Clear() - v.SetCursor(0, 0) - g.SetViewOnBottom("commitMessage") - switchFocus(g, v, getFilesView(g)) - return refreshCommits(g) -} - -func handleCommitClose(g *gocui.Gui, v *gocui.View) error { - g.SetViewOnBottom("commitMessage") - return switchFocus(g, v, getFilesView(g)) -} - -func handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error { - // resising ahead of time so that the top line doesn't get hidden to make - // room for the cursor on the second line - x0, y0, x1, y1 := getConfirmationPanelDimensions(g, v.Buffer()) - if _, err := g.SetView("commitMessage", x0, y0, x1, y1+1, 0); err != nil { - if err != gocui.ErrUnknownView { - return err - } - } - - v.EditNewLine() - return nil -} - -func handleCommitFocused(g *gocui.Gui, v *gocui.View) error { - return renderString(g, "options", "esc: close, enter: confirm") -} diff --git a/commits_panel.go b/commits_panel.go deleted file mode 100644 index 1a9976ffe..000000000 --- a/commits_panel.go +++ /dev/null @@ -1,176 +0,0 @@ -package main - -import ( - "errors" - - "github.com/fatih/color" - "github.com/jesseduffield/gocui" -) - -var ( - // ErrNoCommits : When no commits are found for the branch - ErrNoCommits = errors.New("No commits for this branch") -) - -func refreshCommits(g *gocui.Gui) error { - g.Update(func(*gocui.Gui) error { - state.Commits = getCommits() - v, err := g.View("commits") - if err != nil { - panic(err) - } - v.Clear() - red := color.New(color.FgRed) - yellow := color.New(color.FgYellow) - white := color.New(color.FgWhite) - shaColor := white - for _, commit := range state.Commits { - if commit.Pushed { - shaColor = red - } else { - shaColor = yellow - } - shaColor.Fprint(v, commit.Sha+" ") - white.Fprintln(v, commit.Name) - } - refreshStatus(g) - if g.CurrentView().Name() == "commits" { - handleCommitSelect(g, v) - } - return nil - }) - return nil -} - -func handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error { - return createConfirmationPanel(g, commitView, "Reset To Commit", "Are you sure you want to reset to this commit?", func(g *gocui.Gui, v *gocui.View) error { - commit, err := getSelectedCommit(g) - if err != nil { - panic(err) - } - if output, err := gitResetToCommit(commit.Sha); err != nil { - return createErrorPanel(g, output) - } - if err := refreshCommits(g); err != nil { - panic(err) - } - if err := refreshFiles(g); err != nil { - panic(err) - } - resetOrigin(commitView) - return handleCommitSelect(g, nil) - }, nil) -} - -func renderCommitsOptions(g *gocui.Gui) error { - return renderOptionsMap(g, map[string]string{ - "s": "squash down", - "r": "rename", - "g": "reset to this commit", - "f": "fixup commit", - "← → ↑ ↓": "navigate", - }) -} - -func handleCommitSelect(g *gocui.Gui, v *gocui.View) error { - if err := renderCommitsOptions(g); err != nil { - return err - } - commit, err := getSelectedCommit(g) - if err != nil { - if err != ErrNoCommits { - return err - } - return renderString(g, "main", "No commits for this branch") - } - commitText := gitShow(commit.Sha) - return renderString(g, "main", commitText) -} - -func handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { - if getItemPosition(v) != 0 { - return createErrorPanel(g, "Can only squash topmost commit") - } - if len(state.Commits) == 1 { - return createErrorPanel(g, "You have no commits to squash with") - } - commit, err := getSelectedCommit(g) - if err != nil { - return err - } - if output, err := gitSquashPreviousTwoCommits(commit.Name); err != nil { - return createErrorPanel(g, output) - } - if err := refreshCommits(g); err != nil { - panic(err) - } - refreshStatus(g) - return handleCommitSelect(g, v) -} - -// TODO: move to files panel -func anyUnStagedChanges(files []GitFile) bool { - for _, file := range files { - if file.Tracked && file.HasUnstagedChanges { - return true - } - } - return false -} - -func handleCommitFixup(g *gocui.Gui, v *gocui.View) error { - if len(state.Commits) == 1 { - return createErrorPanel(g, "You have no commits to squash with") - } - objectLog(state.GitFiles) - if anyUnStagedChanges(state.GitFiles) { - return createErrorPanel(g, "Can't fixup while there are unstaged changes") - } - branch := state.Branches[0] - commit, err := getSelectedCommit(g) - if err != nil { - return err - } - createConfirmationPanel(g, v, "Fixup", "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitSquashFixupCommit(branch.Name, commit.Sha); err != nil { - return createErrorPanel(g, output) - } - if err := refreshCommits(g); err != nil { - panic(err) - } - return refreshStatus(g) - }, nil) - return nil -} - -func handleRenameCommit(g *gocui.Gui, v *gocui.View) error { - if getItemPosition(v) != 0 { - return createErrorPanel(g, "Can only rename topmost commit") - } - createPromptPanel(g, v, "Rename Commit", func(g *gocui.Gui, v *gocui.View) error { - if output, err := gitRenameCommit(v.Buffer()); err != nil { - return createErrorPanel(g, output) - } - if err := refreshCommits(g); err != nil { - panic(err) - } - return handleCommitSelect(g, v) - }) - return nil -} - -func getSelectedCommit(g *gocui.Gui) (Commit, error) { - v, err := g.View("commits") - if err != nil { - panic(err) - } - if len(state.Commits) == 0 { - return Commit{}, ErrNoCommits - } - lineNumber := getItemPosition(v) - if lineNumber > len(state.Commits)-1 { - devLog("potential error in getSelected Commit (mismatched ui and state)", state.Commits, lineNumber) - return state.Commits[len(state.Commits)-1], nil - } - return state.Commits[lineNumber], nil -} diff --git a/confirmation_panel.go b/confirmation_panel.go deleted file mode 100644 index a8719d237..000000000 --- a/confirmation_panel.go +++ /dev/null @@ -1,155 +0,0 @@ -// lots of this has been directly ported from one of the example files, will brush up later - -// Copyright 2014 The gocui Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "strings" - - "github.com/fatih/color" - "github.com/jesseduffield/gocui" -) - -func wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error) func(*gocui.Gui, *gocui.View) error { - return func(g *gocui.Gui, v *gocui.View) error { - if function != nil { - if err := function(g, v); err != nil { - panic(err) - } - } - return closeConfirmationPrompt(g) - } -} - -func closeConfirmationPrompt(g *gocui.Gui) error { - view, err := g.View("confirmation") - if err != nil { - panic(err) - } - if err := returnFocus(g, view); err != nil { - panic(err) - } - g.DeleteKeybindings("confirmation") - return g.DeleteView("confirmation") -} - -func getMessageHeight(message string, width int) int { - lines := strings.Split(message, "\n") - lineCount := 0 - for _, line := range lines { - lineCount += len(line)/width + 1 - } - return lineCount -} - -func getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int, int) { - width, height := g.Size() - panelWidth := width / 2 - panelHeight := getMessageHeight(prompt, panelWidth) - return width/2 - panelWidth/2, - height/2 - panelHeight/2 - panelHeight%2 - 1, - width/2 + panelWidth/2, - height/2 + panelHeight/2 -} - -func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error { - g.SetViewOnBottom("commitMessage") - // only need to fit one line - x0, y0, x1, y1 := getConfirmationPanelDimensions(g, "") - if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil { - if err != gocui.ErrUnknownView { - return err - } - - confirmationView.Editable = true - confirmationView.Title = title - switchFocus(g, currentView, confirmationView) - return setKeyBindings(g, handleConfirm, nil) - } - return nil -} - -func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { - g.SetViewOnBottom("commitMessage") - g.Update(func(g *gocui.Gui) error { - // delete the existing confirmation panel if it exists - if view, _ := g.View("confirmation"); view != nil { - if err := closeConfirmationPrompt(g); err != nil { - panic(err) - } - } - x0, y0, x1, y1 := getConfirmationPanelDimensions(g, prompt) - if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil { - if err != gocui.ErrUnknownView { - return err - } - confirmationView.Title = title - confirmationView.FgColor = gocui.ColorWhite - renderString(g, "confirmation", prompt) - switchFocus(g, currentView, confirmationView) - return setKeyBindings(g, handleConfirm, handleClose) - } - return nil - }) - return nil -} - -func handleNewline(g *gocui.Gui, v *gocui.View) error { - // resising ahead of time so that the top line doesn't get hidden to make - // room for the cursor on the second line - x0, y0, x1, y1 := getConfirmationPanelDimensions(g, v.Buffer()) - if _, err := g.SetView("confirmation", x0, y0, x1, y1+1, 0); err != nil { - if err != gocui.ErrUnknownView { - return err - } - } - - v.EditNewLine() - return nil -} - -func setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { - renderString(g, "options", "esc: close, enter: confirm") - if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, wrappedConfirmationFunction(handleConfirm)); err != nil { - return err - } - if err := g.SetKeybinding("confirmation", gocui.KeyTab, gocui.ModNone, handleNewline); err != nil { - return err - } - return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, wrappedConfirmationFunction(handleClose)) -} - -func createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error { - return createConfirmationPanel(g, currentView, title, prompt, nil, nil) -} - -func createErrorPanel(g *gocui.Gui, message string) error { - currentView := g.CurrentView() - colorFunction := color.New(color.FgRed).SprintFunc() - coloredMessage := colorFunction(strings.TrimSpace(message)) - return createConfirmationPanel(g, currentView, "Error", coloredMessage, nil, nil) -} - -func trimTrailingNewline(str string) string { - if strings.HasSuffix(str, "\n") { - return str[:len(str)-1] - } - return str -} - -func resizePopupPanel(g *gocui.Gui, v *gocui.View) error { - // If the confirmation panel is already displayed, just resize the width, - // otherwise continue - content := trimTrailingNewline(v.Buffer()) - x0, y0, x1, y1 := getConfirmationPanelDimensions(g, content) - vx0, vy0, vx1, vy1 := v.Dimensions() - if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 { - return nil - } - devLog("resizing popup panel") - _, err := g.SetView(v.Name(), x0, y0, x1, y1, 0) - return err -} diff --git a/docs/Keybindings.md b/docs/Keybindings.md index 8ef749277..2b9f9d952 100644 --- a/docs/Keybindings.md +++ b/docs/Keybindings.md @@ -2,11 +2,12 @@ ## Global:
-  /hjkl:  navigate
-  PgUp/PgDn:           scroll diff panel (use fn+up/fn+down on osx)
-  q:                    quit
-  p:                    pull
-  shift+P:             push
+  /hjkl:               navigate
+  PgUp/PgDn or ctrl+u/ctrl+d:   scroll diff panel 
+                                     (for PgUp and PgDn, use fn+up/fn+down on osx)
+  q:                                quit
+  p:                                pull
+  shift+P:                         push
 
## Files Panel: diff --git a/files_panel.go b/files_panel.go deleted file mode 100644 index 0957f6f71..000000000 --- a/files_panel.go +++ /dev/null @@ -1,362 +0,0 @@ -package main - -import ( - - // "io" - // "io/ioutil" - - // "strings" - - "errors" - "strings" - - "github.com/fatih/color" - "github.com/jesseduffield/gocui" -) - -var ( - errNoFiles = errors.New("No changed files") - errNoUsername = errors.New(`No username set. Please do: git config --global user.name "Your Name"`) -) - -func stagedFiles(files []GitFile) []GitFile { - result := make([]GitFile, 0) - for _, file := range files { - if file.HasStagedChanges { - result = append(result, file) - } - } - return result -} - -func stageSelectedFile(g *gocui.Gui) error { - file, err := getSelectedFile(g) - if err != nil { - return err - } - return stageFile(file.Name) -} - -func handleFilePress(g *gocui.Gui, v *gocui.View) error { - file, err := getSelectedFile(g) - if err != nil { - if err == errNoFiles { - return nil - } - return err - } - - if file.HasMergeConflicts { - return handleSwitchToMerge(g, v) - } - - if file.HasUnstagedChanges { - stageFile(file.Name) - } else { - unStageFile(file.Name, file.Tracked) - } - - if err := refreshFiles(g); err != nil { - return err - } - - return handleFileSelect(g, v) -} - -func handleAddPatch(g *gocui.Gui, v *gocui.View) error { - file, err := getSelectedFile(g) - if err != nil { - if err == errNoFiles { - return nil - } - return err - } - if !file.HasUnstagedChanges { - return createErrorPanel(g, "File has no unstaged changes to add") - } - if !file.Tracked { - return createErrorPanel(g, "Cannot git add --patch untracked files") - } - gitAddPatch(g, file.Name) - return err -} - -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 -} - -func handleFileRemove(g *gocui.Gui, v *gocui.View) error { - file, err := getSelectedFile(g) - if err != nil { - if err == errNoFiles { - return nil - } - return err - } - var deleteVerb string - if file.Tracked { - deleteVerb = "checkout" - } else { - deleteVerb = "delete" - } - return createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", "Are you sure you want to "+deleteVerb+" "+file.Name+" (you will lose your changes)?", func(g *gocui.Gui, v *gocui.View) error { - if err := removeFile(file); err != nil { - panic(err) - } - return refreshFiles(g) - }, nil) -} - -func handleIgnoreFile(g *gocui.Gui, v *gocui.View) error { - file, err := getSelectedFile(g) - if err != nil { - return createErrorPanel(g, err.Error()) - } - 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{ - "← → ↑ ↓": ShortLocalize("navigate", "navigate"), - "S": ShortLocalize("stashFiles", "stash files"), - "c": ShortLocalize("commitChanges", "commit changes"), - "o": ShortLocalize("open", "open"), - "i": ShortLocalize("ignore", "ignore"), - "d": ShortLocalize("delete", "delete"), - "space": ShortLocalize("toggleStaged", "toggle staged"), - "R": ShortLocalize("refresh", "refresh"), - "t": ShortLocalize("addPatch", "add patch"), - "e": ShortLocalize("edit", "edit"), - "PgUp/PgDn": ShortLocalize("scroll", "scroll"), - } - if state.HasMergeConflicts { - optionsMap["a"] = ShortLocalize("abortMerge", "abort merge") - optionsMap["m"] = ShortLocalize("resolveMergeConflicts", "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") - return renderfilesOptions(g, nil) - } - renderfilesOptions(g, &gitFile) - var content string - if gitFile.HasMergeConflicts { - return refreshMergePanel(g) - } - - 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") - } - commitMessageView := getCommitMessageView(g) - g.Update(func(g *gocui.Gui) error { - g.SetViewOnTop("commitMessage") - switchFocus(g, filesView, commitMessageView) - return nil - }) - return nil -} - -func handleCommitEditorPress(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") - } - runSubProcess(g, "git", "commit") - return nil -} - -func genericFileOpen(g *gocui.Gui, v *gocui.View, open func(*gocui.Gui, string) (string, error)) error { - file, err := getSelectedFile(g) - if err != nil { - if err != errNoFiles { - return err - } - return nil - } - if _, err := open(g, file.Name); err != nil { - return createErrorPanel(g, err.Error()) - } - return nil -} - -func handleFileEdit(g *gocui.Gui, v *gocui.View) error { - return genericFileOpen(g, v, editFile) -} - -func handleFileOpen(g *gocui.Gui, v *gocui.View) error { - return genericFileOpen(g, v, openFile) -} - -func handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error { - 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 { - return refreshFiles(g) -} - -func refreshStateGitFiles() { - // 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 -} - -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 && !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 -} - -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 -} - -func pullFiles(g *gocui.Gui, v *gocui.View) error { - createMessagePanel(g, v, "", "Pulling...") - go func() { - if output, err := gitPull(); err != nil { - createErrorPanel(g, output) - } else { - closeConfirmationPrompt(g) - refreshCommits(g) - refreshStatus(g) - } - refreshFiles(g) - }() - return nil -} - -func pushFiles(g *gocui.Gui, v *gocui.View) error { - createMessagePanel(g, v, "", "Pushing...") - go func() { - if output, err := gitPush(); err != nil { - createErrorPanel(g, output) - } else { - closeConfirmationPrompt(g) - refreshCommits(g) - refreshStatus(g) - } - }() - 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 createErrorPanel(g, "This file has no merge conflicts") - } - 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") - refreshStatus(g) - return refreshFiles(g) -} - -func handleResetHard(g *gocui.Gui, v *gocui.View) error { - return createConfirmationPanel(g, v, "Clear file panel", "Are you sure you want `reset --hard HEAD`? You may lose changes", func(g *gocui.Gui, v *gocui.View) error { - if err := gitResetHard(); err != nil { - createErrorPanel(g, err.Error()) - } - return refreshFiles(g) - }, nil) -} diff --git a/gitcommands.go b/gitcommands.go deleted file mode 100644 index c65841e99..000000000 --- a/gitcommands.go +++ /dev/null @@ -1,529 +0,0 @@ -package main - -import ( - - // "log" - "errors" - "fmt" - "os" - "os/exec" - "strings" - - "github.com/jesseduffield/gocui" - gitconfig "github.com/tcnksm/go-gitconfig" - git "gopkg.in/src-d/go-git.v4" -) - -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") -) - -// 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 -} - -// Commit : A git commit -type Commit struct { - Sha string - Name string - Pushed bool - DisplayString string -} - -// StashEntry : A git stash entry -type StashEntry struct { - 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 -} - -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 := gitCommitsToPush() - 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 - } - return runDirectCommand("git commit -m \"" + message + "\"") -} - -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 { - //