diff options
-rw-r--r-- | plumbing/format/config/common.go | 54 | ||||
-rw-r--r-- | plumbing/format/config/common_test.go | 8 | ||||
-rw-r--r-- | plumbing/format/config/option.go | 10 | ||||
-rw-r--r-- | plumbing/format/config/option_test.go | 15 | ||||
-rw-r--r-- | plumbing/format/config/section.go | 85 | ||||
-rw-r--r-- | plumbing/format/config/section_test.go | 253 | ||||
-rw-r--r-- | remote.go | 15 | ||||
-rw-r--r-- | remote_test.go | 2 | ||||
-rw-r--r-- | submodule.go | 24 | ||||
-rw-r--r-- | worktree.go | 6 | ||||
-rw-r--r-- | worktree_test.go | 59 |
11 files changed, 471 insertions, 60 deletions
diff --git a/plumbing/format/config/common.go b/plumbing/format/config/common.go index 8f98ad1..6d689ea 100644 --- a/plumbing/format/config/common.go +++ b/plumbing/format/config/common.go @@ -44,28 +44,14 @@ func (c *Config) Section(name string) *Section { return s } -// AddOption adds an option to a given section and subsection. Use the -// NoSubsection constant for the subsection argument if no subsection is wanted. -func (c *Config) AddOption(section string, subsection string, key string, value string) *Config { - if subsection == "" { - c.Section(section).AddOption(key, value) - } else { - c.Section(section).Subsection(subsection).AddOption(key, value) - } - - return c -} - -// SetOption sets an option to a given section and subsection. Use the -// NoSubsection constant for the subsection argument if no subsection is wanted. -func (c *Config) SetOption(section string, subsection string, key string, value string) *Config { - if subsection == "" { - c.Section(section).SetOption(key, value) - } else { - c.Section(section).Subsection(subsection).SetOption(key, value) +// HasSection checks if the Config has a section with the specified name. +func (c *Config) HasSection(name string) bool { + for _, s := range c.Sections { + if s.IsName(name) { + return true + } } - - return c + return false } // RemoveSection removes a section from a config file. @@ -81,7 +67,7 @@ func (c *Config) RemoveSection(name string) *Config { return c } -// RemoveSubsection remove s a subsection from a config file. +// RemoveSubsection remove a subsection from a config file. func (c *Config) RemoveSubsection(section string, subsection string) *Config { for _, s := range c.Sections { if s.IsName(section) { @@ -97,3 +83,27 @@ func (c *Config) RemoveSubsection(section string, subsection string) *Config { return c } + +// AddOption adds an option to a given section and subsection. Use the +// NoSubsection constant for the subsection argument if no subsection is wanted. +func (c *Config) AddOption(section string, subsection string, key string, value string) *Config { + if subsection == "" { + c.Section(section).AddOption(key, value) + } else { + c.Section(section).Subsection(subsection).AddOption(key, value) + } + + return c +} + +// SetOption sets an option to a given section and subsection. Use the +// NoSubsection constant for the subsection argument if no subsection is wanted. +func (c *Config) SetOption(section string, subsection string, key string, value string) *Config { + if subsection == "" { + c.Section(section).SetOption(key, value) + } else { + c.Section(section).Subsection(subsection).SetOption(key, value) + } + + return c +} diff --git a/plumbing/format/config/common_test.go b/plumbing/format/config/common_test.go index 365b53f..dca38df 100644 --- a/plumbing/format/config/common_test.go +++ b/plumbing/format/config/common_test.go @@ -64,6 +64,14 @@ func (s *CommonSuite) TestConfig_AddOption(c *C) { c.Assert(obtained, DeepEquals, expected) } +func (s *CommonSuite) TestConfig_HasSection(c *C) { + sect := New(). + AddOption("section1", "sub1", "key1", "value1"). + AddOption("section1", "sub2", "key1", "value1") + c.Assert(sect.HasSection("section1"), Equals, true) + c.Assert(sect.HasSection("section2"), Equals, false) +} + func (s *CommonSuite) TestConfig_RemoveSection(c *C) { sect := New(). AddOption("section1", NoSubsection, "key1", "value1"). diff --git a/plumbing/format/config/option.go b/plumbing/format/config/option.go index 218f992..cad3948 100644 --- a/plumbing/format/config/option.go +++ b/plumbing/format/config/option.go @@ -54,6 +54,16 @@ func (opts Options) Get(key string) string { return "" } +// Has checks if an Option exist with the given key. +func (opts Options) Has(key string) bool { + for _, o := range opts { + if o.IsKey(key) { + return true + } + } + return false +} + // GetAll returns all possible values for the same key. func (opts Options) GetAll(key string) []string { result := []string{} diff --git a/plumbing/format/config/option_test.go b/plumbing/format/config/option_test.go index 8588de1..49b4855 100644 --- a/plumbing/format/config/option_test.go +++ b/plumbing/format/config/option_test.go @@ -8,6 +8,21 @@ type OptionSuite struct{} var _ = Suite(&OptionSuite{}) +func (s *OptionSuite) TestOptions_Has(c *C) { + o := Options{ + &Option{"k", "v"}, + &Option{"ok", "v1"}, + &Option{"K", "v2"}, + } + c.Assert(o.Has("k"), Equals, true) + c.Assert(o.Has("K"), Equals, true) + c.Assert(o.Has("ok"), Equals, true) + c.Assert(o.Has("unexistant"), Equals, false) + + o = Options{} + c.Assert(o.Has("k"), Equals, false) +} + func (s *OptionSuite) TestOptions_GetAll(c *C) { o := Options{ &Option{"k", "v"}, diff --git a/plumbing/format/config/section.go b/plumbing/format/config/section.go index f743da6..07f72f3 100644 --- a/plumbing/format/config/section.go +++ b/plumbing/format/config/section.go @@ -64,31 +64,6 @@ func (s *Section) IsName(name string) bool { return strings.EqualFold(s.Name, name) } -// Option return the value for the specified key. Empty string is returned if -// key does not exists. -func (s *Section) Option(key string) string { - return s.Options.Get(key) -} - -// AddOption adds a new Option to the Section. The updated Section is returned. -func (s *Section) AddOption(key string, value string) *Section { - s.Options = s.Options.withAddedOption(key, value) - return s -} - -// SetOption adds a new Option to the Section. If the option already exists, is replaced. -// The updated Section is returned. -func (s *Section) SetOption(key string, value string) *Section { - s.Options = s.Options.withSettedOption(key, value) - return s -} - -// Remove an option with the specified key. The updated Section is returned. -func (s *Section) RemoveOption(key string) *Section { - s.Options = s.Options.withoutOption(key) - return s -} - // Subsection returns a Subsection from the specified Section. If the // Subsection does not exists, new one is created and added to Section. func (s *Section) Subsection(name string) *Subsection { @@ -115,6 +90,55 @@ func (s *Section) HasSubsection(name string) bool { return false } +// RemoveSubsection removes a subsection from a Section. +func (s *Section) RemoveSubsection(name string) *Section { + result := Subsections{} + for _, s := range s.Subsections { + if !s.IsName(name) { + result = append(result, s) + } + } + + s.Subsections = result + return s +} + +// Option return the value for the specified key. Empty string is returned if +// key does not exists. +func (s *Section) Option(key string) string { + return s.Options.Get(key) +} + +// OptionAll returns all possible values for an option with the specified key. +// If the option does not exists, an empty slice will be returned. +func (s *Section) OptionAll(key string) []string { + return s.Options.GetAll(key) +} + +// HasOption checks if the Section has an Option with the given key. +func (s *Section) HasOption(key string) bool { + return s.Options.Has(key) +} + +// AddOption adds a new Option to the Section. The updated Section is returned. +func (s *Section) AddOption(key string, value string) *Section { + s.Options = s.Options.withAddedOption(key, value) + return s +} + +// SetOption adds a new Option to the Section. If the option already exists, is replaced. +// The updated Section is returned. +func (s *Section) SetOption(key string, value string) *Section { + s.Options = s.Options.withSettedOption(key, value) + return s +} + +// Remove an option with the specified key. The updated Section is returned. +func (s *Section) RemoveOption(key string) *Section { + s.Options = s.Options.withoutOption(key) + return s +} + // IsName checks if the name of the subsection is exactly the specified name. func (s *Subsection) IsName(name string) bool { return s.Name == name @@ -126,6 +150,17 @@ func (s *Subsection) Option(key string) string { return s.Options.Get(key) } +// OptionAll returns all possible values for an option with the specified key. +// If the option does not exists, an empty slice will be returned. +func (s *Subsection) OptionAll(key string) []string { + return s.Options.GetAll(key) +} + +// HasOption checks if the Subsection has an Option with the given key. +func (s *Subsection) HasOption(key string) bool { + return s.Options.Has(key) +} + // AddOption adds a new Option to the Subsection. The updated Subsection is returned. func (s *Subsection) AddOption(key string, value string) *Subsection { s.Options = s.Options.withAddedOption(key, value) diff --git a/plumbing/format/config/section_test.go b/plumbing/format/config/section_test.go index 0290386..c7cc4a9 100644 --- a/plumbing/format/config/section_test.go +++ b/plumbing/format/config/section_test.go @@ -8,6 +8,115 @@ type SectionSuite struct{} var _ = Suite(&SectionSuite{}) +func (s *SectionSuite) TestSections_GoString(c *C) { + sects := Sections{ + &Section{ + Options: []*Option{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + }, + }, + &Section{ + Options: []*Option{ + {Key: "key1", Value: "value3"}, + {Key: "key2", Value: "value4"}, + }, + }, + } + + expected := "&config.Section{Name:\"\", Options:&config.Option{Key:\"key1\", Value:\"value1\"}, &config.Option{Key:\"key2\", Value:\"value2\"}, Subsections:}, &config.Section{Name:\"\", Options:&config.Option{Key:\"key1\", Value:\"value3\"}, &config.Option{Key:\"key2\", Value:\"value4\"}, Subsections:}" + c.Assert(sects.GoString(), Equals, expected) +} + +func (s *SectionSuite) TestSubsections_GoString(c *C) { + sects := Subsections{ + &Subsection{ + Options: []*Option{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + {Key: "key1", Value: "value3"}, + }, + }, + &Subsection{ + Options: []*Option{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + {Key: "key1", Value: "value3"}, + }, + }, + } + + expected := "&config.Subsection{Name:\"\", Options:&config.Option{Key:\"key1\", Value:\"value1\"}, &config.Option{Key:\"key2\", Value:\"value2\"}, &config.Option{Key:\"key1\", Value:\"value3\"}}, &config.Subsection{Name:\"\", Options:&config.Option{Key:\"key1\", Value:\"value1\"}, &config.Option{Key:\"key2\", Value:\"value2\"}, &config.Option{Key:\"key1\", Value:\"value3\"}}" + c.Assert(sects.GoString(), Equals, expected) +} + +func (s *SectionSuite) TestSection_IsName(c *C) { + sect := &Section{ + Name: "name1", + } + + c.Assert(sect.IsName("name1"), Equals, true) + c.Assert(sect.IsName("Name1"), Equals, true) +} + +func (s *SectionSuite) TestSection_Subsection(c *C) { + subSect1 := &Subsection{ + Name: "name1", + Options: Options{ + &Option{Key: "key1", Value: "value1"}, + }, + } + sect := &Section{ + Subsections: Subsections{ + subSect1, + }, + } + + c.Assert(sect.Subsection("name1"), DeepEquals, subSect1) + + subSect2 := &Subsection{ + Name: "name2", + } + c.Assert(sect.Subsection("name2"), DeepEquals, subSect2) +} + +func (s *SectionSuite) TestSection_HasSubsection(c *C) { + sect := &Section{ + Subsections: Subsections{ + &Subsection{ + Name: "name1", + }, + }, + } + + c.Assert(sect.HasSubsection("name1"), Equals, true) + c.Assert(sect.HasSubsection("name2"), Equals, false) +} + +func (s *SectionSuite) TestSection_RemoveSubsection(c *C) { + sect := &Section{ + Subsections: Subsections{ + &Subsection{ + Name: "name1", + }, + &Subsection{ + Name: "name2", + }, + }, + } + + expected := &Section{ + Subsections: Subsections{ + &Subsection{ + Name: "name2", + }, + }, + } + c.Assert(sect.RemoveSubsection("name1"), DeepEquals, expected) + c.Assert(sect.HasSubsection("name1"), Equals, false) + c.Assert(sect.HasSubsection("name2"), Equals, true) +} + func (s *SectionSuite) TestSection_Option(c *C) { sect := &Section{ Options: []*Option{ @@ -21,17 +130,71 @@ func (s *SectionSuite) TestSection_Option(c *C) { c.Assert(sect.Option("key1"), Equals, "value3") } -func (s *SectionSuite) TestSubsection_Option(c *C) { - sect := &Subsection{ +func (s *SectionSuite) TestSection_OptionAll(c *C) { + sect := &Section{ Options: []*Option{ {Key: "key1", Value: "value1"}, {Key: "key2", Value: "value2"}, {Key: "key1", Value: "value3"}, }, } - c.Assert(sect.Option("otherkey"), Equals, "") - c.Assert(sect.Option("key2"), Equals, "value2") - c.Assert(sect.Option("key1"), Equals, "value3") + c.Assert(sect.OptionAll("otherkey"), DeepEquals, []string{}) + c.Assert(sect.OptionAll("key2"), DeepEquals, []string{"value2"}) + c.Assert(sect.OptionAll("key1"), DeepEquals, []string{"value1", "value3"}) +} + +func (s *SectionSuite) TestSection_HasOption(c *C) { + sect := &Section{ + Options: []*Option{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + {Key: "key1", Value: "value3"}, + }, + } + c.Assert(sect.HasOption("otherkey"), Equals, false) + c.Assert(sect.HasOption("key2"), Equals, true) + c.Assert(sect.HasOption("key1"), Equals, true) +} + +func (s *SectionSuite) TestSection_AddOption(c *C) { + sect := &Section{ + Options: []*Option{ + {"key1", "value1"}, + }, + } + sect1 := &Section{ + Options: []*Option{ + {"key1", "value1"}, + {"key2", "value2"}, + }, + } + c.Assert(sect.AddOption("key2", "value2"), DeepEquals, sect1) + + sect2 := &Section{ + Options: []*Option{ + {"key1", "value1"}, + {"key2", "value2"}, + {"key1", "value3"}, + }, + } + c.Assert(sect.AddOption("key1", "value3"), DeepEquals, sect2) +} + +func (s *SectionSuite) TestSection_SetOption(c *C) { + sect := &Section{ + Options: []*Option{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + }, + } + + expected := &Section{ + Options: []*Option{ + {Key: "key2", Value: "value2"}, + {Key: "key1", Value: "value4"}, + }, + } + c.Assert(sect.SetOption("key1", "value4"), DeepEquals, expected) } func (s *SectionSuite) TestSection_RemoveOption(c *C) { @@ -52,7 +215,16 @@ func (s *SectionSuite) TestSection_RemoveOption(c *C) { c.Assert(sect.RemoveOption("key1"), DeepEquals, expected) } -func (s *SectionSuite) TestSubsection_RemoveOption(c *C) { +func (s *SectionSuite) TestSubsection_IsName(c *C) { + sect := &Subsection{ + Name: "name1", + } + + c.Assert(sect.IsName("name1"), Equals, true) + c.Assert(sect.IsName("Name1"), Equals, false) +} + +func (s *SectionSuite) TestSubsection_Option(c *C) { sect := &Subsection{ Options: []*Option{ {Key: "key1", Value: "value1"}, @@ -60,14 +232,59 @@ func (s *SectionSuite) TestSubsection_RemoveOption(c *C) { {Key: "key1", Value: "value3"}, }, } - c.Assert(sect.RemoveOption("otherkey"), DeepEquals, sect) + c.Assert(sect.Option("otherkey"), Equals, "") + c.Assert(sect.Option("key2"), Equals, "value2") + c.Assert(sect.Option("key1"), Equals, "value3") +} - expected := &Subsection{ +func (s *SectionSuite) TestSubsection_OptionAll(c *C) { + sect := &Subsection{ Options: []*Option{ + {Key: "key1", Value: "value1"}, {Key: "key2", Value: "value2"}, + {Key: "key1", Value: "value3"}, }, } - c.Assert(sect.RemoveOption("key1"), DeepEquals, expected) + c.Assert(sect.OptionAll("otherkey"), DeepEquals, []string{}) + c.Assert(sect.OptionAll("key2"), DeepEquals, []string{"value2"}) + c.Assert(sect.OptionAll("key1"), DeepEquals, []string{"value1", "value3"}) +} + +func (s *SectionSuite) TestSubsection_HasOption(c *C) { + sect := &Subsection{ + Options: []*Option{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + {Key: "key1", Value: "value3"}, + }, + } + c.Assert(sect.HasOption("otherkey"), Equals, false) + c.Assert(sect.HasOption("key2"), Equals, true) + c.Assert(sect.HasOption("key1"), Equals, true) +} + +func (s *SectionSuite) TestSubsection_AddOption(c *C) { + sect := &Subsection{ + Options: []*Option{ + {"key1", "value1"}, + }, + } + sect1 := &Subsection{ + Options: []*Option{ + {"key1", "value1"}, + {"key2", "value2"}, + }, + } + c.Assert(sect.AddOption("key2", "value2"), DeepEquals, sect1) + + sect2 := &Subsection{ + Options: []*Option{ + {"key1", "value1"}, + {"key2", "value2"}, + {"key1", "value3"}, + }, + } + c.Assert(sect.AddOption("key1", "value3"), DeepEquals, sect2) } func (s *SectionSuite) TestSubsection_SetOption(c *C) { @@ -88,3 +305,21 @@ func (s *SectionSuite) TestSubsection_SetOption(c *C) { } c.Assert(sect.SetOption("key1", "value1", "value4"), DeepEquals, expected) } + +func (s *SectionSuite) TestSubsection_RemoveOption(c *C) { + sect := &Subsection{ + Options: []*Option{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + {Key: "key1", Value: "value3"}, + }, + } + c.Assert(sect.RemoveOption("otherkey"), DeepEquals, sect) + + expected := &Subsection{ + Options: []*Option{ + {Key: "key2", Value: "value2"}, + }, + } + c.Assert(sect.RemoveOption("key1"), DeepEquals, expected) +} @@ -32,6 +32,19 @@ var ( ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec") ) +type NoMatchingRefSpecError struct { + refSpec config.RefSpec +} + +func (e NoMatchingRefSpecError) Error() string { + return fmt.Sprintf("couldn't find remote ref %q", e.refSpec.Src()) +} + +func (e NoMatchingRefSpecError) Is(target error) bool { + _, ok := target.(NoMatchingRefSpecError) + return ok +} + const ( // This describes the maximum number of commits to walk when // computing the haves to send to a server, for each ref in the @@ -751,7 +764,7 @@ func doCalculateRefs( }) if !matched && !s.IsWildcard() { - return fmt.Errorf("couldn't find remote ref %q", s.Src()) + return NoMatchingRefSpecError{refSpec: s} } return err diff --git a/remote_test.go b/remote_test.go index c6ea9ea..3446f1a 100644 --- a/remote_test.go +++ b/remote_test.go @@ -3,6 +3,7 @@ package git import ( "bytes" "context" + "errors" "io" "io/ioutil" "os" @@ -145,6 +146,7 @@ func (s *RemoteSuite) TestFetchNonExistantReference(c *C) { }) c.Assert(err, ErrorMatches, "couldn't find remote ref.*") + c.Assert(errors.Is(err, NoMatchingRefSpecError{}), Equals, true) } func (s *RemoteSuite) TestFetchContext(c *C) { diff --git a/submodule.go b/submodule.go index dff26b0..b6bef46 100644 --- a/submodule.go +++ b/submodule.go @@ -5,6 +5,8 @@ import ( "context" "errors" "fmt" + "net/url" + "path" "github.com/go-git/go-billy/v5" "github.com/go-git/go-git/v5/config" @@ -131,9 +133,29 @@ func (s *Submodule) Repository() (*Repository, error) { return nil, err } + moduleURL, err := url.Parse(s.c.URL) + if err != nil { + return nil, err + } + + if !path.IsAbs(moduleURL.Path) { + remotes, err := s.w.r.Remotes() + if err != nil { + return nil, err + } + + rootURL, err := url.Parse(remotes[0].c.URLs[0]) + if err != nil { + return nil, err + } + + rootURL.Path = path.Join(rootURL.Path, moduleURL.Path) + *moduleURL = *rootURL + } + _, err = r.CreateRemote(&config.RemoteConfig{ Name: DefaultRemoteName, - URLs: []string{s.c.URL}, + URLs: []string{moduleURL.String()}, }) return r, err diff --git a/worktree.go b/worktree.go index 62ad03b..1c89a02 100644 --- a/worktree.go +++ b/worktree.go @@ -716,7 +716,11 @@ func (w *Worktree) readGitmodulesFile() (*config.Modules, error) { } m := config.NewModules() - return m, m.Unmarshal(input) + if err := m.Unmarshal(input); err != nil { + return m, err + } + + return m, nil } // Clean the worktree by removing untracked files. diff --git a/worktree_test.go b/worktree_test.go index 59c80af..8a7586a 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "io" "io/ioutil" "os" "path/filepath" @@ -12,7 +13,7 @@ import ( "testing" "time" - fixtures "github.com/go-git/go-git-fixtures/v4" + "github.com/go-git/go-git-fixtures/v4" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" @@ -519,6 +520,62 @@ func (s *WorktreeSuite) TestCheckoutSubmoduleInitialized(c *C) { c.Assert(status.IsClean(), Equals, true) } +func (s *WorktreeSuite) TestCheckoutRelativePathSubmoduleInitialized(c *C) { + url := "https://github.com/git-fixtures/submodule.git" + r := s.NewRepository(fixtures.ByURL(url).One()) + + // modify the .gitmodules from original one + file, err := r.wt.OpenFile(".gitmodules", os.O_WRONLY|os.O_TRUNC, 0666) + c.Assert(err, IsNil) + + n, err := io.WriteString(file, `[submodule "basic"] + path = basic + url = ../basic.git +[submodule "itself"] + path = itself + url = ../submodule.git`) + c.Assert(err, IsNil) + c.Assert(n, Not(Equals), 0) + + w, err := r.Worktree() + c.Assert(err, IsNil) + + w.Add(".gitmodules") + w.Commit("test", &CommitOptions{}) + + // test submodule path + modules, err := w.readGitmodulesFile() + + c.Assert(modules.Submodules["basic"].URL, Equals, "../basic.git") + c.Assert(modules.Submodules["itself"].URL, Equals, "../submodule.git") + + basicSubmodule, err := w.Submodule("basic") + c.Assert(err, IsNil) + basicRepo, err := basicSubmodule.Repository() + c.Assert(err, IsNil) + basicRemotes, err := basicRepo.Remotes() + c.Assert(err, IsNil) + c.Assert(basicRemotes[0].Config().URLs[0], Equals, "https://github.com/git-fixtures/basic.git") + + itselfSubmodule, err := w.Submodule("itself") + c.Assert(err, IsNil) + itselfRepo, err := itselfSubmodule.Repository() + c.Assert(err, IsNil) + itselfRemotes, err := itselfRepo.Remotes() + c.Assert(err, IsNil) + c.Assert(itselfRemotes[0].Config().URLs[0], Equals, "https://github.com/git-fixtures/submodule.git") + + sub, err := w.Submodules() + c.Assert(err, IsNil) + + err = sub.Update(&SubmoduleUpdateOptions{Init: true, RecurseSubmodules: DefaultSubmoduleRecursionDepth}) + c.Assert(err, IsNil) + + status, err := w.Status() + c.Assert(err, IsNil) + c.Assert(status.IsClean(), Equals, true) +} + func (s *WorktreeSuite) TestCheckoutIndexMem(c *C) { fs := memfs.New() w := &Worktree{ |