diff --git a/pkg/commands/submodules.go b/pkg/commands/submodules.go index fb72d090d..5c369ab8e 100644 --- a/pkg/commands/submodules.go +++ b/pkg/commands/submodules.go @@ -3,6 +3,7 @@ package commands import ( "bufio" "os" + "path/filepath" "regexp" "github.com/jesseduffield/lazygit/pkg/commands/models" @@ -59,21 +60,36 @@ func (c *GitCommand) GetSubmoduleConfigs() ([]*models.SubmoduleConfig, error) { return configs, nil } -func (c *GitCommand) SubmoduleStash(config *models.SubmoduleConfig) error { +func (c *GitCommand) SubmoduleStash(submodule *models.SubmoduleConfig) error { // if the path does not exist then it hasn't yet been initialized so we'll swallow the error // because the intention here is to have no dirty worktree state - if _, err := os.Stat(config.Path); os.IsNotExist(err) { - c.Log.Infof("submodule path %s does not exist, returning", config.Path) + if _, err := os.Stat(submodule.Path); os.IsNotExist(err) { + c.Log.Infof("submodule path %s does not exist, returning", submodule.Path) return nil } - return c.OSCommand.RunCommand("git -C %s stash --include-untracked", config.Path) + return c.OSCommand.RunCommand("git -C %s stash --include-untracked", submodule.Path) } -func (c *GitCommand) SubmoduleReset(config *models.SubmoduleConfig) error { - return c.OSCommand.RunCommand("git submodule update --force %s", config.Name) +func (c *GitCommand) SubmoduleReset(submodule *models.SubmoduleConfig) error { + return c.OSCommand.RunCommand("git submodule update --init --force %s", submodule.Name) } func (c *GitCommand) SubmoduleUpdateAll() error { + // not doing an --init here because the user probably doesn't want that return c.OSCommand.RunCommand("git submodule update --force") } + +func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error { + // based on https://gist.github.com/myusuf3/7f645819ded92bda6677 + + if err := c.OSCommand.RunCommand("git submodule deinit %s", submodule.Name); err != nil { + return err + } + + if err := c.OSCommand.RunCommand("git rm %s", submodule.Path); err != nil { + return err + } + + return os.RemoveAll(filepath.Join(c.DotGitDir, "modules", submodule.Name)) +} diff --git a/pkg/gui/discard_changes_menu_panel.go b/pkg/gui/discard_changes_menu_panel.go index 6805187c4..9d79a70fa 100644 --- a/pkg/gui/discard_changes_menu_panel.go +++ b/pkg/gui/discard_changes_menu_panel.go @@ -23,24 +23,15 @@ func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error { var menuItems []*menuItem - submoduleConfigs := gui.State.Submodules - if file.IsSubmodule(submoduleConfigs) { - submoduleConfig := file.SubmoduleConfig(submoduleConfigs) + submodules := gui.State.Submodules + if file.IsSubmodule(submodules) { + submodule := file.SubmoduleConfig(submodules) menuItems = []*menuItem{ { displayString: gui.Tr.SLocalize("submoduleStashAndReset"), 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}}) + return gui.resetSubmodule(submodule) }, }, } diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index b08f3df92..fcdbbb2c1 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -1586,6 +1586,21 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Handler: gui.wrappedHandler(gui.handleCopySelectedSideContextItemToClipboard), Description: gui.Tr.SLocalize("copySubmoduleNameToClipboard"), }, + { + ViewName: "files", + Contexts: []string{SUBMODULES_CONTEXT_KEY}, + Key: gui.getKey("universal.remove"), + + Handler: gui.wrappedHandler(gui.handleRemoveSubmodule), + Description: gui.Tr.SLocalize("removeSubmodule"), + }, + { + ViewName: "files", + Contexts: []string{SUBMODULES_CONTEXT_KEY}, + Key: gui.getKey("u"), + Handler: gui.wrappedHandler(gui.handleResetSubmodule), + Description: gui.Tr.SLocalize("submoduleStashAndReset"), + }, } for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} { diff --git a/pkg/gui/submodules_panel.go b/pkg/gui/submodules_panel.go index e956a30f4..7089a9015 100644 --- a/pkg/gui/submodules_panel.go +++ b/pkg/gui/submodules_panel.go @@ -25,6 +25,7 @@ func (gui *Gui) handleSubmoduleSelect() error { task = gui.createRenderStringTask("No submodules") } else { // TODO: we want to display the path, name, url, and a diff. We really need to be able to pipe commands together. We can always pipe commands together and just not do it asynchronously, but what if it's an expensive diff to obtain? I think that makes the most sense now though. + task = gui.createRenderStringTask( fmt.Sprintf( "Name: %s\nPath: %s\nUrl: %s\n", @@ -62,74 +63,113 @@ func (gui *Gui) enterSubmodule(submodule *models.SubmoduleConfig) error { return gui.dispatchSwitchToRepo(submodule.Path) } -// func (gui *Gui) handleAddRemote(g *gocui.Gui, v *gocui.View) error { -// return gui.prompt(gui.Tr.SLocalize("newRemoteName"), "", func(remoteName string) error { -// return gui.prompt(gui.Tr.SLocalize("newRemoteUrl"), "", func(remoteUrl string) error { -// if err := gui.GitCommand.AddRemote(remoteName, remoteUrl); err != nil { +func (gui *Gui) handleRemoveSubmodule() error { + submodule := gui.getSelectedSubmodule() + if submodule == nil { + return nil + } + + return gui.ask(askOpts{ + title: gui.Tr.SLocalize("RemoveSubmodule"), + prompt: gui.Tr.SLocalize("RemoveSubmodulePrompt") + " '" + submodule.Name + "'?", + handleConfirm: func() error { + if err := gui.GitCommand.SubmoduleDelete(submodule); err != nil { + return err + } + + return gui.refreshSidePanels(refreshOptions{scope: []int{SUBMODULES, FILES}}) + }, + }) +} + +func (gui *Gui) handleResetSubmodule() error { + return gui.WithWaitingStatus(gui.Tr.SLocalize("resettingSubmoduleStatus"), func() error { + submodule := gui.getSelectedSubmodule() + if submodule == nil { + return nil + } + + return gui.resetSubmodule(submodule) + }) +} + +func (gui *Gui) fileForSubmodule(submodule *models.SubmoduleConfig) *models.File { + for _, file := range gui.State.Files { + if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) { + return file + } + } + + return nil +} + +func (gui *Gui) resetSubmodule(submodule *models.SubmoduleConfig) error { + file := gui.fileForSubmodule(submodule) + if file != nil { + if err := gui.GitCommand.UnStageFile(file.Name, file.Tracked); err != nil { + return gui.surfaceError(err) + } + } + + if err := gui.GitCommand.SubmoduleStash(submodule); err != nil { + return gui.surfaceError(err) + } + if err := gui.GitCommand.SubmoduleReset(submodule); err != nil { + return gui.surfaceError(err) + } + + return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []int{FILES, SUBMODULES}}) +} + +// func (gui *Gui) handleAddsubmodule(g *gocui.Gui, v *gocui.View) error { +// return gui.prompt(gui.Tr.SLocalize("newsubmoduleName"), "", func(submoduleName string) error { +// return gui.prompt(gui.Tr.SLocalize("newsubmoduleUrl"), "", func(submoduleUrl string) error { +// if err := gui.GitCommand.Addsubmodule(submoduleName, submoduleUrl); err != nil { // return err // } -// return gui.refreshSidePanels(refreshOptions{scope: []int{REMOTES}}) +// return gui.refreshSidePanels(refreshOptions{scope: []int{submoduleS}}) // }) // }) // } -// func (gui *Gui) handleRemoveRemote(g *gocui.Gui, v *gocui.View) error { -// remote := gui.getSelectedSubmodule() -// if remote == nil { -// return nil -// } - -// return gui.ask(askOpts{ -// title: gui.Tr.SLocalize("removeRemote"), -// prompt: gui.Tr.SLocalize("removeRemotePrompt") + " '" + remote.Name + "'?", -// handleConfirm: func() error { -// if err := gui.GitCommand.RemoveRemote(remote.Name); err != nil { -// return err -// } - -// return gui.refreshSidePanels(refreshOptions{scope: []int{BRANCHES, REMOTES}}) -// }, -// }) -// } - -// func (gui *Gui) handleEditRemote(g *gocui.Gui, v *gocui.View) error { -// remote := gui.getSelectedSubmodule() -// if remote == nil { +// func (gui *Gui) handleEditsubmodule(g *gocui.Gui, v *gocui.View) error { +// submodule := gui.getSelectedSubmodule() +// if submodule == nil { // return nil // } // editNameMessage := gui.Tr.TemplateLocalize( -// "editRemoteName", +// "editsubmoduleName", // Teml{ -// "remoteName": remote.Name, +// "submoduleName": submodule.Name, // }, // ) -// return gui.prompt(editNameMessage, remote.Name, func(updatedRemoteName string) error { -// if updatedRemoteName != remote.Name { -// if err := gui.GitCommand.RenameRemote(remote.Name, updatedRemoteName); err != nil { +// return gui.prompt(editNameMessage, submodule.Name, func(updatedsubmoduleName string) error { +// if updatedsubmoduleName != submodule.Name { +// if err := gui.GitCommand.Renamesubmodule(submodule.Name, updatedsubmoduleName); err != nil { // return gui.surfaceError(err) // } // } // editUrlMessage := gui.Tr.TemplateLocalize( -// "editRemoteUrl", +// "editsubmoduleUrl", // Teml{ -// "remoteName": updatedRemoteName, +// "submoduleName": updatedsubmoduleName, // }, // ) -// urls := remote.Urls +// urls := submodule.Urls // url := "" // if len(urls) > 0 { // url = urls[0] // } -// return gui.prompt(editUrlMessage, url, func(updatedRemoteUrl string) error { -// if err := gui.GitCommand.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil { +// return gui.prompt(editUrlMessage, url, func(updatedsubmoduleUrl string) error { +// if err := gui.GitCommand.UpdatesubmoduleUrl(updatedsubmoduleName, updatedsubmoduleUrl); err != nil { // return gui.surfaceError(err) // } -// return gui.refreshSidePanels(refreshOptions{scope: []int{BRANCHES, REMOTES}}) +// return gui.refreshSidePanels(refreshOptions{scope: []int{BRANCHES, submoduleS}}) // }) // }) // } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 45a13647f..b2282d5ca 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -1203,6 +1203,18 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "copySubmoduleNameToClipboard", Other: "copy submodule name to clipboard", + }, &i18n.Message{ + ID: "RemoveSubmodule", + Other: "Remove submodule", + }, &i18n.Message{ + ID: "removeSubmodule", + Other: "remove submodule", + }, &i18n.Message{ + ID: "RemoveSubmodulePrompt", + Other: "Are you sure you want to remove submodule", + }, &i18n.Message{ + ID: "resettingSubmoduleStatus", + Other: "resetting submodule", }, ) }