aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--_examples/clone/main.go7
-rw-r--r--options.go29
-rw-r--r--repository.go23
-rw-r--r--repository_test.go41
-rw-r--r--submodule.go128
-rw-r--r--submodule_test.go86
-rw-r--r--worktree.go45
7 files changed, 286 insertions, 73 deletions
diff --git a/_examples/clone/main.go b/_examples/clone/main.go
index 18d8a55..d4d3880 100644
--- a/_examples/clone/main.go
+++ b/_examples/clone/main.go
@@ -14,12 +14,11 @@ func main() {
directory := os.Args[2]
// Clone the given repository to the given directory
- Info("git clone %s %s", url, directory)
+ Info("git clone %s %s --recursive", url, directory)
r, err := git.PlainClone(directory, false, &git.CloneOptions{
- URL: url,
- RecursiveSubmodules: true,
- Depth: 1,
+ URL: url,
+ RecurseSubmodules: git.DefaultRecursivity,
})
CheckIfError(err)
diff --git a/options.go b/options.go
index 1f83c36..db694b8 100644
--- a/options.go
+++ b/options.go
@@ -9,9 +9,18 @@ import (
"srcd.works/go-git.v4/plumbing/transport"
)
+// SubmoduleRescursivity defines how depth will affect any submodule recursive
+// operation
+type SubmoduleRescursivity int
+
const (
// DefaultRemoteName name of the default Remote, just like git command
DefaultRemoteName = "origin"
+
+ // NoRecursivity disables the recursion for a submodule operation
+ NoRecursivity SubmoduleRescursivity = 0
+ // DefaultRecursivity allow recursion in a submodule operation
+ DefaultRecursivity SubmoduleRescursivity = 10
)
var (
@@ -32,10 +41,10 @@ type CloneOptions struct {
SingleBranch bool
// Limit fetching to the specified number of commits
Depth int
- // RecursiveSubmodules after the clone is created, initialize all submodules
+ // RecurseSubmodules after the clone is created, initialize all submodules
// within, using their default settings. This option is ignored if the
// cloned repository does not have a worktree
- RecursiveSubmodules bool
+ RecurseSubmodules SubmoduleRescursivity
// Progress is where the human readable information sent by the server is
// stored, if nil nothing is stored and the capability (if supported)
// no-progress, is sent to the server to avoid send this information
@@ -71,6 +80,9 @@ type PullOptions struct {
Depth int
// Auth credentials, if required, to use with the remote repository
Auth transport.AuthMethod
+ // RecurseSubmodules controls if new commits of all populated submodules
+ // should be fetched too
+ RecurseSubmodules SubmoduleRescursivity
// Progress is where the human readable information sent by the server is
// stored, if nil nothing is stored and the capability (if supported)
// no-progress, is sent to the server to avoid send this information
@@ -152,3 +164,16 @@ func (o *PushOptions) Validate() error {
return nil
}
+
+// SubmoduleUpdateOptions describes how a submodule update should be performed
+type SubmoduleUpdateOptions struct {
+ // Init initializes the submodules recorded in the index
+ Init bool
+ // NoFetch tell to the update command to don’t fetch new objects from the
+ // remote site.
+ NoFetch bool
+ // RecurseSubmodules the update is performed not only in the submodules of
+ // the current repository but also in any nested submodules inside those
+ // submodules (and so on). Until the SubmoduleRescursivity is reached.
+ RecurseSubmodules SubmoduleRescursivity
+}
diff --git a/repository.go b/repository.go
index 4c184ab..9969b86 100644
--- a/repository.go
+++ b/repository.go
@@ -350,8 +350,8 @@ func (r *Repository) clone(o *CloneOptions) error {
return err
}
- if o.RecursiveSubmodules && r.wt != nil {
- if err := r.initSubmodules(); err != nil {
+ if o.RecurseSubmodules != NoRecursivity && r.wt != nil {
+ if err := r.updateSubmodules(o.RecurseSubmodules); err != nil {
return err
}
}
@@ -359,7 +359,7 @@ func (r *Repository) clone(o *CloneOptions) error {
return r.updateRemoteConfig(remote, o, c, head)
}
-func (r *Repository) initSubmodules() error {
+func (r *Repository) updateSubmodules(recursion SubmoduleRescursivity) error {
w, err := r.Worktree()
if err != nil {
return err
@@ -370,7 +370,10 @@ func (r *Repository) initSubmodules() error {
return err
}
- return s.Init()
+ return s.Update(&SubmoduleUpdateOptions{
+ Init: true,
+ RecurseSubmodules: recursion,
+ })
}
func (r *Repository) cloneRefSpec(o *CloneOptions,
@@ -546,7 +549,17 @@ func (r *Repository) Pull(o *PullOptions) error {
return NoErrAlreadyUpToDate
}
- return r.updateWorktree()
+ if err := r.updateWorktree(); err != nil {
+ return err
+ }
+
+ if o.RecurseSubmodules != NoRecursivity && r.wt != nil {
+ if err := r.updateSubmodules(o.RecurseSubmodules); err != nil {
+ return err
+ }
+ }
+
+ return nil
}
func (r *Repository) updateWorktree() error {
diff --git a/repository_test.go b/repository_test.go
index 2c1d4a2..6a9c14a 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -293,6 +293,25 @@ func (s *RepositorySuite) TestPlainClone(c *C) {
c.Assert(remotes, HasLen, 1)
}
+func (s *RepositorySuite) TestPlainCloneWithRecurseSubmodules(c *C) {
+ dir, err := ioutil.TempDir("", "plain-clone-submodule")
+ c.Assert(err, IsNil)
+ defer os.RemoveAll(dir)
+
+ path := fixtures.ByTag("submodule").One().Worktree().Base()
+
+ r, err := PlainClone(dir, false, &CloneOptions{
+ URL: fmt.Sprintf("file://%s", path),
+ RecurseSubmodules: DefaultRecursivity,
+ })
+
+ c.Assert(err, IsNil)
+
+ cfg, err := r.Config()
+ c.Assert(cfg.Remotes, HasLen, 1)
+ c.Assert(cfg.Submodules, HasLen, 2)
+}
+
func (s *RepositorySuite) TestFetch(c *C) {
r, _ := Init(memory.NewStorage(), nil)
_, err := r.CreateRemote(&config.RemoteConfig{
@@ -562,6 +581,28 @@ func (s *RepositorySuite) TestPullProgress(c *C) {
c.Assert(buf.Len(), Not(Equals), 0)
}
+func (s *RepositorySuite) TestPullProgressWithRecursion(c *C) {
+ path := fixtures.ByTag("submodule").One().Worktree().Base()
+
+ dir, err := ioutil.TempDir("", "plain-clone-submodule")
+ c.Assert(err, IsNil)
+ defer os.RemoveAll(dir)
+
+ r, _ := PlainInit(dir, false)
+ r.CreateRemote(&config.RemoteConfig{
+ Name: DefaultRemoteName,
+ URL: fmt.Sprintf("file://%s", path),
+ })
+
+ err = r.Pull(&PullOptions{
+ RecurseSubmodules: DefaultRecursivity,
+ })
+ c.Assert(err, IsNil)
+
+ cfg, err := r.Config()
+ c.Assert(cfg.Submodules, HasLen, 2)
+}
+
func (s *RepositorySuite) TestPullAdd(c *C) {
path := fixtures.Basic().ByTag("worktree").One().Worktree().Base()
diff --git a/submodule.go b/submodule.go
index 83c28b7..b6cc045 100644
--- a/submodule.go
+++ b/submodule.go
@@ -1,60 +1,141 @@
package git
import (
+ "errors"
+
"srcd.works/go-git.v4/config"
"srcd.works/go-git.v4/plumbing"
)
+var (
+ ErrSubmoduleAlreadyInitialized = errors.New("submodule already initialized")
+ ErrSubmoduleNotInitialized = errors.New("submodule not initialized")
+)
+
// Submodule a submodule allows you to keep another Git repository in a
// subdirectory of your repository.
type Submodule struct {
- m *config.Submodule
+ initialized bool
+
+ c *config.Submodule
w *Worktree
- // r is the submodule repository
- r *Repository
}
// Config returns the submodule config
func (s *Submodule) Config() *config.Submodule {
- return s.m
+ return s.c
}
// Init initialize the submodule reading the recoreded Entry in the index for
// the given submodule
func (s *Submodule) Init() error {
- e, err := s.w.readIndexEntry(s.m.Path)
+ cfg, err := s.w.r.Storer.Config()
if err != nil {
return err
}
- _, err = s.r.CreateRemote(&config.RemoteConfig{
+ _, ok := cfg.Submodules[s.c.Name]
+ if ok {
+ return ErrSubmoduleAlreadyInitialized
+ }
+
+ s.initialized = true
+
+ cfg.Submodules[s.c.Name] = s.c
+ return s.w.r.Storer.SetConfig(cfg)
+}
+
+// Repository returns the Repository represented by this submodule
+func (s *Submodule) Repository() (*Repository, error) {
+ storer, err := s.w.r.Storer.Module(s.c.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = storer.Reference(plumbing.HEAD)
+ if err != nil && err != plumbing.ErrReferenceNotFound {
+ return nil, err
+ }
+
+ worktree := s.w.fs.Dir(s.c.Path)
+ if err == nil {
+ return Open(storer, worktree)
+ }
+
+ r, err := Init(storer, worktree)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = r.CreateRemote(&config.RemoteConfig{
Name: DefaultRemoteName,
- URL: s.m.URL,
+ URL: s.c.URL,
})
+ return r, err
+}
+
+// Update the registered submodule to match what the superproject expects, the
+// submodule should be initilized first calling the Init method or setting in
+// the options SubmoduleUpdateOptions.Init equals true
+func (s *Submodule) Update(o *SubmoduleUpdateOptions) error {
+ if !s.initialized && !o.Init {
+ return ErrSubmoduleNotInitialized
+ }
+
+ if !s.initialized && o.Init {
+ if err := s.Init(); err != nil {
+ return err
+ }
+ }
+
+ e, err := s.w.readIndexEntry(s.c.Path)
if err != nil {
return err
}
- return s.fetchAndCheckout(e.Hash)
+ r, err := s.Repository()
+ if err != nil {
+ return err
+ }
+
+ if err := s.fetchAndCheckout(r, o, e.Hash); err != nil {
+ return err
+ }
+
+ return s.doRecrusiveUpdate(r, o)
}
-// Update the registered submodule to match what the superproject expects
-func (s *Submodule) Update() error {
- e, err := s.w.readIndexEntry(s.m.Path)
+func (s *Submodule) doRecrusiveUpdate(r *Repository, o *SubmoduleUpdateOptions) error {
+ if o.RecurseSubmodules == NoRecursivity {
+ return nil
+ }
+
+ w, err := r.Worktree()
if err != nil {
return err
}
- return s.fetchAndCheckout(e.Hash)
+ l, err := w.Submodules()
+ if err != nil {
+ return err
+ }
+
+ new := &SubmoduleUpdateOptions{}
+ *new = *o
+ new.RecurseSubmodules--
+ return l.Update(new)
}
-func (s *Submodule) fetchAndCheckout(hash plumbing.Hash) error {
- if err := s.r.Fetch(&FetchOptions{}); err != nil && err != NoErrAlreadyUpToDate {
- return err
+func (s *Submodule) fetchAndCheckout(r *Repository, o *SubmoduleUpdateOptions, hash plumbing.Hash) error {
+ if !o.NoFetch {
+ err := r.Fetch(&FetchOptions{})
+ if err != nil && err != NoErrAlreadyUpToDate {
+ return err
+ }
}
- w, err := s.r.Worktree()
+ w, err := r.Worktree()
if err != nil {
return err
}
@@ -64,13 +145,13 @@ func (s *Submodule) fetchAndCheckout(hash plumbing.Hash) error {
}
head := plumbing.NewHashReference(plumbing.HEAD, hash)
- return s.r.Storer.SetReference(head)
+ return r.Storer.SetReference(head)
}
// Submodules list of several submodules from the same repository
type Submodules []*Submodule
-// Init initialize the submodule recorded in the index
+// Init initializes the submodules in this list
func (s Submodules) Init() error {
for _, sub := range s {
if err := sub.Init(); err != nil {
@@ -80,3 +161,14 @@ func (s Submodules) Init() error {
return nil
}
+
+// Update updates all the submodules in this list
+func (s Submodules) Update(o *SubmoduleUpdateOptions) error {
+ for _, sub := range s {
+ if err := sub.Update(o); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/submodule_test.go b/submodule_test.go
index ed49927..a933965 100644
--- a/submodule_test.go
+++ b/submodule_test.go
@@ -36,7 +36,7 @@ func (s *SubmoduleSuite) SetUpTest(c *C) {
s.Worktree, err = r.Worktree()
c.Assert(err, IsNil)
- s.path = path
+ s.path = dir
}
func (s *SubmoduleSuite) TearDownTest(c *C) {
@@ -48,32 +48,78 @@ func (s *SubmoduleSuite) TestInit(c *C) {
sm, err := s.Worktree.Submodule("basic")
c.Assert(err, IsNil)
- _, err = sm.r.Reference(plumbing.HEAD, true)
- c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
-
err = sm.Init()
c.Assert(err, IsNil)
- ref, err := sm.r.Reference(plumbing.HEAD, true)
+ cfg, err := s.Repository.Config()
c.Assert(err, IsNil)
- c.Assert(ref.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
- w, err := sm.r.Worktree()
+ c.Assert(cfg.Submodules, HasLen, 1)
+ c.Assert(cfg.Submodules["basic"], NotNil)
+}
+
+func (s *SubmoduleSuite) TestUpdate(c *C) {
+ sm, err := s.Worktree.Submodule("basic")
c.Assert(err, IsNil)
- status, err := w.Status()
+ err = sm.Update(&SubmoduleUpdateOptions{
+ Init: true,
+ })
+
c.Assert(err, IsNil)
- c.Assert(status.IsClean(), Equals, true)
+
+ r, err := sm.Repository()
+ c.Assert(err, IsNil)
+
+ ref, err := r.Reference(plumbing.HEAD, true)
+ c.Assert(err, IsNil)
+ c.Assert(ref.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+
}
-func (s *SubmoduleSuite) TestUpdate(c *C) {
+func (s *SubmoduleSuite) TestUpdateWithoutInit(c *C) {
sm, err := s.Worktree.Submodule("basic")
c.Assert(err, IsNil)
- _, err = sm.r.Reference(plumbing.HEAD, true)
- c.Assert(err, Equals, plumbing.ErrReferenceNotFound)
+ err = sm.Update(&SubmoduleUpdateOptions{})
+ c.Assert(err, Equals, ErrSubmoduleNotInitialized)
+}
- err = sm.Init()
+func (s *SubmoduleSuite) TestUpdateWithNotFetch(c *C) {
+ sm, err := s.Worktree.Submodule("basic")
+ c.Assert(err, IsNil)
+
+ err = sm.Update(&SubmoduleUpdateOptions{
+ Init: true,
+ NoFetch: true,
+ })
+
+ // Since we are not fetching, the object is not there
+ c.Assert(err, Equals, plumbing.ErrObjectNotFound)
+}
+
+func (s *SubmoduleSuite) TestUpdateWithRecursion(c *C) {
+ sm, err := s.Worktree.Submodule("itself")
+ c.Assert(err, IsNil)
+
+ err = sm.Update(&SubmoduleUpdateOptions{
+ Init: true,
+ RecurseSubmodules: 2,
+ })
+
+ c.Assert(err, IsNil)
+
+ _, err = s.Worktree.fs.Stat("itself/basic/LICENSE")
+ c.Assert(err, IsNil)
+}
+
+func (s *SubmoduleSuite) TestUpdateWithInitAndUpdate(c *C) {
+ sm, err := s.Worktree.Submodule("basic")
+ c.Assert(err, IsNil)
+
+ err = sm.Update(&SubmoduleUpdateOptions{
+ Init: true,
+ })
c.Assert(err, IsNil)
idx, err := s.Repository.Storer.Index()
@@ -90,10 +136,13 @@ func (s *SubmoduleSuite) TestUpdate(c *C) {
err = s.Repository.Storer.SetIndex(idx)
c.Assert(err, IsNil)
- err = sm.Update()
+ err = sm.Update(&SubmoduleUpdateOptions{})
+ c.Assert(err, IsNil)
+
+ r, err := sm.Repository()
c.Assert(err, IsNil)
- ref, err := sm.r.Reference(plumbing.HEAD, true)
+ ref, err := r.Reference(plumbing.HEAD, true)
c.Assert(err, IsNil)
c.Assert(ref.Hash().String(), Equals, "b029517f6300c2da0f4b651b8642506cd6aaf45d")
@@ -106,9 +155,10 @@ func (s *SubmoduleSuite) TestSubmodulesInit(c *C) {
err = sm.Init()
c.Assert(err, IsNil)
+ sm, err = s.Worktree.Submodules()
+ c.Assert(err, IsNil)
+
for _, m := range sm {
- ref, err := m.r.Reference(plumbing.HEAD, true)
- c.Assert(err, IsNil)
- c.Assert(ref.Hash(), Not(Equals), plumbing.ZeroHash)
+ c.Assert(m.initialized, Equals, true)
}
}
diff --git a/worktree.go b/worktree.go
index 2a5b58a..2514a0c 100644
--- a/worktree.go
+++ b/worktree.go
@@ -212,17 +212,18 @@ const gitmodulesFile = ".gitmodules"
// Submodule returns the submodule with the given name
func (w *Worktree) Submodule(name string) (*Submodule, error) {
- m, err := w.readGitmodulesFile()
- if err != nil || m == nil {
+ l, err := w.Submodules()
+ if err != nil {
return nil, err
}
- c, ok := m.Submodules[name]
- if !ok {
- return nil, ErrSubmoduleNotFound
+ for _, m := range l {
+ if m.Config().Name == name {
+ return m, nil
+ }
}
- return w.newSubmodule(c)
+ return nil, ErrSubmoduleNotFound
}
// Submodules returns all the available submodules
@@ -233,34 +234,26 @@ func (w *Worktree) Submodules() (Submodules, error) {
return l, err
}
- for _, c := range m.Submodules {
- s, err := w.newSubmodule(c)
- if err != nil {
- return nil, err
- }
-
- l = append(l, s)
+ c, err := w.r.Config()
+ for _, s := range m.Submodules {
+ l = append(l, w.newSubmodule(s, c.Submodules[s.Name]))
}
return l, nil
}
-func (w *Worktree) newSubmodule(m *config.Submodule) (*Submodule, error) {
- s, err := w.r.Storer.Module(m.Name)
- if err != nil {
- return nil, err
- }
+func (w *Worktree) newSubmodule(fromModules, fromConfig *config.Submodule) *Submodule {
+ m := &Submodule{w: w}
+ m.initialized = fromConfig != nil
- r, err := Init(s, w.fs.Dir(m.Path))
- if err != nil {
- return nil, err
+ if !m.initialized {
+ m.c = fromModules
+ return m
}
- return &Submodule{
- m: m,
- w: w,
- r: r,
- }, nil
+ m.c = fromConfig
+ m.c.Path = fromModules.Path
+ return m
}
func (w *Worktree) readGitmodulesFile() (*config.Modules, error) {