diff options
Diffstat (limited to 'pkg/commands/files.go')
-rw-r--r-- | pkg/commands/files.go | 353 |
1 files changed, 31 insertions, 322 deletions
diff --git a/pkg/commands/files.go b/pkg/commands/files.go index acb503d7f..9e17e60e0 100644 --- a/pkg/commands/files.go +++ b/pkg/commands/files.go @@ -1,359 +1,68 @@ package commands import ( - "fmt" "io/ioutil" - "os" - "path/filepath" "strconv" - "time" "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/commands/loaders" - "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/gui/filetree" + "github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/utils" ) -// CatFile obtains the content of a file -func (c *GitCommand) CatFile(fileName string) (string, error) { - buf, err := ioutil.ReadFile(fileName) - if err != nil { - return "", nil - } - return string(buf), nil -} - -func (c *GitCommand) OpenMergeToolCmdObj() oscommands.ICmdObj { - return c.Cmd.New("git mergetool") -} - -func (c *GitCommand) OpenMergeTool() error { - return c.OpenMergeToolCmdObj().Run() -} - -// StageFile stages a file -func (c *GitCommand) StageFile(fileName string) error { - return c.Cmd.New("git add -- " + c.OSCommand.Quote(fileName)).Run() -} - -// StageAll stages all files -func (c *GitCommand) StageAll() error { - return c.Cmd.New("git add -A").Run() -} - -// UnstageAll unstages all files -func (c *GitCommand) UnstageAll() error { - return c.Cmd.New("git reset").Run() -} - -// UnStageFile unstages a file -// we accept an array of filenames for the cases where a file has been renamed i.e. -// we accept the current name and the previous name -func (c *GitCommand) UnStageFile(fileNames []string, reset bool) error { - command := "git rm --cached --force -- %s" - if reset { - command = "git reset HEAD -- %s" - } - - for _, name := range fileNames { - err := c.Cmd.New(fmt.Sprintf(command, c.OSCommand.Quote(name))).Run() - if 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. 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. - - filesWithoutRenames := loaders. - NewFileLoader(c.Common, c.Cmd, c.GitConfig). - GetStatusFiles(loaders.GetStatusFileOptions{NoRenames: true}) - - var beforeFile *models.File - var afterFile *models.File - for _, f := range filesWithoutRenames { - if f.Name == file.PreviousName { - beforeFile = f - } - - if f.Name == file.Name { - 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 - } - - quotedFileName := c.OSCommand.Quote(file.Name) - - if file.ShortStatus == "AA" { - if err := c.Cmd.New("git checkout --ours -- " + quotedFileName).Run(); err != nil { - return err - } - if err := c.Cmd.New("git add -- " + quotedFileName).Run(); err != nil { - return err - } - return nil - } - - if file.ShortStatus == "DU" { - return c.Cmd.New("git rm -- " + quotedFileName).Run() - } - - // if the file isn't tracked, we assume you want to delete it - if file.HasStagedChanges || file.HasMergeConflicts { - if err := c.Cmd.New("git reset -- " + quotedFileName).Run(); err != nil { - return err - } - } - - if file.ShortStatus == "DD" || file.ShortStatus == "AU" { - return nil - } - - if file.Added { - return c.OSCommand.RemoveFile(file.Name) - } - return c.DiscardUnstagedFileChanges(file) -} - -func (c *GitCommand) DiscardAllDirChanges(node *filetree.FileNode) error { - // this could be more efficient but we would need to handle all the edge cases - return node.ForEachFile(c.DiscardAllFileChanges) -} - -func (c *GitCommand) DiscardUnstagedDirChanges(node *filetree.FileNode) error { - if err := c.RemoveUntrackedDirFiles(node); err != nil { - return err - } - - quotedPath := c.OSCommand.Quote(node.GetPath()) - if err := c.Cmd.New("git checkout -- " + quotedPath).Run(); err != nil { - return err - } - - return nil -} - -func (c *GitCommand) RemoveUntrackedDirFiles(node *filetree.FileNode) error { - untrackedFilePaths := node.GetPathsMatching( - func(n *filetree.FileNode) bool { return n.File != nil && !n.File.GetIsTracked() }, - ) - - for _, path := range untrackedFilePaths { - err := os.Remove(path) - if err != nil { - return err - } - } - - return nil -} - -// DiscardUnstagedFileChanges directly -func (c *GitCommand) DiscardUnstagedFileChanges(file *models.File) error { - quotedFileName := c.OSCommand.Quote(file.Name) - return c.Cmd.New("git checkout -- " + quotedFileName).Run() -} - -// 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, ignoreWhitespace bool) string { - // for now we assume an error means the file was deleted - s, _ := c.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace).RunWithOutput() - return s -} - -func (c *GitCommand) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj { - cachedArg := "" - trackedArg := "--" - colorArg := c.colorArg() - quotedPath := c.OSCommand.Quote(node.GetPath()) - ignoreWhitespaceArg := "" - contextSize := c.UserConfig.Git.DiffContextSize - if cached { - cachedArg = "--cached" - } - if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached { - trackedArg = "--no-index -- /dev/null" - } - if plain { - colorArg = "never" - } - if ignoreWhitespace { - ignoreWhitespaceArg = "--ignore-all-space" - } - - cmdStr := fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --color=%s %s %s %s %s", contextSize, colorArg, ignoreWhitespaceArg, cachedArg, trackedArg, quotedPath) - - return c.Cmd.New(cmdStr).DontLog() -} - -func (c *GitCommand) ApplyPatch(patch string, flags ...string) error { - filepath := filepath.Join(oscommands.GetTempDir(), 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 - } +type FileCommands struct { + *common.Common - return c.Cmd.New(fmt.Sprintf("git apply%s %s", flagStr, c.OSCommand.Quote(filepath))).Run() + cmd oscommands.ICmdObjBuilder + config *ConfigCommands + os FileOSCommand } -// 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) { - return c.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput() +type FileOSCommand interface { + Getenv(string) string } -func (c *GitCommand) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj { - colorArg := c.colorArg() - contextSize := c.UserConfig.Git.DiffContextSize - if plain { - colorArg = "never" +func NewFileCommands( + common *common.Common, + cmd oscommands.ICmdObjBuilder, + config *ConfigCommands, + osCommand FileOSCommand, +) *FileCommands { + return &FileCommands{ + Common: common, + cmd: cmd, + config: config, + os: osCommand, } - - reverseFlag := "" - if reverse { - reverseFlag = " -R " - } - - return c.Cmd.New(fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --no-renames --color=%s %s %s %s -- %s", contextSize, colorArg, from, to, reverseFlag, c.OSCommand.Quote(fileName))).DontLog() -} - -// CheckoutFile checks out the file for the given commit -func (c *GitCommand) CheckoutFile(commitSha, fileName string) error { - return c.Cmd.New(fmt.Sprintf("git checkout %s -- %s", commitSha, c.OSCommand.Quote(fileName))).Run() -} - -// 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.Cmd.New("git cat-file -e HEAD^:" + c.OSCommand.Quote(fileName)).DontLog().Run(); 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 - err := c.AmendHead() - 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.Cmd.New("git checkout -- .").Run() -} - -// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked -func (c *GitCommand) RemoveTrackedFiles(name string) error { - return c.Cmd.New("git rm -r --cached -- " + c.OSCommand.Quote(name)).Run() -} - -// RemoveUntrackedFiles runs `git clean -fd` -func (c *GitCommand) RemoveUntrackedFiles() error { - return c.Cmd.New("git clean -fd").Run() -} - -// ResetAndClean removes all unstaged changes and removes all untracked files -func (c *GitCommand) ResetAndClean() error { - submoduleConfigs, err := c.GetSubmoduleConfigs() +// Cat obtains the content of a file +func (self *FileCommands) Cat(fileName string) (string, error) { + buf, err := ioutil.ReadFile(fileName) if err != nil { - return err - } - - if len(submoduleConfigs) > 0 { - if err := c.ResetSubmodules(submoduleConfigs); err != nil { - return err - } - } - - if err := c.ResetHard("HEAD"); err != nil { - return err + return "", nil } - - return c.RemoveUntrackedFiles() + return string(buf), nil } -func (c *GitCommand) EditFileCmdStr(filename string, lineNumber int) (string, error) { +func (c *FileCommands) GetEditCmdStr(filename string, lineNumber int) (string, error) { editor := c.UserConfig.OS.EditCommand if editor == "" { - editor = c.GitConfig.Get("core.editor") + editor = c.config.GetCoreEditor() } if editor == "" { - editor = c.OSCommand.Getenv("GIT_EDITOR") + editor = c.os.Getenv("GIT_EDITOR") } if editor == "" { - editor = c.OSCommand.Getenv("VISUAL") + editor = c.os.Getenv("VISUAL") } if editor == "" { - editor = c.OSCommand.Getenv("EDITOR") + editor = c.os.Getenv("EDITOR") } if editor == "" { - if err := c.OSCommand.Cmd.New("which vi").DontLog().Run(); err == nil { + if err := c.cmd.New("which vi").DontLog().Run(); err == nil { editor = "vi" } } @@ -363,7 +72,7 @@ func (c *GitCommand) EditFileCmdStr(filename string, lineNumber int) (string, er templateValues := map[string]string{ "editor": editor, - "filename": c.OSCommand.Quote(filename), + "filename": c.cmd.Quote(filename), "line": strconv.Itoa(lineNumber), } |