diff options
-rw-r--r-- | _examples/clone/main.go | 7 | ||||
-rw-r--r-- | options.go | 29 | ||||
-rw-r--r-- | repository.go | 23 | ||||
-rw-r--r-- | repository_test.go | 41 | ||||
-rw-r--r-- | submodule.go | 128 | ||||
-rw-r--r-- | submodule_test.go | 86 | ||||
-rw-r--r-- | worktree.go | 45 |
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) @@ -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) { |