1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-17 01:42:45 +02:00

support discarding submodule changes

This commit is contained in:
Jesse Duffield
2020-09-28 09:14:32 +10:00
parent ca437a6504
commit b8da166ab1
10 changed files with 174 additions and 76 deletions

View File

@ -19,7 +19,6 @@ type File struct {
DisplayString string DisplayString string
Type string // one of 'file', 'directory', and 'other' Type string // one of 'file', 'directory', and 'other'
ShortStatus string // e.g. 'AD', ' A', 'M ', '??' ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
IsSubmodule bool
} }
const RENAME_SEPARATOR = " -> " const RENAME_SEPARATOR = " -> "
@ -45,3 +44,17 @@ func (f *File) ID() string {
func (f *File) Description() string { func (f *File) Description() string {
return f.Name return f.Name
} }
func (f *File) IsSubmodule(configs []*SubmoduleConfig) bool {
return f.SubmoduleConfig(configs) != nil
}
func (f *File) SubmoduleConfig(configs []*SubmoduleConfig) *SubmoduleConfig {
for _, config := range configs {
if f.Name == config.Name {
return config
}
}
return nil
}

View File

@ -1,7 +1,6 @@
package commands package commands
import ( import (
"bufio"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -272,32 +271,6 @@ func (c *GitCommand) GetConfigValue(key string) string {
return strings.TrimSpace(output) return strings.TrimSpace(output)
} }
func (c *GitCommand) GetSubmoduleNames() ([]string, error) {
file, err := os.Open(".gitmodules")
if err != nil {
if err == os.ErrNotExist {
return nil, nil
}
return nil, err
}
submoduleNames := []string{}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
re := regexp.MustCompile(`\[submodule "(.*)"\]`)
matches := re.FindStringSubmatch(line)
if len(matches) > 0 {
submoduleNames = append(submoduleNames, matches[1])
}
}
return submoduleNames, nil
}
func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*File { func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*File {
// check if config wants us ignoring untracked files // check if config wants us ignoring untracked files
untrackedFilesSetting := c.GetConfigValue("status.showUntrackedFiles") untrackedFilesSetting := c.GetConfigValue("status.showUntrackedFiles")
@ -314,11 +287,6 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*File {
statusStrings := utils.SplitLines(statusOutput) statusStrings := utils.SplitLines(statusOutput)
files := []*File{} files := []*File{}
submoduleNames, err := c.GetSubmoduleNames()
if err != nil {
c.Log.Error(err)
}
for _, statusString := range statusStrings { for _, statusString := range statusStrings {
if strings.HasPrefix(statusString, "warning") { if strings.HasPrefix(statusString, "warning") {
c.Log.Warningf("warning when calling git status: %s", statusString) c.Log.Warningf("warning when calling git status: %s", statusString)
@ -332,7 +300,6 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*File {
hasNoStagedChanges := utils.IncludesString([]string{" ", "U", "?"}, stagedChange) hasNoStagedChanges := utils.IncludesString([]string{" ", "U", "?"}, stagedChange)
hasMergeConflicts := utils.IncludesString([]string{"DD", "AA", "UU", "AU", "UA", "UD", "DU"}, change) hasMergeConflicts := utils.IncludesString([]string{"DD", "AA", "UU", "AU", "UA", "UD", "DU"}, change)
hasInlineMergeConflicts := utils.IncludesString([]string{"UU", "AA"}, change) hasInlineMergeConflicts := utils.IncludesString([]string{"UU", "AA"}, change)
isSubmodule := utils.IncludesString(submoduleNames, filename)
file := &File{ file := &File{
Name: filename, Name: filename,
@ -345,7 +312,6 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*File {
HasInlineMergeConflicts: hasInlineMergeConflicts, HasInlineMergeConflicts: hasInlineMergeConflicts,
Type: c.OSCommand.FileType(filename), Type: c.OSCommand.FileType(filename),
ShortStatus: change, ShortStatus: change,
IsSubmodule: isSubmodule,
} }
files = append(files, file) files = append(files, file)
} }
@ -752,11 +718,7 @@ func (c *GitCommand) DiscardAllFileChanges(file *File) error {
// if the file isn't tracked, we assume you want to delete it // if the file isn't tracked, we assume you want to delete it
quotedFileName := c.OSCommand.Quote(file.Name) quotedFileName := c.OSCommand.Quote(file.Name)
if file.IsSubmodule { if file.HasStagedChanges || file.HasMergeConflicts {
if err := c.OSCommand.RunCommand(fmt.Sprintf("git submodule update --checkout --force --init %s", quotedFileName)); err != nil {
return err
}
} else if file.HasStagedChanges || file.HasMergeConflicts {
if err := c.OSCommand.RunCommand("git reset -- %s", quotedFileName); err != nil { if err := c.OSCommand.RunCommand("git reset -- %s", quotedFileName); err != nil {
return err return err
} }
@ -816,7 +778,7 @@ func (c *GitCommand) ShowCmdStr(sha string, filterPath string) string {
if filterPath != "" { if filterPath != "" {
filterPathArg = fmt.Sprintf(" -- %s", c.OSCommand.Quote(filterPath)) filterPathArg = fmt.Sprintf(" -- %s", c.OSCommand.Quote(filterPath))
} }
return fmt.Sprintf("git show --color=%s --no-renames --stat -p %s %s", c.colorArg(), sha, filterPathArg) return fmt.Sprintf("git show --submodule --color=%s --no-renames --stat -p %s %s", c.colorArg(), sha, filterPathArg)
} }
func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string { func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string {

View File

@ -0,0 +1,7 @@
package commands
type SubmoduleConfig struct {
Name string
Path string
Url string
}

View File

@ -0,0 +1,66 @@
package commands
import (
"bufio"
"os"
"regexp"
)
// .gitmodules looks like this:
// [submodule "mysubmodule"]
// path = blah/mysubmodule
// url = git@github.com:subbo.git
func (c *GitCommand) GetSubmoduleConfigs() ([]*SubmoduleConfig, error) {
file, err := os.Open(".gitmodules")
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
firstMatch := func(str string, regex string) (string, bool) {
re := regexp.MustCompile(regex)
matches := re.FindStringSubmatch(str)
if len(matches) > 0 {
return matches[1], true
} else {
return "", false
}
}
configs := []*SubmoduleConfig{}
for scanner.Scan() {
line := scanner.Text()
if name, ok := firstMatch(line, `\[submodule "(.*)"\]`); ok {
configs = append(configs, &SubmoduleConfig{Name: name})
continue
}
if len(configs) > 0 {
lastConfig := configs[len(configs)-1]
if path, ok := firstMatch(line, `\s*path\s*=\s*(.*)\s*`); ok {
lastConfig.Path = path
} else if url, ok := firstMatch(line, `\s*url\s*=\s*(.*)\s*`); ok {
lastConfig.Url = url
}
}
}
return configs, nil
}
func (c *GitCommand) SubmoduleStash(config *SubmoduleConfig) error {
return c.OSCommand.RunCommand("git -C %s stash --include-untracked", config.Path)
}
func (c *GitCommand) SubmoduleReset(config *SubmoduleConfig) error {
return c.OSCommand.RunCommand("git submodule update --force %s", config.Name)
}

View File

@ -2,42 +2,74 @@ package gui
import ( import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
) )
func (gui *Gui) submoduleFromFile(file *commands.File) *commands.SubmoduleConfig {
for _, config := range gui.State.SubmoduleConfigs {
if config.Name == file.Name {
return config
}
}
return nil
}
func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error {
file := gui.getSelectedFile() file := gui.getSelectedFile()
if file == nil { if file == nil {
return nil return nil
} }
if file.IsSubmodule { var menuItems []*menuItem
// git submodule foreach '[[ "$name" == "renderers/chartify" ]] && git stash --include-untracked'
// git submodule update --force renderers/chartify
}
menuItems := []*menuItem{ submoduleConfigs := gui.State.SubmoduleConfigs
{ if file.IsSubmodule(submoduleConfigs) {
displayString: gui.Tr.SLocalize("discardAllChanges"), submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
onPress: func() error {
if err := gui.GitCommand.DiscardAllFileChanges(file); err != nil { menuItems = []*menuItem{
return gui.surfaceError(err) {
} displayString: gui.Tr.SLocalize("submoduleStashAndReset"),
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []int{FILES}}) onPress: func() error {
if err := gui.GitCommand.UnStageFile(file.Name, file.Tracked); err != nil {
return gui.surfaceError(err)
}
if err := gui.GitCommand.SubmoduleStash(submoduleConfig); err != nil {
return gui.surfaceError(err)
}
if err := gui.GitCommand.SubmoduleReset(submoduleConfig); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []int{FILES}})
},
}, },
}, }
} } else {
menuItems = []*menuItem{
if file.HasStagedChanges && file.HasUnstagedChanges { {
menuItems = append(menuItems, &menuItem{ displayString: gui.Tr.SLocalize("discardAllChanges"),
displayString: gui.Tr.SLocalize("discardUnstagedChanges"), onPress: func() error {
onPress: func() error { if err := gui.GitCommand.DiscardAllFileChanges(file); err != nil {
if err := gui.GitCommand.DiscardUnstagedFileChanges(file); err != nil { return gui.surfaceError(err)
return gui.surfaceError(err) }
} return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []int{FILES}})
},
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []int{FILES}})
}, },
}) }
if file.HasStagedChanges && file.HasUnstagedChanges {
menuItems = append(menuItems, &menuItem{
displayString: gui.Tr.SLocalize("discardUnstagedChanges"),
onPress: func() error {
if err := gui.GitCommand.DiscardUnstagedFileChanges(file); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []int{FILES}})
},
})
}
} }
return gui.createMenu(file.Name, menuItems, createMenuOptions{showCancel: true}) return gui.createMenu(file.Name, menuItems, createMenuOptions{showCancel: true})

View File

@ -93,6 +93,9 @@ func (gui *Gui) refreshFiles() error {
// if the filesView hasn't been instantiated yet we just return // if the filesView hasn't been instantiated yet we just return
return nil return nil
} }
if err := gui.refreshStateSubmoduleConfigs(); err != nil {
return err
}
if err := gui.refreshStateFiles(); err != nil { if err := gui.refreshStateFiles(); err != nil {
return err return err
} }
@ -426,6 +429,17 @@ func (gui *Gui) refreshStateFiles() error {
return nil return nil
} }
func (gui *Gui) refreshStateSubmoduleConfigs() error {
configs, err := gui.GitCommand.GetSubmoduleConfigs()
if err != nil {
return err
}
gui.State.SubmoduleConfigs = configs
return nil
}
func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() { if gui.popupPanelFocused() {
return nil return nil

View File

@ -271,11 +271,12 @@ type Modes struct {
} }
type guiState struct { type guiState struct {
Files []*commands.File Files []*commands.File
Branches []*commands.Branch SubmoduleConfigs []*commands.SubmoduleConfig
Commits []*commands.Commit Branches []*commands.Branch
StashEntries []*commands.StashEntry Commits []*commands.Commit
CommitFiles []*commands.CommitFile StashEntries []*commands.StashEntry
CommitFiles []*commands.CommitFile
// FilteredReflogCommits are the ones that appear in the reflog panel. // FilteredReflogCommits are the ones that appear in the reflog panel.
// when in filtering mode we only include the ones that match the given path // when in filtering mode we only include the ones that match the given path
FilteredReflogCommits []*commands.Commit FilteredReflogCommits []*commands.Commit

View File

@ -270,7 +270,7 @@ func (gui *Gui) filesListContext() *ListContext {
ResetMainViewOriginOnFocus: false, ResetMainViewOriginOnFocus: false,
Kind: SIDE_CONTEXT, Kind: SIDE_CONTEXT,
GetDisplayStrings: func() [][]string { GetDisplayStrings: func() [][]string {
return presentation.GetFileListDisplayStrings(gui.State.Files, gui.State.Modes.Diffing.Ref) return presentation.GetFileListDisplayStrings(gui.State.Files, gui.State.Modes.Diffing.Ref, gui.State.SubmoduleConfigs)
}, },
SelectedItem: func() (ListItem, bool) { SelectedItem: func() (ListItem, bool) {
item := gui.getSelectedFile() item := gui.getSelectedFile()

View File

@ -7,19 +7,19 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
func GetFileListDisplayStrings(files []*commands.File, diffName string) [][]string { func GetFileListDisplayStrings(files []*commands.File, diffName string, submoduleConfigs []*commands.SubmoduleConfig) [][]string {
lines := make([][]string, len(files)) lines := make([][]string, len(files))
for i := range files { for i := range files {
diffed := files[i].Name == diffName diffed := files[i].Name == diffName
lines[i] = getFileDisplayStrings(files[i], diffed) lines[i] = getFileDisplayStrings(files[i], diffed, submoduleConfigs)
} }
return lines return lines
} }
// getFileDisplayStrings returns the display string of branch // getFileDisplayStrings returns the display string of branch
func getFileDisplayStrings(f *commands.File, diffed bool) []string { func getFileDisplayStrings(f *commands.File, diffed bool, submoduleConfigs []*commands.SubmoduleConfig) []string {
// potentially inefficient to be instantiating these color // potentially inefficient to be instantiating these color
// objects with each render // objects with each render
red := color.New(color.FgRed) red := color.New(color.FgRed)
@ -55,7 +55,7 @@ func getFileDisplayStrings(f *commands.File, diffed bool) []string {
output += secondCharCl.Sprint(secondChar) output += secondCharCl.Sprint(secondChar)
output += restColor.Sprintf(" %s", f.Name) output += restColor.Sprintf(" %s", f.Name)
if f.IsSubmodule { if f.IsSubmodule(submoduleConfigs) {
output += utils.ColoredString(" (submodule)", theme.DefaultTextColor) output += utils.ColoredString(" (submodule)", theme.DefaultTextColor)
} }

View File

@ -1191,6 +1191,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{ }, &i18n.Message{
ID: "runningCustomCommandStatus", ID: "runningCustomCommandStatus",
Other: "running custom command", Other: "running custom command",
}, &i18n.Message{
ID: "submoduleStashAndReset",
Other: "stash uncommitted submodule changes and reset",
}, },
) )
} }