summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2020-09-29 20:03:39 +1000
committerJesse Duffield <jessedduffield@gmail.com>2020-09-29 20:48:49 +1000
commit72af7e41778bca93d82fa668641f515fba1d92bc (patch)
tree7e755e857be72205ee99641d5eb5d4556151ad8f /pkg
parent1767f91047a35318f6b1e469199c8a7f547f2afc (diff)
factor out code from git.go
Diffstat (limited to 'pkg')
-rw-r--r--pkg/commands/branches.go157
-rw-r--r--pkg/commands/commits.go93
-rw-r--r--pkg/commands/config.go52
-rw-r--r--pkg/commands/files.go270
-rw-r--r--pkg/commands/git.go1141
-rw-r--r--pkg/commands/git_test.go2
-rw-r--r--pkg/commands/loading_branches.go (renamed from pkg/commands/branch_list_builder.go)0
-rw-r--r--pkg/commands/loading_commits.go (renamed from pkg/commands/commit_list_builder.go)0
-rw-r--r--pkg/commands/loading_commits_test.go (renamed from pkg/commands/commit_list_builder_test.go)0
-rw-r--r--pkg/commands/loading_files.go9
-rw-r--r--pkg/commands/patch_rebases.go28
-rw-r--r--pkg/commands/rebasing.go287
-rw-r--r--pkg/commands/remotes.go40
-rw-r--r--pkg/commands/stash_entries.go58
-rw-r--r--pkg/commands/status.go48
-rw-r--r--pkg/commands/sync.go74
-rw-r--r--pkg/commands/tags.go13
-rw-r--r--pkg/gui/rebase_options_panel.go2
-rw-r--r--pkg/gui/remotes_panel.go6
19 files changed, 1168 insertions, 1112 deletions
diff --git a/pkg/commands/branches.go b/pkg/commands/branches.go
new file mode 100644
index 000000000..2da076e3a
--- /dev/null
+++ b/pkg/commands/branches.go
@@ -0,0 +1,157 @@
+package commands
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+// NewBranch create new branch
+func (c *GitCommand) NewBranch(name string, base string) error {
+ return c.OSCommand.RunCommand("git checkout -b %s %s", name, base)
+}
+
+// CurrentBranchName get the current branch name and displayname.
+// the first returned string is the name and the second is the displayname
+// e.g. name is 123asdf and displayname is '(HEAD detached at 123asdf)'
+func (c *GitCommand) CurrentBranchName() (string, string, error) {
+ branchName, err := c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
+ if err == nil && branchName != "HEAD\n" {
+ trimmedBranchName := strings.TrimSpace(branchName)
+ return trimmedBranchName, trimmedBranchName, nil
+ }
+ output, err := c.OSCommand.RunCommandWithOutput("git branch --contains")
+ if err != nil {
+ return "", "", err
+ }
+ for _, line := range utils.SplitLines(output) {
+ re := regexp.MustCompile(CurrentBranchNameRegex)
+ match := re.FindStringSubmatch(line)
+ if len(match) > 0 {
+ branchName = match[1]
+ displayBranchName := match[0][2:]
+ return branchName, displayBranchName, nil
+ }
+ }
+ return "HEAD", "HEAD", nil
+}
+
+// DeleteBranch delete branch
+func (c *GitCommand) DeleteBranch(branch string, force bool) error {
+ command := "git branch -d"
+
+ if force {
+ command = "git branch -D"
+ }
+
+ return c.OSCommand.RunCommand("%s %s", command, branch)
+}
+
+// Checkout checks out a branch (or commit), with --force if you set the force arg to true
+type CheckoutOptions struct {
+ Force bool
+ EnvVars []string
+}
+
+func (c *GitCommand) Checkout(branch string, options CheckoutOptions) error {
+ forceArg := ""
+ if options.Force {
+ forceArg = "--force "
+ }
+ return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git checkout %s %s", forceArg, branch), oscommands.RunCommandOptions{EnvVars: options.EnvVars})
+}
+
+// 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) {
+ cmdStr := c.GetBranchGraphCmdStr(branchName)
+ return c.OSCommand.RunCommandWithOutput(cmdStr)
+}
+
+func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) {
+ output, err := c.OSCommand.RunCommandWithOutput("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", branchName)
+ return strings.TrimSpace(output), err
+}
+
+func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string {
+ branchLogCmdTemplate := c.Config.GetUserConfig().GetString("git.branchLogCmd")
+ templateValues := map[string]string{
+ "branchName": branchName,
+ }
+ return utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)
+}
+
+func (c *GitCommand) SetUpstreamBranch(upstream string) error {
+ return c.OSCommand.RunCommand("git branch -u %s", upstream)
+}
+
+func (c *GitCommand) SetBranchUpstream(remoteName string, remoteBranchName string, branchName string) error {
+ return c.OSCommand.RunCommand("git branch --set-upstream-to=%s/%s %s", remoteName, remoteBranchName, branchName)
+}
+
+func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
+ return c.GetCommitDifferences("HEAD", "HEAD@{u}")
+}
+
+func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string, string) {
+ return c.GetCommitDifferences(branchName, branchName+"@{u}")
+}
+
+// GetCommitDifferences checks how many pushables/pullables there are for the
+// current branch
+func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
+ command := "git rev-list %s..%s --count"
+ pushableCount, err := c.OSCommand.RunCommandWithOutput(command, to, from)
+ if err != nil {
+ return "?", "?"
+ }
+ pullableCount, err := c.OSCommand.RunCommandWithOutput(command, from, to)
+ if err != nil {
+ return "?", "?"
+ }
+ return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount)
+}
+
+type MergeOpts struct {
+ FastForwardOnly bool
+}
+
+// Merge merge
+func (c *GitCommand) Merge(branchName string, opts MergeOpts) error {
+ mergeArgs := c.Config.GetUserConfig().GetString("git.merging.args")
+
+ command := fmt.Sprintf("git merge --no-edit %s %s", mergeArgs, branchName)
+ if opts.FastForwardOnly {
+ command = fmt.Sprintf("%s --ff-only", command)
+ }
+
+ return c.OSCommand.RunCommand(command)
+}
+
+// AbortMerge abort merge
+func (c *GitCommand) AbortMerge() error {
+ return c.OSCommand.RunCommand("git merge --abort")
+}
+
+func (c *GitCommand) IsHeadDetached() bool {
+ err := c.OSCommand.RunCommand("git symbolic-ref -q HEAD")
+ return err != nil
+}
+
+// ResetHardHead runs `git reset --hard`
+func (c *GitCommand) ResetHard(ref string) error {
+ return c.OSCommand.RunCommand("git reset --hard " + ref)
+}
+
+// ResetSoft runs `git reset --soft HEAD`
+func (c *GitCommand) ResetSoft(ref string) error {
+ return c.OSCommand.RunCommand("git reset --soft " + ref)
+}
+
+func (c *GitCommand) RenameBranch(oldName string, newName string) error {
+ return c.OSCommand.RunCommand("git branch --move %s %s", oldName, newName)
+}
diff --git a/pkg/commands/commits.go b/pkg/commands/commits.go
new file mode 100644
index 000000000..7fc4cc2a9
--- /dev/null
+++ b/pkg/commands/commits.go
@@ -0,0 +1,93 @@
+package commands
+
+import (
+ "fmt"
+ "os/exec"
+ "strconv"
+ "strings"
+
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/models"
+)
+
+// 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 %s", c.OSCommand.Quote(name))
+}
+
+// ResetToCommit reset to commit
+func (c *GitCommand) ResetToCommit(sha string, strength string, options oscommands.RunCommandOptions) error {
+ return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git reset --%s %s", strength, sha), options)
+}
+
+// Commit commits to git
+func (c *GitCommand) Commit(message string, flags string) (*exec.Cmd, error) {
+ command := fmt.Sprintf("git commit %s -m %s", flags, strconv.Quote(message))
+ if c.usingGpg() {
+ return c.OSCommand.ShellCommandFromString(command), nil
+ }
+
+ return nil, c.OSCommand.RunCommand(command)
+}
+
+// Get the subject of the HEAD commit
+func (c *GitCommand) GetHeadCommitMessage() (string, error) {
+ cmdStr := "git log -1 --pretty=%s"
+ message, err := c.OSCommand.RunCommandWithOutput(cmdStr)
+ return strings.TrimSpace(message), err
+}
+
+func (c *GitCommand) GetCommitMessage(commitSha string) (string, error) {
+ cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha
+ messageWithHeader, err := c.OSCommand.RunCommandWithOutput(cmdStr)
+ message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "\n")
+ return strings.TrimSpace(message), err
+}
+
+// AmendHead amends HEAD with whatever is staged in your working tree
+func (c *GitCommand) AmendHead() (*exec.Cmd, error) {
+ command := "git commit --amend --no-edit --allow-empty"
+ if c.usingGpg() {
+ return c.OSCommand.ShellCommandFromString(command), nil
+ }
+
+ return nil, c.OSCommand.RunCommand(command)
+}
+
+// PrepareCommitAmendSubProcess prepares a subprocess for `git commit --amend --allow-empty`
+func (c *GitCommand) PrepareCommitAmendSubProcess() *exec.Cmd {
+ return c.OSCommand.PrepareSubProcess("git", "commit", "--amend", "--allow-empty")
+}
+
+func (c *GitCommand) ShowCmdStr(sha string, filterPath string) string {
+ filterPathArg := ""
+ if filterPath != "" {
+ filterPathArg = fmt.Sprintf(" -- %s", c.OSCommand.Quote(filterPath))
+ }
+ return fmt.Sprintf("git show --submodule --color=%s --no-renames --stat -p %s %s", c.colorArg(), sha, filterPathArg)
+}
+
+// Revert reverts the selected commit by sha
+func (c *GitCommand) Revert(sha string) error {
+ return c.OSCommand.RunCommand("git revert %s", sha)
+}
+
+// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
+func (c *GitCommand) CherryPickCommits(commits []*models.Commit) error {
+ todo := ""
+ for _, commit := range commits {
+ todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo
+ }
+
+ cmd, err := c.PrepareInteractiveRebaseCommand("HEAD", todo, false)
+ if err != nil {
+ return err
+ }
+
+ return c.OSCommand.RunPreparedCommand(cmd)
+}
+
+// CreateFixupCommit creates a commit that fixes up a previous commit
+func (c *GitCommand) CreateFixupCommit(sha string) error {
+ return c.OSCommand.RunCommand("git commit --fixup=%s", sha)
+}
diff --git a/pkg/commands/config.go b/pkg/commands/config.go
new file mode 100644
index 000000000..018de2724
--- /dev/null
+++ b/pkg/commands/config.go
@@ -0,0 +1,52 @@
+package commands
+
+import (
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+func (c *GitCommand) ConfiguredPager() string {
+ if os.Getenv("GIT_PAGER") != "" {
+ return os.Getenv("GIT_PAGER")
+ }
+ if os.Getenv("PAGER") != "" {
+ return os.Getenv("PAGER")
+ }
+ output, err := c.OSCommand.RunCommandWithOutput("git config --get-all core.pager")
+ if err != nil {
+ return ""
+ }
+ trimmedOutput := strings.TrimSpace(output)
+ return strings.Split(trimmedOutput, "\n")[0]
+}
+
+func (c *GitCommand) GetPager(width int) string {
+ useConfig := c.Config.GetUserConfig().GetBool("git.paging.useConfig")
+ if useConfig {
+ pager := c.ConfiguredPager()
+ return strings.Split(pager, "| less")[0]
+ }
+
+ templateValues := map[string]string{
+ "columnWidth": strconv.Itoa(width/2 - 6),
+ }
+
+ pagerTemplate := c.Config.GetUserConfig().GetString("git.paging.pager")
+ return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
+}
+
+func (c *GitCommand) colorArg() string {
+ return c.Config.GetUserConfig().GetString("git.paging.colorArg")
+}
+
+func (c *GitCommand) GetConfigValue(key string) string {
+ output, err := c.OSCommand.RunCommandWithOutput("git config --get %s", key)
+ if err != nil {
+ // looks like this returns an error if there is no matching value which we're okay with
+ return ""
+ }
+ return strings.TrimSpace(output)
+}
diff --git a/pkg/commands/files.go b/pkg/commands/files.go
new file mode 100644
index 000000000..e52de446b
--- /dev/null
+++ b/pkg/commands/files.go
@@ -0,0 +1,270 @@
+package commands
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/go-errors/errors"
+ "github.com/jesseduffield/lazygit/pkg/models"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+// CatFile obtains the content of a file
+func (c *GitCommand) CatFile(fileName string) (string, error) {
+ return c.OSCommand.RunCommandWithOutput("%s %s", c.OSCommand.Platform.CatCmd, c.OSCommand.Quote(fileName))
+}
+
+// StageFile stages a file
+func (c *GitCommand) StageFile(fileName string) error {
+ // renamed files look like "file1 -> file2"
+ fileNames := strings.Split(fileName, " -> ")
+ return c.OSCommand.RunCommand("git add %s", c.OSCommand.Quote(fileNames[len(fileNames)-1]))
+}
+
+// StageAll stages all files
+func (c *GitCommand) StageAll() error {
+ return c.OSCommand.RunCommand("git add -A")
+}
+
+// UnstageAll stages all files
+func (c *GitCommand) UnstageAll() error {
+ return c.OSCommand.RunCommand("git reset")
+}
+
+// UnStageFile unstages a file
+func (c *GitCommand) UnStageFile(fileName string, tracked bool) error {
+ command := "git rm --cached %s"
+ if tracked {
+ command = "git reset HEAD %s"
+ }
+
+ // renamed files look like "file1 -> file2"
+ fileNames := strings.Split(fileName, " -> ")
+ for _, name := range fileNames {
+ if err := c.OSCommand.RunCommand(command, c.OSCommand.Quote(name)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (c *GitCommand) BeforeAndAfterFileForRename(file *models.File) (*models.File, *models.File, error) {
+
+ if !file.IsRename() {
+ return nil, nil, errors.New("Expected renamed file")
+ }
+
+ // we've got a file that represents a rename from one file to another. Unfortunately
+ // our File abstraction fails to consider this case, so here we will refetch
+ // all files, passing the --no-renames flag and then recursively call the function
+ // again for the before file and after file. At some point we should fix the abstraction itself
+
+ split := strings.Split(file.Name, " -> ")
+ filesWithoutRenames := c.GetStatusFiles(GetStatusFileOptions{NoRenames: true})
+ var beforeFile *models.File
+ var afterFile *models.File
+ for _, f := range filesWithoutRenames {
+ if f.Name == split[0] {
+ beforeFile = f
+ }
+ if f.Name == split[1] {
+ afterFile = f
+ }
+ }
+
+ if beforeFile == nil || afterFile == nil {
+ return nil, nil, errors.New("Could not find deleted file or new file for file rename")
+ }
+
+ if beforeFile.IsRename() || afterFile.IsRename() {
+ // probably won't happen but we want to ensure we don't get an infinite loop
+ return nil, nil, errors.New("Nested rename found")
+ }
+
+ return beforeFile, afterFile, nil
+}
+
+// DiscardAllFileChanges directly
+func (c *GitCommand) DiscardAllFileChanges(file *models.File) error {
+ if file.IsRename() {
+ beforeFile, afterFile, err := c.BeforeAndAfterFileForRename(file)
+ if err != nil {
+ return err
+ }
+
+ if err := c.DiscardAllFileChanges(beforeFile); err != nil {
+ return err
+ }
+
+ if err := c.DiscardAllFileChanges(afterFile); err != nil {
+ return err
+ }
+
+ return nil
+ }
+
+ // if the file isn't tracked, we assume you want to delete it
+ quotedFileName := c.OSCommand.Quote(file.Name)
+ if file.HasStagedChanges || file.HasMergeConflicts {
+ if err := c.OSCommand.RunCommand("git reset -- %s", quotedFileName); err != nil {
+ return err
+ }
+ }
+
+ if !file.Tracked {
+ return c.removeFile(file.Name)
+ }
+ return c.DiscardUnstagedFileChanges(file)
+}
+
+// DiscardUnstagedFileChanges directly
+func (c *GitCommand) DiscardUnstagedFileChanges(file *models.File) error {
+ quotedFileName := c.OSCommand.Quote(file.Name)
+ return c.OSCommand.RunCommand("git checkout -- %s", quotedFileName)
+}
+
+// Ignore adds a file to the gitignore for the repo
+func (c *GitCommand) Ignore(filename string) error {
+ return c.OSCommand.AppendLineToFile(".gitignore", filename)
+}
+
+// WorktreeFileDiff returns the diff of a file
+func (c *GitCommand) WorktreeFileDiff(file *models.File, plain bool, cached bool) string {
+ // for now we assume an error means the file was deleted
+ s, _ := c.OSCommand.RunCommandWithOutput(c.WorktreeFileDiffCmdStr(file, plain, cached))
+ return s
+}
+
+func (c *GitCommand) WorktreeFileDiffCmdStr(file *models.File, plain bool, cached bool) string {
+ cachedArg := ""
+ trackedArg := "--"
+ colorArg := c.colorArg()
+ split := strings.Split(file.Name, " -> ") // in case of a renamed file we get the new filename
+ fileName := c.OSCommand.Quote(split[len(split)-1])
+ if cached {
+ cachedArg = "--cached"
+ }
+ if !file.Tracked && !file.HasStagedChanges && !cached {
+ trackedArg = "--no-index /dev/null"
+ }
+ if plain {
+ colorArg = "never"
+ }
+
+ return fmt.Sprintf("git diff --submodule --no-ext-diff --color=%s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
+}
+
+func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
+ filepath := filepath.Join(c.Config.GetUserConfigDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
+ c.Log.Infof("saving temporary patch to %s", filepath)
+ if err := c.OSCommand.CreateFileWithContent(filepath, patch); err != nil {
+ return err
+ }
+
+ flagStr := ""
+ for _, flag := range flags {
+ flagStr += " --" + flag
+ }
+
+ return c.OSCommand.RunCommand("git apply %s %s", flagStr, c.OSCommand.Quote(filepath))
+}
+
+// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc
+// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode.
+func (c *GitCommand) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) {
+ cmdStr := c.ShowFileDiffCmdStr(from, to, reverse, fileName, plain)
+ return c.OSCommand.RunCommandWithOutput(cmdStr)
+}
+
+func (c *GitCommand) ShowFileDiffCmdStr(from string, to string, reverse bool, fileName string, plain bool) string {
+ colorArg := c.colorArg()
+ if plain {
+ colorArg = "never"
+ }
+
+ reverseFlag := ""
+ if reverse {
+ reverseFlag = " -R "
+ }
+
+ return fmt.Sprintf("git diff --submodule --no-ext-diff --no-renames --color=%s %s %s %s -- %s", colorArg, from, to, reverseFlag, fileName)
+}
+
+// CheckoutFile checks out the file for the given commit
+func (c *GitCommand) CheckoutFile(commitSha, fileName string) error {
+ return c.OSCommand.RunCommand("git checkout %s %s", commitSha, fileName)
+}
+
+// DiscardOldFileChanges discards changes to a file from an old commit
+func (c *GitCommand) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, fileName string) error {
+ if err := c.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
+ return err
+ }
+
+ // check if file exists in previous commit (this command returns an error if the file doesn't exist)
+ if err := c.OSCommand.RunCommand("git cat-file -e HEAD^:%s", fileName); err != nil {
+ if err := c.OSCommand.Remove(fileName); err != nil {
+ return err
+ }
+ if err := c.StageFile(fileName); err != nil {
+ return err
+ }
+ } else if err := c.CheckoutFile("HEAD^", fileName); err != nil {
+ return err
+ }
+
+ // amend the commit
+ cmd, err := c.AmendHead()
+ if cmd != nil {
+ return errors.New("received unexpected pointer to cmd")
+ }
+ if err != nil {
+ return err
+ }
+
+ // continue
+ return c.GenericMergeOrRebaseAction("rebase", "continue")
+}
+
+// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .`
+func (c *GitCommand) DiscardAnyUnstagedFileChanges() error {
+ return c.OSCommand.RunCommand("git checkout -- .")
+}
+
+// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
+func (c *GitCommand) RemoveTrackedFiles(name string) error {
+ return c.OSCommand.RunCommand("git rm -r --cached %s", name)
+}
+
+// RemoveUntrackedFiles runs `git clean -fd`
+func (c *GitCommand) RemoveUntrackedFiles() error {
+ return c.OSCommand.RunCommand("git clean -fd")
+}
+
+// ResetAndClean removes all unstaged changes and removes all untracked files
+func (c *GitCommand) ResetAndClean() error {
+ submoduleConfigs, err := c.GetSubmoduleConfigs()
+ if err != nil {
+ return err
+ }
+
+ if len(submoduleConfigs) > 0 {
+ for _, config := range submoduleConfigs {
+ if err := c.SubmoduleStash(config); err != nil {
+ return err
+ }
+ }
+
+ if err := c.SubmoduleUpdateAll(); err != nil {
+ return err
+ }
+ }
+
+ if err := c.ResetHard("HEAD"); err != nil {
+ return err
+ }
+
+ return c.RemoveUntrackedFiles()
+}
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 278b1d557..ab2819f75 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -1,17 +1,10 @@
package commands
import (
- "fmt"
"io/ioutil"
"os"
- "os/exec"
"path/filepath"
- "regexp"
- "strconv"
"strings"
- "time"
-
- "github.com/mgutz/str"
"github.com/go-errors/errors"
@@ -21,7 +14,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/i18n"
- "github.com/jesseduffield/lazygit/pkg/models"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
gitconfig "github.com/tcnksm/go-gitconfig"
@@ -33,83 +25,6 @@ import (
// and returns '264fc6f5' as the second match
const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$`
-func verifyInGitRepo(runCmd func(string, ...interface{}) error) error {
- return runCmd("git status")
-}
-
-func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
- gitDir := env.GetGitDirEnv()
- if gitDir != "" {
- // we've been given the git directory explicitly so no need to navigate to it
- _, err := stat(gitDir)
- if err != nil {
- return utils.WrapError(err)
- }
-
- return nil
- }
-
- // we haven't been given the git dir explicitly so we assume it's in the current working directory as `.git/` (or an ancestor directory)
-
- for {
- _, err := stat(".git")
-
- if err == nil {
- return nil
- }
-
- if !os.IsNotExist(err) {
- return utils.WrapError(err)
- }
-
- if err = chdir(".."); err != nil {
- return utils.WrapError(err)
- }
- }
-}
-
-// resolvePath takes a path containing a symlink and returns the true path
-func resolvePath(path string) (string, error) {
- l, err := os.Lstat(path)
- if err != nil {
- return "", err
- }
-
- if l.Mode()&os.ModeSymlink == 0 {
- return path, nil
- }
-
- return filepath.EvalSymlinks(path)
-}
-
-func setupRepository(openGitRepository func(string) (*gogit.Repository, error), sLocalize func(string) string) (*gogit.Repository, error) {
- unresolvedPath := env.GetGitDirEnv()
- if unresolvedPath == "" {
- var err error
- unresolvedPath, err = os.Getwd()
- if err != nil {
- return nil, err
- }
- }
-
- path, err := resolvePath(unresolvedPath)
- if err != nil {
- return nil, err
- }
-
- repository, err := openGitRepository(path)
-
- if err != nil {
- if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
- return nil, errors.New(sLocalize("GitconfigParseErr"))
- }
-
- return nil, err
- }
-
- return repository, err
-}
-
// GitCommand is our main git interface
type GitCommand struct {
Log *logrus.Entry
@@ -176,1060 +91,104 @@ func NewGitCommand(log *logrus.Entry, osCommand *oscommands.OSCommand, tr *i18n.
return gitCommand, nil
}
-func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filename string) ([]byte, error)) (string, error) {
- if env.GetGitDirEnv() != "" {
- return env.GetGitDirEnv(), nil
- }
-
- f, err := stat(".git")
- if err != nil {
- return "", err
- }
-
- if f.IsDir() {
- return ".git", nil
- }
-
- fileBytes, err := readFile(".git")
- if err != nil {
- return "", err
- }
- fileContent := string(fileBytes)
- if !strings.HasPrefix(fileContent, "gitdir: ") {
- return "", errors.New(".git is a file which suggests we are in a submodule but the file's contents do not contain a gitdir pointing to the actual .git directory")
- }
- return strings.TrimSpace(strings.TrimPrefix(fileContent, "gitdir: ")), nil
-}
-
-// GetStashEntryDiff stash diff
-func (c *GitCommand) ShowStashEntryCmdStr(index int) string {
- return fmt.Sprintf("git stash show -p --stat --color=%s stash@{%d}", c.colorArg(), index)
-}
-
-// GetStatusFiles git status files
-type GetStatusFileOptions struct {
- NoRenames bool
-}
-
-func (c *GitCommand) GetConfigValue(key string) string {
- output, _ := c.OSCommand.RunCommandWithOutput("git config --get %s", key)
- // looks like this returns an error if there is no matching value which we're okay with
- return strings.TrimSpace(output)
-}
-
-// StashDo modify stash
-func (c *GitCommand) StashDo(index int, method string) error {
- return c.OSCommand.RunCommand("git stash %s stash@{%d}", method, 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 %s", c.OSCommand.Quote(message))
-}
-
-func includesInt(list []int, a int) bool {
- for _, b := range list {
- if b == a {
- return true
- }
- }
- return false
+func verifyInGitRepo(runCmd func(string, ...interface{}) error) error {
+ return runCmd("git status")
}
-// ResetAndClean removes all unstaged changes and removes all untracked files
-func (c *GitCommand) ResetAndClean() error {
- submoduleConfigs, err := c.GetSubmoduleConfigs()
- if err != nil {
- return err
- }
-
- if len(submoduleConfigs) > 0 {
- for _, config := range submoduleConfigs {
- if err := c.SubmoduleStash(config); err != nil {
- return err
- }
- }
-
- if err := c.SubmoduleUpdateAll(); err != nil {
- return err
+func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
+ gitDir := env.GetGitDirEnv()
+ if gitDir != "" {
+ // we've been given the git directory explicitly so no need to navigate to it
+ _, err := stat(gitDir)
+ if err != nil {
+ return utils.WrapError(err)
}
- }
-
- if err := c.ResetHard("HEAD"); err != nil {
- return err
- }
-
- return c.RemoveUntrackedFiles()
-}
-
-func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
- return c.GetCommitDifferences("HEAD", "HEAD@{u}")
-}
-
-func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string, string) {
- return c.GetCommitDifferences(branchName, branchName+"@{u}")
-}
-
-// GetCommitDifferences checks how many pushables/pullables there are for the
-// current branch
-func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
- command := "git rev-list %s..%s --count"
- pushableCount, err := c.OSCommand.RunCommandWithOutput(command, to, from)
- if err != nil {
- return "?", "?"
- }
- pullableCount, err := c.OSCommand.RunCommandWithOutput(command, from, to)
- if err != nil {
- return "?", "?"
- }
- return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount)
-}
-
-// 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 %s", c.OSCommand.Quote(name))
-}
-// RebaseBranch interactive rebases onto a branch
-func (c *GitCommand) RebaseBranch(branchName string) error {
- cmd, err := c.PrepareInteractiveRebaseCommand(branchName, "", false)
- if err != nil {
- return err
+ return nil
}
- return c.OSCommand.RunPreparedCommand(cmd)
-}
-
-type FetchOptions struct {
- PromptUserForCredential func(string) string
- RemoteName string
- BranchName string
-}
-
-// Fetch fetch git repo
-func (c *GitCommand) Fetch(opts FetchOptions) error {
- command := "git fetch"
+ // we haven't been given the git dir explicitly so we assume it's in the current working directory as `.git/` (or an ancestor directory)
- if opts.RemoteName != "" {
- command = fmt.Sprintf("%s %s", command, opts.RemoteName)
- }
- if opts.BranchName != "" {
- command = fmt.Sprintf("%s %s", command, opts.BranchName)
- }
+ for {
+ _, err := stat(".git")
- return c.OSCommand.DetectUnamePass(command, func(question string) string {
- if opts.PromptUserForCredential != nil {
- return opts.PromptUserForCredential(question)
+ if err == nil {
+ return nil
}
- return "\n"</