diff options
author | Mark Kopenga <mkopenga@gmail.com> | 2018-08-14 11:05:26 +0200 |
---|---|---|
committer | Mark Kopenga <mkopenga@gmail.com> | 2018-08-14 11:05:26 +0200 |
commit | dfafb988713a79664139d15ad471736a5a4b1b90 (patch) | |
tree | ebde4bd5c64ce8e1fe5e8670657c708984b0e8ff /pkg | |
parent | f2dfcb6e12d78f3e7b890d5bd43be7b032e1df88 (diff) |
tried to update to latest master
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/app/app.go | 71 | ||||
-rw-r--r-- | pkg/commands/branch.go | 39 | ||||
-rw-r--r-- | pkg/commands/git.go | 497 | ||||
-rw-r--r-- | pkg/commands/git_structs.go | 36 | ||||
-rw-r--r-- | pkg/commands/os.go | 174 | ||||
-rw-r--r-- | pkg/config/app_config.go | 45 | ||||
-rw-r--r-- | pkg/git/branch_list_builder.go | 160 | ||||
-rw-r--r-- | pkg/gui/branches_panel.go | 141 | ||||
-rw-r--r-- | pkg/gui/commit_message_panel.go | 50 | ||||
-rw-r--r-- | pkg/gui/commits_panel.go | 176 | ||||
-rw-r--r-- | pkg/gui/confirmation_panel.go | 149 | ||||
-rw-r--r-- | pkg/gui/files_panel.go | 400 | ||||
-rw-r--r-- | pkg/gui/gui.go | 331 | ||||
-rw-r--r-- | pkg/gui/keybindings.go | 93 | ||||
-rw-r--r-- | pkg/gui/merge_panel.go | 260 | ||||
-rw-r--r-- | pkg/gui/stash_panel.go | 97 | ||||
-rw-r--r-- | pkg/gui/status_panel.go | 42 | ||||
-rw-r--r-- | pkg/gui/view_helpers.go | 245 | ||||
-rw-r--r-- | pkg/i18n/i18n.go | 108 | ||||
-rw-r--r-- | pkg/utils/utils.go | 65 |
20 files changed, 3179 insertions, 0 deletions
diff --git a/pkg/app/app.go b/pkg/app/app.go new file mode 100644 index 000000000..d558ed250 --- /dev/null +++ b/pkg/app/app.go @@ -0,0 +1,71 @@ +package app + +import ( + "io" + "io/ioutil" + "os" + + "github.com/Sirupsen/logrus" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui" +) + +// App struct +type App struct { + closers []io.Closer + + Config config.AppConfigurer + Log *logrus.Logger + OSCommand *commands.OSCommand + GitCommand *commands.GitCommand + Gui *gui.Gui +} + +func newLogger(config config.AppConfigurer) *logrus.Logger { + log := logrus.New() + if !config.GetDebug() { + log.Out = ioutil.Discard + return log + } + file, err := os.OpenFile("development.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + panic("unable to log to file") // TODO: don't panic (also, remove this call to the `panic` function) + } + log.SetOutput(file) + return log +} + +// NewApp retruns a new applications +func NewApp(config config.AppConfigurer) (*App, error) { + app := &App{ + closers: []io.Closer{}, + Config: config, + } + var err error + app.Log = newLogger(config) + 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 + } + app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, config.GetVersion()) + 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/branch.go b/pkg/commands/branch.go new file mode 100644 index 000000000..13c26e766 --- /dev/null +++ b/pkg/commands/branch.go @@ -0,0 +1,39 @@ +package commands + +import ( + "strings" + + "github.com/fatih/color" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// Branch : A git branch +// duplicating this for now +type Branch struct { + Name string + Recency string +} + +// GetDisplayString returns the dispaly string of branch +func (b *Branch) GetDisplayString() string { + return utils.WithPadding(b.Recency, 4) + utils.ColoredString(b.Name, b.GetColor()) +} + +// GetColor branch color +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/pkg/commands/git.go b/pkg/commands/git.go new file mode 100644 index 000000000..44fd57f1c --- /dev/null +++ b/pkg/commands/git.go @@ -0,0 +1,497 @@ +package commands + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/utils" + gitconfig "github.com/tcnksm/go-gitconfig" + gogit "gopkg.in/src-d/go-git.v4" +) + +// GitCommand is our main git interface +type GitCommand struct { + Log *logrus.Logger + OSCommand *OSCommand + Worktree *gogit.Worktree + Repo *gogit.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() +} + +// GetStashEntries stash entryies +func (c *GitCommand) GetStashEntries() []StashEntry { + stashEntries := make([]StashEntry, 0) + rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'") + for i, line := range utils.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, + } +} + +// GetStashEntryDiff stash diff +func (c *GitCommand) GetStashEntryDiff(index int) (string, error) { + return c.OSCommand.RunCommandWithOutput("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 +} + +// GetStatusFiles git status files +func (c *GitCommand) GetStatusFiles() []File { + statusOutput, _ := c.GitStatus() + statusStrings := utils.SplitLines(statusOutput) + files := make([]File, 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) + file := File{ + Name: filename, + DisplayString: statusString, + HasStagedChanges: !includes([]string{" ", "U", "?"}, stagedChange), + HasUnstagedChanges: unstagedChange != " ", + Tracked: tracked, + Deleted: unstagedChange == "D" || stagedChange == "D", + HasMergeConflicts: change == "UU", + } + files = append(files, file) + } + c.Log.Info(files) // TODO: use a dumper-esque log here + return files +} + +// StashDo modify stash +func (c *GitCommand) StashDo(index int, method string) error { + return c.OSCommand.RunCommand("git stash " + method + " stash@{" + fmt.Sprint(index) + "}") +} + +// StashSave save stash +// TODO: before calling this, check if there is anything to save +func (c *GitCommand) StashSave(message string) error { + return c.OSCommand.RunCommand("git stash save " + c.OSCommand.Quote(message)) +} + +// MergeStatusFiles merge status files +func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File { + if len(oldFiles) == 0 { + return newFiles + } + + appendedIndexes := make([]int, 0) + + // retain position of files we already could see + result := make([]File, 0) + for _, oldFile := range oldFiles { + for newIndex, newFile := range newFiles { + if oldFile.Name == newFile.Name { + result = append(result, newFile) + appendedIndexes = append(appendedIndexes, newIndex) + break + } + } + } + + // append any new files to the end + for index, newFile := range newFiles { + if !includesInt(appendedIndexes, index) { + result = append(result, newFile) + } + } + + return result +} + +func (c *GitCommand) verifyInGitRepo() { + if output, err := c.OSCommand.RunCommandWithOutput("git status"); err != nil { + fmt.Println(output) + os.Exit(1) + } +} + +// GetBranchName branch name +func (c *GitCommand) GetBranchName() (string, error) { + return c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") +} + +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() { + r, err := gogit.PlainOpen(".") + if err != nil { + panic(err) + } + c.Repo = r + + w, err := r.Worktree() + if err != nil { + panic(err) + } + c.Worktree = w +} + +// ResetHard does the equivalent of `git reset --hard HEAD` +func (c *GitCommand) ResetHard() error { + return c.Worktree.Reset(&gogit.ResetOptions{Mode: gogit.HardReset}) +} + +// UpstreamDifferenceCount checks how many pushables/pullables there are for the +// current branch +func (c *GitCommand) UpstreamDifferenceCount() (string, string) { + pushableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --count") + if err != nil { + return "?", "?" + } + pullableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list head..@{u} --count") + if err != nil { + return "?", "?" + } + return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount) +} + +// GetCommitsToPush Returns the sha's of the commits that have not yet been pushed +// to the remote branch of the current branch +func (c *GitCommand) GetCommitsToPush() []string { + pushables, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --abbrev-commit") + if err != nil { + return make([]string, 0) + } + return utils.SplitLines(pushables) +} + +// RenameCommit renames the topmost commit with the given name +func (c *GitCommand) RenameCommit(name string) error { + return c.OSCommand.RunCommand("git commit --allow-empty --amend -m " + c.OSCommand.Quote(name)) +} + +// Fetch fetch git repo +func (c *GitCommand) Fetch() error { + return c.OSCommand.RunCommand("git fetch") +} + +// ResetToCommit reset to commit +func (c *GitCommand) ResetToCommit(sha string) error { + return c.OSCommand.RunCommand("git reset " + sha) +} + +// NewBranch create new branch +func (c *GitCommand) NewBranch(name string) error { + return c.OSCommand.RunCommand("git checkout -b " + name) +} + +// DeleteBranch delete branch +func (c *GitCommand) DeleteBranch(branch string) error { + return c.OSCommand.RunCommand("git branch -d " + branch) +} + +// ListStash list stash +func (c *GitCommand) ListStash() (string, error) { + return c.OSCommand.RunCommandWithOutput("git stash list") +} + +// Merge merge +func (c *GitCommand) Merge(branchName string) error { + return c.OSCommand.RunCommand("git merge --no-edit " + branchName) +} + +// AbortMerge abort merge +func (c *GitCommand) AbortMerge() error { + return c.OSCommand.RunCommand("git merge --abort") +} + +// UsingGpg tells us whether the user has gpg enabled so that we can know +// whether we need to run a subprocess to allow them to enter their password +func (c *GitCommand) UsingGpg() bool { + gpgsign, _ := gitconfig.Global("commit.gpgsign") + if gpgsign == "" { + gpgsign, _ = gitconfig.Local("commit.gpgsign") + } + if gpgsign == "" { + return false + } + return true +} + +// Commit commit to git +func (c *GitCommand) Commit(g *gocui.Gui, message string) (*exec.Cmd, error) { + command := "git commit -m " + c.OSCommand.Quote(message) + if c.UsingGpg() { + return c.OSCommand.PrepareSubProcess(c.OSCommand.Platform.shell, c.OSCommand.Platform.shellArg, command) + } + return nil, c.OSCommand.RunCommand(command) +} + +// Pull pull from repo +func (c *GitCommand) Pull() error { + return c.OSCommand.RunCommand("git pull --no-edit") +} + +// Push push to a branch +func (c *GitCommand) Push(branchName string) error { + return c.OSCommand.RunCommand("git push -u origin " + branchName) +} + +// SquashPreviousTwoCommits squashes a commit down to the one below it +// retaining the message of the higher commit +func (c *GitCommand) SquashPreviousTwoCommits(message string) error { + // TODO: test this + err := c.OSCommand.RunCommand("git reset --soft HEAD^") + if err != nil { + return err + } + // TODO: if password is required, we need to return a subprocess + return c.OSCommand.RunCommand("git commit --amend -m " + c.OSCommand.Quote(message)) +} + +// SquashFixupCommit squashes a 'FIXUP' commit into the commit beneath it, +// retaining the commit message of the lower commit +func (c *GitCommand) SquashFixupCommit(branchName string, shaValue 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 { + c.Log.Info(command) + output, err := c.OSCommand.RunCommandWithOutput(command) + ret += output + if err != nil { + c.Log.Info(ret) + break + } + } + if err != nil { + // We are already in an error state here so we're just going to append + // the output of these commands + output, _ := c.OSCommand.RunCommandWithOutput("git branch -d " + shaValue) + ret += output + output, _ = c.OSCommand.RunCommandWithOutput("git checkout " + branchName) + ret += output + } + if err != nil { + return errors.New(ret) + } + return nil +} + +// CatFile obtain the contents of a file +func (c *GitCommand) CatFile(file string) (string, error) { + return c.OSCommand.RunCommandWithOutput("cat " + file) +} + +// StageFile stages a file +func (c *GitCommand) StageFile(file string) error { + return c.OSCommand.RunCommand("git add " + c.OSCommand.Quote(file)) +} + +// UnStageFile unstages a file +func (c *GitCommand) UnStageFile(file string, tracked bool) error { + var command string + if tracked { + command = "git reset HEAD " + } else { + command = "git rm --cached " + } + return c.OSCommand.RunCommand(command + file) +} + +// GitStatus returns the plaintext short status of the repo +func (c *GitCommand) GitStatus() (string, error) { + return c.OSCommand.RunCommandWithOutput("git status --untracked-files=all --short") +} + +// IsInMergeState states whether we are still mid-merge +func (c *GitCommand) IsInMergeState() (bool, error) { + output, err := c.OSCommand.RunCommandWithOutput("git status --untracked-files=all") + if err != nil { + return false, err + } + return strings.Contains(output, "conclude merge") || strings.Contains(output, "unmerged paths"), nil +} + +// RemoveFile directly +func (c *GitCommand) RemoveFile(file File) error { + // if the file isn't tracked, we assume you want to delete it + if !file.Tracked { + return c.OSCommand.RunCommand("rm -rf ./" + file.Name) + } + // if the file is tracked, we assume you want to just check it out + return c.OSCommand.RunCommand("git checkout " + file.Name) +} + +// Checkout checks out a branch, with --force if you set the force arg to true +func (c *GitCommand) Checkout(branch string, force bool) error { + forceArg := "" + if force { + forceArg = "--force " + } + return c.OSCommand.RunCommand("git checkout " + forceArg + branch) +} + +// AddPatch prepares a subprocess for adding a patch by patch +// this will eventually be swapped out for a better solution inside the Gui +func (c *GitCommand) AddPatch(filename string) (*exec.Cmd, error) { + return c.OSCommand.PrepareSubProcess("git", "add", "--patch", filename) +} + +// PrepareCommitSubProcess prepares a subprocess for `git commit` +func (c *GitCommand) PrepareCommitSubProcess() (*exec.Cmd, error) { + return c.OSCommand.PrepareSubProcess("git", "commit") +} + +// GetBranchGraph gets the color-formatted graph of the log for the given branch +// Currently it limits the result to 100 commits, but when we get async stuff +// working we can do lazy loading +func (c *GitCommand) GetBranchGraph(branchName string) (string, error) { + return c.OSCommand.RunCommandWithOutput("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branchName) +} + +// 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 +} + +// GetCommits obtains the commits of the current branch +func (c *GitCommand) GetCommits() []Commit { + pushables := c.GetCommitsToPush() + log := c.GetLog() + commits := make([]Commit, 0) + // now we can split it up and turn it into commits + lines := utils.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 +} + +// GetLog gets the git log (currently limited to 30 commits for performance +// until we work out lazy loading +func (c *GitCommand) GetLog() string { + // currently limiting to 30 for performance reasons + // TODO: add lazyloading when you scroll down + result, err := c.OSCommand.RunCommandWithOutput("git log --oneline -30") + if err != nil { + // assume if there is an error there are no commits yet for this branch + return "" + } + return result +} + +// Ignore adds a file to the gitignore for the repo +func (c *GitCommand) Ignore(filename string) { + if _, err := c.OSCommand.RunDirectCommand("echo '" + filename + "' >> .gitignore"); err != nil { + panic(err) + } +} + +// Show shows the diff of a commit +func (c *GitCommand) Show(sha string) string { + result, err := c.OSCommand.RunCommandWithOutput("git show --color " + sha) + if err != nil { + panic(err) + } + return result +} + +// Diff returns the diff of a file +func (c *GitCommand) Diff(file File) string { + cachedArg := "" + fileName := file.Name + if file.HasStagedChanges && !file.HasUnstagedChanges { + cachedArg = "--cached" + } else { + // if the file is staged and has spaces in it, it comes pre-quoted + fileName = c.OSCommand.Quote(fileName) + } + deletedArg := "" + if file.Deleted { + deletedArg = "--" + } + trackedArg := "" + if !file.Tracked && !file.HasStagedChanges { + trackedArg = "--no-index /dev/null" + } + command := fmt.Sprintf("%s %s %s %s %s", "git diff --color ", cachedArg, deletedArg, trackedArg, fileName) + + // for now we assume an error means the file was deleted + s, _ := c.OSCommand.RunCommandWithOutput(command) + return s +} diff --git a/pkg/commands/git_structs.go b/pkg/commands/git_structs.go new file mode 100644 index 000000000..6b10b18bb --- /dev/null +++ b/pkg/commands/git_structs.go @@ -0,0 +1,36 @@ +package commands + +// 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 +} + +// Conflict : A git conflict with a start middle and end corresponding to line +// numbers in the file where the conflict bars appear +type Conflict struct { + Start int + Middle int + End int +} diff --git a/pkg/commands/os.go b/pkg/commands/os.go new file mode 100644 index 000000000..9f9819a5a --- /dev/null +++ b/pkg/commands/os.go @@ -0,0 +1,174 @@ +package commands + +import ( + "errors" + "os" + "os/exec" + "runtime" + + "github.com/davecgh/go-spew/spew" + + "github.com/mgutz/str" + + "github.com/Sirupsen/logrus" + gitconfig "github.com/tcnksm/go-gitconfig" +) + +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") + // ErrNoEditorDefined : When we can't find an editor to edit a file + ErrNoEditorDefined = errors.New("No editor defined in $VISUAL, $EDITOR, or git config") +) + +// 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 +} + +// RunCommandWithOutput wrapper around commands returning their output and error +func (c *OSCommand) RunCommandWithOutput(command string) (string, error) { + c.Log.WithField("command", command).Info("RunCommand") + splitCmd := str.ToArgv(command) + c.Log.Info(splitCmd) + cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput() + return sanitisedCommandOutput(cmdOut, err) +} + +// RunCommand runs a command and just returns the error +func (c *OSCommand) RunCommand(command string) error { + _, err := c.RunCommandWithOutput(command) + return err +} + +// RunDirectCommand wrapper around direct commands +func (c *OSCommand) RunDirectCommand(command string) (string, error) { + c.Log.WithField("command", command).Info("RunDirectCommand") + args := str.ToArgv(c.Platform.shellArg + " " + command) + c.Log.Info(spew.Sdump(args)) + + cmdOut, err := exec. + Command(c.Platform.shell, args...). + CombinedOutput() + return sanitisedCommandOutput(cmdOut, err) +} + +func sanitisedCommandOutput(output []byte, err error) (string, error) { + outputString := string(output) + if err != nil { + // errors like 'exit status 1' are not very useful so we'll create an error + // from the combined output + return outputString, errors.New(outputString) + } + return outputString, nil +} + +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: "\"", + } + } +} + +// GetOpenCommand get open command +func (c *OSCommand) 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 err := c.RunCommand("which " + name); err == nil { + return name, trail, nil + } + } + return "", "", ErrNoOpenCommand +} + +// VsCodeOpenFile opens the file in code, with the -r flag to open in the +// current window +// each of these open files needs to have the same function signature because +// they're being passed as arguments into another function, +// but only editFile actually returns a *exec.Cmd +func (c *OSCommand) VsCodeOpenFile(filename string) (*exec.Cmd, error) { + return nil, c.RunCommand("code -r " + filename) +} + +// SublimeOpenFile opens the filein sublime +// may be deprecated in the future +func (c *OSCommand) SublimeOpenFile(filename string) (*exec.Cmd, error) { + return nil, c.RunCommand("subl " + filename) +} + +// OpenFile opens a file with the given +func (c *OSCommand) OpenFile(filename string) (*exec.Cmd, error) { + cmdName, cmdTrail, err := c.GetOpenCommand() + if err != nil { + return nil, err + } + err = c.RunCommand(cmdName + " " + filename + cmdTrail) // TODO: test on linux + return nil, err +} + +// EditFile opens a file in a subprocess using whatever editor is available, +// falling back to core.editor, VISUAL, EDITOR, then vi +func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) { + editor, _ := gitconfig.Global("core.editor") + if editor == "" { + editor = os.Getenv("VISUAL") + } + if editor == "" { + editor = os.Getenv("EDITOR") + } + if editor == "" { + if err := c.RunCommand("which vi"); err == nil { + editor = "vi" + } + } + if editor == "" { + return nil, ErrNoEditorDefined + } + return c.PrepareSubProcess(editor, filename) +} + +// PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it +func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) (*exec.Cmd, error) { + subprocess := exec.Command(cmdName, commandArgs...) + return subprocess, nil +} + +// Quote wraps a message in platform-specific quotation marks +func (c *OSCommand) Quote(message string) string { + return c.Platform.escapedQuote + message + c.Platform.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/pkg/git/branch_list_builder.go b/pkg/git/branch_list_builder.go new file mode 100644 index 000000000..41e59c093 --- /dev/null +++ b/pkg/git/branch_list_builder.go @@ -0,0 +1,160 @@ |