mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-11 20:36:03 +02:00
Show all submodules recursively
This commit is contained in:
parent
db4f12929e
commit
3b723282cb
13 changed files with 260 additions and 21 deletions
|
@ -60,6 +60,14 @@ func (self *GitCommandBuilder) Dir(path string) *GitCommandBuilder {
|
|||
return self
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) DirIf(condition bool, path string) *GitCommandBuilder {
|
||||
if condition {
|
||||
return self.Dir(path)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// Note, you may prefer to use the Dir method instead of this one
|
||||
func (self *GitCommandBuilder) Worktree(path string) *GitCommandBuilder {
|
||||
// worktree arg comes before the command
|
||||
|
|
|
@ -26,8 +26,12 @@ func NewSubmoduleCommands(gitCommon *GitCommon) *SubmoduleCommands {
|
|||
}
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) GetConfigs() ([]*models.SubmoduleConfig, error) {
|
||||
file, err := os.Open(".gitmodules")
|
||||
func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig) ([]*models.SubmoduleConfig, error) {
|
||||
gitModulesPath := ".gitmodules"
|
||||
if parentModule != nil {
|
||||
gitModulesPath = filepath.Join(parentModule.FullPath(), gitModulesPath)
|
||||
}
|
||||
file, err := os.Open(gitModulesPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
|
@ -51,21 +55,27 @@ func (self *SubmoduleCommands) GetConfigs() ([]*models.SubmoduleConfig, error) {
|
|||
}
|
||||
|
||||
configs := []*models.SubmoduleConfig{}
|
||||
lastConfigIdx := -1
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
if name, ok := firstMatch(line, `\[submodule "(.*)"\]`); ok {
|
||||
configs = append(configs, &models.SubmoduleConfig{Name: name})
|
||||
configs = append(configs, &models.SubmoduleConfig{
|
||||
Name: name, ParentModule: parentModule,
|
||||
})
|
||||
lastConfigIdx = len(configs) - 1
|
||||
continue
|
||||
}
|
||||
|
||||
if len(configs) > 0 {
|
||||
lastConfig := configs[len(configs)-1]
|
||||
|
||||
if lastConfigIdx != -1 {
|
||||
if path, ok := firstMatch(line, `\s*path\s*=\s*(.*)\s*`); ok {
|
||||
lastConfig.Path = path
|
||||
configs[lastConfigIdx].Path = path
|
||||
nestedConfigs, err := self.GetConfigs(configs[lastConfigIdx])
|
||||
if err == nil {
|
||||
configs = append(configs, nestedConfigs...)
|
||||
}
|
||||
} else if url, ok := firstMatch(line, `\s*url\s*=\s*(.*)\s*`); ok {
|
||||
lastConfig.Url = url
|
||||
configs[lastConfigIdx].Url = url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,12 +87,12 @@ func (self *SubmoduleCommands) Stash(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(submodule.Path); os.IsNotExist(err) {
|
||||
self.Log.Infof("submodule path %s does not exist, returning", submodule.Path)
|
||||
self.Log.Infof("submodule path %s does not exist, returning", submodule.FullPath())
|
||||
return nil
|
||||
}
|
||||
|
||||
cmdArgs := NewGitCmd("stash").
|
||||
Dir(submodule.Path).
|
||||
Dir(submodule.FullPath()).
|
||||
Arg("--include-untracked").
|
||||
ToArgv()
|
||||
|
||||
|
@ -90,8 +100,13 @@ func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
|
|||
}
|
||||
|
||||
func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error {
|
||||
parentDir := ""
|
||||
if submodule.ParentModule != nil {
|
||||
parentDir = submodule.ParentModule.FullPath()
|
||||
}
|
||||
cmdArgs := NewGitCmd("submodule").
|
||||
Arg("update", "--init", "--force", "--", submodule.Path).
|
||||
DirIf(parentDir != "", parentDir).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
|
@ -107,6 +122,20 @@ func (self *SubmoduleCommands) UpdateAll() error {
|
|||
func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
|
||||
// based on https://gist.github.com/myusuf3/7f645819ded92bda6677
|
||||
|
||||
if submodule.ParentModule != nil {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Chdir(submodule.ParentModule.FullPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() { _ = os.Chdir(wd) }()
|
||||
}
|
||||
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("submodule").
|
||||
Arg("deinit", "--force", "--", submodule.Path).ToArgv(),
|
||||
|
@ -141,7 +170,7 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
|
|||
|
||||
// We may in fact want to use the repo's git dir path but git docs say not to
|
||||
// mix submodules and worktrees anyway.
|
||||
return os.RemoveAll(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "modules", submodule.Name))
|
||||
return os.RemoveAll(submodule.GitDirPath(self.repoPaths.repoGitDirPath))
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) Add(name string, path string, url string) error {
|
||||
|
@ -159,6 +188,20 @@ func (self *SubmoduleCommands) Add(name string, path string, url string) error {
|
|||
}
|
||||
|
||||
func (self *SubmoduleCommands) UpdateUrl(submodule *models.SubmoduleConfig, newUrl string) error {
|
||||
if submodule.ParentModule != nil {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Chdir(submodule.ParentModule.FullPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() { _ = os.Chdir(wd) }()
|
||||
}
|
||||
|
||||
setUrlCmdStr := NewGitCmd("config").
|
||||
Arg(
|
||||
"--file", ".gitmodules", "submodule."+submodule.Name+".url", newUrl,
|
||||
|
|
|
@ -343,7 +343,7 @@ func (self *WorkingTreeCommands) RemoveUntrackedFiles() error {
|
|||
|
||||
// ResetAndClean removes all unstaged changes and removes all untracked files
|
||||
func (self *WorkingTreeCommands) ResetAndClean() error {
|
||||
submoduleConfigs, err := self.submodule.GetConfigs()
|
||||
submoduleConfigs, err := self.submodule.GetConfigs(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,13 +1,33 @@
|
|||
package models
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
type SubmoduleConfig struct {
|
||||
Name string
|
||||
Path string
|
||||
Url string
|
||||
|
||||
ParentModule *SubmoduleConfig // nil if top-level
|
||||
}
|
||||
|
||||
func (r *SubmoduleConfig) FullName() string {
|
||||
if r.ParentModule != nil {
|
||||
return r.ParentModule.FullName() + "/" + r.Name
|
||||
}
|
||||
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *SubmoduleConfig) FullPath() string {
|
||||
if r.ParentModule != nil {
|
||||
return r.ParentModule.FullPath() + "/" + r.Path
|
||||
}
|
||||
|
||||
return r.Path
|
||||
}
|
||||
|
||||
func (r *SubmoduleConfig) RefName() string {
|
||||
return r.Name
|
||||
return r.FullName()
|
||||
}
|
||||
|
||||
func (r *SubmoduleConfig) ID() string {
|
||||
|
@ -17,3 +37,12 @@ func (r *SubmoduleConfig) ID() string {
|
|||
func (r *SubmoduleConfig) Description() string {
|
||||
return r.RefName()
|
||||
}
|
||||
|
||||
func (r *SubmoduleConfig) GitDirPath(repoGitDirPath string) string {
|
||||
parentPath := repoGitDirPath
|
||||
if r.ParentModule != nil {
|
||||
parentPath = r.ParentModule.GitDirPath(repoGitDirPath)
|
||||
}
|
||||
|
||||
return filepath.Join(parentPath, "modules", r.Name)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
|
|||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.SubmoduleConfig { return c.Model().Submodules },
|
||||
func(submodule *models.SubmoduleConfig) []string {
|
||||
return []string{submodule.Name}
|
||||
return []string{submodule.FullName()}
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
|
|
@ -415,7 +415,7 @@ func (self *RefreshHelper) refreshTags() error {
|
|||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
|
||||
configs, err := self.c.Git().Submodule.GetConfigs()
|
||||
configs, err := self.c.Git().Submodule.GetConfigs(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ func (self *ReposHelper) EnterSubmodule(submodule *models.SubmoduleConfig) error
|
|||
}
|
||||
self.c.State().GetRepoPathStack().Push(wd)
|
||||
|
||||
return self.DispatchSwitchToRepo(submodule.Path, context.NO_CONTEXT)
|
||||
return self.DispatchSwitchToRepo(submodule.FullPath(), context.NO_CONTEXT)
|
||||
}
|
||||
|
||||
func (self *ReposHelper) getCurrentBranch(path string) string {
|
||||
|
|
|
@ -116,8 +116,8 @@ func (self *SubmodulesController) GetOnRenderToMain() func() error {
|
|||
} else {
|
||||
prefix := fmt.Sprintf(
|
||||
"Name: %s\nPath: %s\nUrl: %s\n\n",
|
||||
style.FgGreen.Sprint(submodule.Name),
|
||||
style.FgYellow.Sprint(submodule.Path),
|
||||
style.FgGreen.Sprint(submodule.FullName()),
|
||||
style.FgYellow.Sprint(submodule.FullPath()),
|
||||
style.FgCyan.Sprint(submodule.Url),
|
||||
)
|
||||
|
||||
|
@ -178,7 +178,7 @@ func (self *SubmodulesController) add() error {
|
|||
|
||||
func (self *SubmodulesController) editURL(submodule *models.SubmoduleConfig) error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: fmt.Sprintf(self.c.Tr.UpdateSubmoduleUrl, submodule.Name),
|
||||
Title: fmt.Sprintf(self.c.Tr.UpdateSubmoduleUrl, submodule.FullName()),
|
||||
InitialContent: submodule.Url,
|
||||
HandleConfirm: func(newUrl string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleUrlStatus, func(gocui.Task) error {
|
||||
|
@ -272,7 +272,7 @@ func (self *SubmodulesController) update(submodule *models.SubmoduleConfig) erro
|
|||
func (self *SubmodulesController) remove(submodule *models.SubmoduleConfig) error {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.RemoveSubmodule,
|
||||
Prompt: fmt.Sprintf(self.c.Tr.RemoveSubmodulePrompt, submodule.Name),
|
||||
Prompt: fmt.Sprintf(self.c.Tr.RemoveSubmodulePrompt, submodule.FullName()),
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RemoveSubmodule)
|
||||
if err := self.c.Git().Submodule.Delete(submodule); err != nil {
|
||||
|
|
|
@ -13,5 +13,15 @@ func GetSubmoduleListDisplayStrings(submodules []*models.SubmoduleConfig) [][]st
|
|||
}
|
||||
|
||||
func getSubmoduleDisplayStrings(s *models.SubmoduleConfig) []string {
|
||||
return []string{theme.DefaultTextColor.Sprint(s.Name)}
|
||||
name := s.Name
|
||||
if s.ParentModule != nil {
|
||||
indentation := ""
|
||||
for p := s.ParentModule; p != nil; p = p.ParentModule {
|
||||
indentation += " "
|
||||
}
|
||||
|
||||
name = indentation + "- " + s.Name
|
||||
}
|
||||
|
||||
return []string{theme.DefaultTextColor.Sprint(name)}
|
||||
}
|
||||
|
|
52
pkg/integration/tests/submodule/enter_nested.go
Normal file
52
pkg/integration/tests/submodule/enter_nested.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package submodule
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var EnterNested = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Enter a nested submodule",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(cfg *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
setupNestedSubmodules(shell)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Submodules().Focus().
|
||||
Lines(
|
||||
Equals("outerSubName").IsSelected(),
|
||||
Equals(" - innerSubName"),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Views().Main().ContainsLines(
|
||||
Contains("Name: outerSubName"),
|
||||
Contains("Path: modules/outerSubPath"),
|
||||
Contains("Url: ../outerSubmodule"),
|
||||
)
|
||||
}).
|
||||
SelectNextItem().
|
||||
Tap(func() {
|
||||
t.Views().Main().ContainsLines(
|
||||
Contains("Name: outerSubName/innerSubName"),
|
||||
Contains("Path: modules/outerSubPath/modules/innerSubPath"),
|
||||
Contains("Url: ../innerSubmodule"),
|
||||
)
|
||||
}).
|
||||
// enter the nested submodule
|
||||
PressEnter()
|
||||
|
||||
if t.Git().Version().IsAtLeast(2, 22, 0) {
|
||||
t.Views().Status().Content(Contains("innerSubPath(innerSubName)"))
|
||||
} else {
|
||||
t.Views().Status().Content(Contains("innerSubPath"))
|
||||
}
|
||||
t.Views().Commits().ContainsLines(
|
||||
Contains("initial inner commit"),
|
||||
)
|
||||
|
||||
t.Views().Files().PressEscape()
|
||||
t.Views().Status().Content(Contains("repo"))
|
||||
},
|
||||
})
|
56
pkg/integration/tests/submodule/remove_nested.go
Normal file
56
pkg/integration/tests/submodule/remove_nested.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package submodule
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var RemoveNested = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Remove a nested submodule",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
setupNestedSubmodules(shell)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
gitDirSubmodulePath, _ := filepath.Abs(".git/modules/outerSubName/modules/innerSubName")
|
||||
t.FileSystem().PathPresent(gitDirSubmodulePath)
|
||||
|
||||
t.Views().Submodules().Focus().
|
||||
Lines(
|
||||
Equals("outerSubName").IsSelected(),
|
||||
Equals(" - innerSubName"),
|
||||
).
|
||||
SelectNextItem().
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Remove submodule")).
|
||||
Content(Equals("Are you sure you want to remove submodule 'outerSubName/innerSubName' and its corresponding directory? This is irreversible.")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Equals("outerSubName").IsSelected(),
|
||||
).
|
||||
Press(keys.Universal.GoInto)
|
||||
|
||||
t.Views().Files().IsFocused().
|
||||
Lines(
|
||||
Contains("modules").IsSelected(),
|
||||
MatchesRegexp(`D.*innerSubPath`),
|
||||
MatchesRegexp(`M.*\.gitmodules`),
|
||||
).
|
||||
NavigateToLine(Contains(".gitmodules"))
|
||||
|
||||
t.Views().Main().Content(
|
||||
Contains("-[submodule \"innerSubName\"]").
|
||||
Contains("- path = modules/innerSubPath").
|
||||
Contains("- url = ../innerSubmodule"),
|
||||
)
|
||||
|
||||
t.FileSystem().PathNotPresent(gitDirSubmodulePath)
|
||||
},
|
||||
})
|
39
pkg/integration/tests/submodule/shared.go
Normal file
39
pkg/integration/tests/submodule/shared.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package submodule
|
||||
|
||||
import (
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
func setupNestedSubmodules(shell *Shell) {
|
||||
// we're going to have a directory structure like this:
|
||||
// project
|
||||
// - repo/modules/outerSubName/modules/innerSubName/
|
||||
//
|
||||
shell.CreateFileAndAdd("rootFile", "rootStuff")
|
||||
shell.Commit("initial repo commit")
|
||||
|
||||
shell.Chdir("..")
|
||||
shell.CreateDir("innerSubmodule")
|
||||
shell.Chdir("innerSubmodule")
|
||||
shell.Init()
|
||||
shell.CreateFileAndAdd("inner", "inner")
|
||||
shell.Commit("initial inner commit")
|
||||
|
||||
shell.Chdir("..")
|
||||
shell.CreateDir("outerSubmodule")
|
||||
shell.Chdir("outerSubmodule")
|
||||
shell.Init()
|
||||
shell.CreateFileAndAdd("outer", "outer")
|
||||
shell.Commit("initial outer commit")
|
||||
shell.CreateDir("modules")
|
||||
// the git config (-c) parameter below is required
|
||||
// to let git create a file-protocol/path submodule
|
||||
shell.RunCommand([]string{"git", "-c", "protocol.file.allow=always", "submodule", "add", "--name", "innerSubName", "../innerSubmodule", "modules/innerSubPath"})
|
||||
shell.Commit("add dependency as innerSubmodule")
|
||||
|
||||
shell.Chdir("../repo")
|
||||
shell.CreateDir("modules")
|
||||
shell.RunCommand([]string{"git", "-c", "protocol.file.allow=always", "submodule", "add", "--name", "outerSubName", "../outerSubmodule", "modules/outerSubPath"})
|
||||
shell.Commit("add dependency as outerSubmodule")
|
||||
shell.RunCommand([]string{"git", "-c", "protocol.file.allow=always", "submodule", "update", "--init", "--recursive"})
|
||||
}
|
|
@ -245,7 +245,9 @@ var tests = []*components.IntegrationTest{
|
|||
stash.StashUnstaged,
|
||||
submodule.Add,
|
||||
submodule.Enter,
|
||||
submodule.EnterNested,
|
||||
submodule.Remove,
|
||||
submodule.RemoveNested,
|
||||
submodule.Reset,
|
||||
sync.FetchPrune,
|
||||
sync.FetchWhenSortedByDate,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue