From dcd461d29f21a9626d5298a03283b6d8b46312c3 Mon Sep 17 00:00:00 2001 From: Andrei Miulescu Date: Sun, 12 Aug 2018 19:31:27 +1000 Subject: [PATCH] Restrucure project in a way where it is more modular --- main.go | 31 ++++---- pkg/app/app.go | 49 ++++++++++++ pkg/commands/git.go | 71 +++++++++++++++++ gitcommands.go => pkg/commands/gitcommands.go | 27 ------- pkg/commands/os.go | 77 +++++++++++++++++++ pkg/config/app_config.go | 45 +++++++++++ branch.go => pkg/git/branch.go | 12 +-- .../git/branch_list_builder.go | 2 +- pkg/git/git_structs.go | 28 +++++++ gui.go => pkg/gui/gui.go | 11 +-- keybindings.go => pkg/gui/keybindings.go | 2 +- .../gui/panels/branches_panel.go | 0 .../gui/panels/commit_message_panel.go | 0 .../gui/panels/commits_panel.go | 0 .../gui/panels/confirmation_panel.go | 0 .../gui/panels/files_panel.go | 0 .../gui/panels/merge_panel.go | 0 .../gui/panels/stash_panel.go | 0 .../gui/panels/status_panel.go | 0 view_helpers.go => pkg/gui/view_helpers.go | 6 +- pkg/utils/utils.go | 49 ++++++++++++ utils.go | 5 -- 22 files changed, 357 insertions(+), 58 deletions(-) create mode 100644 pkg/app/app.go create mode 100644 pkg/commands/git.go rename gitcommands.go => pkg/commands/gitcommands.go (95%) create mode 100644 pkg/commands/os.go create mode 100644 pkg/config/app_config.go rename branch.go => pkg/git/branch.go (61%) rename branch_list_builder.go => pkg/git/branch_list_builder.go (99%) create mode 100644 pkg/git/git_structs.go rename gui.go => pkg/gui/gui.go (97%) rename keybindings.go => pkg/gui/keybindings.go (99%) rename branches_panel.go => pkg/gui/panels/branches_panel.go (100%) rename commit_message_panel.go => pkg/gui/panels/commit_message_panel.go (100%) rename commits_panel.go => pkg/gui/panels/commits_panel.go (100%) rename confirmation_panel.go => pkg/gui/panels/confirmation_panel.go (100%) rename files_panel.go => pkg/gui/panels/files_panel.go (100%) rename merge_panel.go => pkg/gui/panels/merge_panel.go (100%) rename stash_panel.go => pkg/gui/panels/stash_panel.go (100%) rename status_panel.go => pkg/gui/panels/status_panel.go (100%) rename view_helpers.go => pkg/gui/view_helpers.go (98%) create mode 100644 pkg/utils/utils.go diff --git a/main.go b/main.go index aec4051bf..aa157c108 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,8 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/app" + "github.com/jesseduffield/lazygit/pkg/config" git "gopkg.in/src-d/go-git.v4" ) @@ -24,8 +26,8 @@ var ( commit string version = "unversioned" + date string - date string debuggingFlag = flag.Bool("debug", false, "a boolean") versionFlag = flag.Bool("v", false, "Print the current version") @@ -77,15 +79,6 @@ func localLog(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") - } -} - // when building the binary, `version` is set as a compile-time variable, along // with `date` and `commit`. If this program has been opened directly via go, // we will populate the `version` with VERSION in the lazygit root directory @@ -112,7 +105,6 @@ func setupWorktree() { } func main() { - devLog("\n\n\n\n\n\n\n\n\n\n") flag.Parse() if version == "unversioned" { version = fallbackVersion() @@ -121,9 +113,22 @@ func main() { fmt.Printf("commit=%s, build date=%s, version=%s", commit, date, version) os.Exit(0) } - verifyInGitRepo() - navigateToRepoRootDirectory() + appConfig := &config.AppConfig{ + Name: "lazygit", + Version: version, + Commit: commit, + BuildDate: date, + Debug: *debuggingFlag, + } + app, err := app.NewApp(appConfig) + app.Log.Info(err) + + app.GitCommand.SetupGit() + // TODO remove this once r, w not used setupWorktree() + + app.Gui.Run() + for { if err := run(); err != nil { if err == gocui.ErrQuit { diff --git a/pkg/app/app.go b/pkg/app/app.go new file mode 100644 index 000000000..726567b01 --- /dev/null +++ b/pkg/app/app.go @@ -0,0 +1,49 @@ +package app + +import ( + "io" + + "github.com/Sirupsen/logrus" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/config" +) + +// App struct +type App struct { + closers []io.Closer + + Config config.AppConfigurer + Log *logrus.Logger + OSCommand *commands.OSCommand + GitCommand *commands.GitCommand +} + +// NewApp retruns a new applications +func NewApp(config config.AppConfigurer) (*App, error) { + app := &App{ + closers: []io.Closer{}, + Config: config, + } + var err error + app.Log = logrus.New() + app.OSCommand, err = commands.NewOSCommand(app.Log) + if err != nil { + return nil, err + } + app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand) + if err != nil { + return nil, err + } + return app, nil +} + +// Close closes any resources +func (app *App) Close() error { + for _, closer := range app.closers { + err := closer.Close() + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/commands/git.go b/pkg/commands/git.go new file mode 100644 index 000000000..17855b98e --- /dev/null +++ b/pkg/commands/git.go @@ -0,0 +1,71 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/Sirupsen/logrus" + git "gopkg.in/src-d/go-git.v4" +) + +// GitCommand is our main git interface +type GitCommand struct { + Log *logrus.Logger + OSCommand *OSCommand + Worktree *git.Worktree + Repo *git.Repository +} + +// NewGitCommand it runs git commands +func NewGitCommand(log *logrus.Logger, osCommand *OSCommand) (*GitCommand, error) { + gitCommand := &GitCommand{ + Log: log, + OSCommand: osCommand, + } + return gitCommand, nil +} + +// SetupGit sets git repo up +func (c *GitCommand) SetupGit() { + c.verifyInGitRepo() + c.navigateToRepoRootDirectory() + c.setupWorktree() +} + +func (c *GitCommand) GitIgnore(filename string) { + if _, err := c.OSCommand.RunDirectCommand("echo '" + filename + "' >> .gitignore"); err != nil { + panic(err) + } +} + +func (c *GitCommand) verifyInGitRepo() { + if output, err := c.OSCommand.RunCommand("git status"); err != nil { + fmt.Println(output) + os.Exit(1) + } +} + +func (c *GitCommand) navigateToRepoRootDirectory() { + _, err := os.Stat(".git") + for os.IsNotExist(err) { + c.Log.Debug("going up a directory to find the root") + os.Chdir("..") + _, err = os.Stat(".git") + } +} + +func (c *GitCommand) setupWorktree() { + var err error + r, err := git.PlainOpen(".") + if err != nil { + panic(err) + } + c.Repo = r + + w, err := r.Worktree() + c.Worktree = w + if err != nil { + panic(err) + } + c.Worktree = w +} diff --git a/gitcommands.go b/pkg/commands/gitcommands.go similarity index 95% rename from gitcommands.go rename to pkg/commands/gitcommands.go index 14dee6dac..bdc23fef2 100644 --- a/gitcommands.go +++ b/pkg/commands/gitcommands.go @@ -21,33 +21,6 @@ var ( 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)) diff --git a/pkg/commands/os.go b/pkg/commands/os.go new file mode 100644 index 000000000..c36990aff --- /dev/null +++ b/pkg/commands/os.go @@ -0,0 +1,77 @@ +package commands + +import ( + "os/exec" + "runtime" + "strings" + + "github.com/Sirupsen/logrus" +) + +// Platform stores the os state +type platform struct { + os string + shell string + shellArg string + escapedQuote string +} + +// OSCommand holds all the os commands +type OSCommand struct { + Log *logrus.Logger + Platform platform +} + +// NewOSCommand os command runner +func NewOSCommand(log *logrus.Logger) (*OSCommand, error) { + osCommand := &OSCommand{ + Log: log, + Platform: getPlatform(), + } + return osCommand, nil +} + +// RunCommand wrapper around commands +func (c *OSCommand) RunCommand(command string) (string, error) { + c.Log.WithField("command", command).Info("RunCommand") + splitCmd := strings.Split(command, " ") + cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput() + return sanitisedCommandOutput(cmdOut, err) +} + +// RunDirectCommand wrapper around direct commands +func (c *OSCommand) RunDirectCommand(command string) (string, error) { + c.Log.WithField("command", command).Info("RunDirectCommand") + + cmdOut, err := exec. + Command(c.Platform.shell, c.Platform.shellArg, command). + CombinedOutput() + return sanitisedCommandOutput(cmdOut, err) +} + +func sanitisedCommandOutput(output []byte, err error) (string, error) { + outputString := string(output) + if outputString == "" && err != nil { + return err.Error(), err + } + return outputString, err +} + +func getPlatform() platform { + switch runtime.GOOS { + case "windows": + return platform{ + os: "windows", + shell: "cmd", + shellArg: "/c", + escapedQuote: "\\\"", + } + default: + return platform{ + os: runtime.GOOS, + shell: "bash", + shellArg: "-c", + escapedQuote: "\"", + } + } +} diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go new file mode 100644 index 000000000..98e56dea2 --- /dev/null +++ b/pkg/config/app_config.go @@ -0,0 +1,45 @@ +package config + +// AppConfig contains the base configuration fields required for lazygit. +type AppConfig struct { + Debug bool `long:"debug" env:"DEBUG" default:"false"` + Version string `long:"version" env:"VERSION" default:"unversioned"` + Commit string `long:"commit" env:"COMMIT"` + BuildDate string `long:"build-date" env:"BUILD_DATE"` + Name string `long:"name" env:"NAME" default:"lazygit"` +} + +// AppConfigurer interface allows individual app config structs to inherit Fields +// from AppConfig and still be used by lazygit. +type AppConfigurer interface { + GetDebug() bool + GetVersion() string + GetCommit() string + GetBuildDate() string + GetName() string +} + +// GetDebug returns debug flag +func (c *AppConfig) GetDebug() bool { + return c.Debug +} + +// GetVersion returns debug flag +func (c *AppConfig) GetVersion() string { + return c.Version +} + +// GetCommit returns debug flag +func (c *AppConfig) GetCommit() string { + return c.Commit +} + +// GetBuildDate returns debug flag +func (c *AppConfig) GetBuildDate() string { + return c.BuildDate +} + +// GetName returns debug flag +func (c *AppConfig) GetName() string { + return c.Name +} diff --git a/branch.go b/pkg/git/branch.go similarity index 61% rename from branch.go rename to pkg/git/branch.go index 78c2e55aa..e0fd75a73 100644 --- a/branch.go +++ b/pkg/git/branch.go @@ -1,4 +1,4 @@ -package main +package git import ( "strings" @@ -12,11 +12,13 @@ type Branch struct { Recency string } -func (b *Branch) getDisplayString() string { - return withPadding(b.Recency, 4) + coloredString(b.Name, b.getColor()) -} +// GetDisplayString returns the dispaly string of branch +// func (b *Branch) GetDisplayString() string { +// return gui.withPadding(b.Recency, 4) + gui.coloredString(b.Name, b.getColor()) +// } -func (b *Branch) getColor() color.Attribute { +// GetColor branch color +func (b *Branch) GetColor() color.Attribute { switch b.getType() { case "feature": return color.FgGreen diff --git a/branch_list_builder.go b/pkg/git/branch_list_builder.go similarity index 99% rename from branch_list_builder.go rename to pkg/git/branch_list_builder.go index 1d4dc338d..1abf11fcc 100644 --- a/branch_list_builder.go +++ b/pkg/git/branch_list_builder.go @@ -1,4 +1,4 @@ -package main +package git import ( "regexp" diff --git a/pkg/git/git_structs.go b/pkg/git/git_structs.go new file mode 100644 index 000000000..711f25f4b --- /dev/null +++ b/pkg/git/git_structs.go @@ -0,0 +1,28 @@ +package git + +// File : A staged/unstaged file +// TODO: decide whether to give all of these the Git prefix +type File 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 +} diff --git a/gui.go b/pkg/gui/gui.go similarity index 97% rename from gui.go rename to pkg/gui/gui.go index 589dabea3..f35a4811f 100644 --- a/gui.go +++ b/pkg/gui/gui.go @@ -1,4 +1,4 @@ -package main +package gui import ( @@ -13,16 +13,17 @@ import ( "github.com/golang-collections/collections/stack" "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/git" ) // OverlappingEdges determines if panel edges overlap var OverlappingEdges = false type stateType struct { - GitFiles []GitFile - Branches []Branch - Commits []Commit - StashEntries []StashEntry + GitFiles []git.File + Branches []git.Branch + Commits []git.Commit + StashEntries []git.StashEntry PreviousView string HasMergeConflicts bool ConflictIndex int diff --git a/keybindings.go b/pkg/gui/keybindings.go similarity index 99% rename from keybindings.go rename to pkg/gui/keybindings.go index afaa09527..23f320ceb 100644 --- a/keybindings.go +++ b/pkg/gui/keybindings.go @@ -1,4 +1,4 @@ -package main +package gui import "github.com/jesseduffield/gocui" diff --git a/branches_panel.go b/pkg/gui/panels/branches_panel.go similarity index 100% rename from branches_panel.go rename to pkg/gui/panels/branches_panel.go diff --git a/commit_message_panel.go b/pkg/gui/panels/commit_message_panel.go similarity index 100% rename from commit_message_panel.go rename to pkg/gui/panels/commit_message_panel.go diff --git a/commits_panel.go b/pkg/gui/panels/commits_panel.go similarity index 100% rename from commits_panel.go rename to pkg/gui/panels/commits_panel.go diff --git a/confirmation_panel.go b/pkg/gui/panels/confirmation_panel.go similarity index 100% rename from confirmation_panel.go rename to pkg/gui/panels/confirmation_panel.go diff --git a/files_panel.go b/pkg/gui/panels/files_panel.go similarity index 100% rename from files_panel.go rename to pkg/gui/panels/files_panel.go diff --git a/merge_panel.go b/pkg/gui/panels/merge_panel.go similarity index 100% rename from merge_panel.go rename to pkg/gui/panels/merge_panel.go diff --git a/stash_panel.go b/pkg/gui/panels/stash_panel.go similarity index 100% rename from stash_panel.go rename to pkg/gui/panels/stash_panel.go diff --git a/status_panel.go b/pkg/gui/panels/status_panel.go similarity index 100% rename from status_panel.go rename to pkg/gui/panels/status_panel.go diff --git a/view_helpers.go b/pkg/gui/view_helpers.go similarity index 98% rename from view_helpers.go rename to pkg/gui/view_helpers.go index b28e84efb..fc252a25c 100644 --- a/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -1,4 +1,4 @@ -package main +package gui import ( "fmt" @@ -233,3 +233,7 @@ func getCommitMessageView(g *gocui.Gui) *gocui.View { v, _ := g.View("commitMessage") return v } + +func trimmedContent(v *gocui.View) string { + return strings.TrimSpace(v.Buffer()) +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 000000000..01556ea4f --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,49 @@ +package utils + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" + + "github.com/fatih/color" +) + +func splitLines(multilineString string) []string { + multilineString = strings.Replace(multilineString, "\r", "", -1) + if multilineString == "" || multilineString == "\n" { + return make([]string, 0) + } + lines := strings.Split(multilineString, "\n") + if lines[len(lines)-1] == "" { + return lines[:len(lines)-1] + } + return lines +} + +func withPadding(str string, padding int) string { + if padding-len(str) < 0 { + return str + } + return str + strings.Repeat(" ", padding-len(str)) +} + +func coloredString(str string, colorAttribute color.Attribute) string { + colour := color.New(colorAttribute) + return coloredStringDirect(str, colour) +} + +// used for aggregating a few color attributes rather than just sending a single one +func coloredStringDirect(str string, colour *color.Color) string { + return colour.SprintFunc()(fmt.Sprint(str)) +} + +// used to get the project name +func getCurrentProject() string { + pwd, err := os.Getwd() + if err != nil { + log.Fatalln(err.Error()) + } + return filepath.Base(pwd) +} diff --git a/utils.go b/utils.go index e2de46233..df7d04818 100644 --- a/utils.go +++ b/utils.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/fatih/color" - "github.com/jesseduffield/gocui" ) func splitLines(multilineString string) []string { @@ -23,10 +22,6 @@ func splitLines(multilineString string) []string { return lines } -func trimmedContent(v *gocui.View) string { - return strings.TrimSpace(v.Buffer()) -} - func withPadding(str string, padding int) string { if padding-len(str) < 0 { return str