Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

worktree: reset and checkout support for submodules #344

Merged
merged 2 commits into from
Apr 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions plumbing/format/index/index.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package index

import (
"bytes"
"errors"
"fmt"
"time"

"bytes"

"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/filemode"
)

var (
// ErrUnsupportedVersion is returned by Decode when the idxindex file
// version is not supported.
ErrUnsupportedVersion = errors.New("Unsuported version")
// ErrUnsupportedVersion is returned by Decode when the index file version
// is not supported.
ErrUnsupportedVersion = errors.New("unsupported version")
// ErrEntryNotFound is returned by Index.Entry, if an entry is not found.
ErrEntryNotFound = errors.New("entry not found")

indexSignature = []byte{'D', 'I', 'R', 'C'}
treeExtSignature = []byte{'T', 'R', 'E', 'E'}
Expand Down Expand Up @@ -50,6 +51,17 @@ type Index struct {
ResolveUndo *ResolveUndo
}

// Entry returns the entry that match the given path, if any.
func (i *Index) Entry(path string) (Entry, error) {
for _, e := range i.Entries {
if e.Name == path {
return e, nil
}
}

return Entry{}, ErrEntryNotFound
}

// String is equivalent to `git ls-files --stage --debug`
func (i *Index) String() string {
buf := bytes.NewBuffer(nil)
Expand Down
22 changes: 22 additions & 0 deletions plumbing/format/index/index_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package index

import (
. "gopkg.in/check.v1"
)

func (s *IndexSuite) TestIndexEntry(c *C) {
idx := &Index{
Entries: []Entry{
{Name: "foo", Size: 42},
{Name: "bar", Size: 82},
},
}

e, err := idx.Entry("foo")
c.Assert(err, IsNil)
c.Assert(e.Name, Equals, "foo")

e, err = idx.Entry("missing")
c.Assert(err, Equals, ErrEntryNotFound)
c.Assert(e.Name, Equals, "")
}
2 changes: 1 addition & 1 deletion storage/storer.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Storer interface {

// ModuleStorer allows interact with the modules' Storers
type ModuleStorer interface {
// Module returns a Storer reprensting a submodule, if not exists returns a
// Module returns a Storer representing a submodule, if not exists returns a
// new empty Storer is returned
Module(name string) (Storer, error)
}
161 changes: 154 additions & 7 deletions submodule.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package git

import (
"bytes"
"errors"
"fmt"

"gopkg.in/src-d/go-git.v4/config"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/format/index"
)

var (
Expand All @@ -15,6 +18,7 @@ var (
// Submodule a submodule allows you to keep another Git repository in a
// subdirectory of your repository.
type Submodule struct {
// initialized defines if a submodule was already initialized.
initialized bool

c *config.Submodule
Expand All @@ -26,7 +30,7 @@ func (s *Submodule) Config() *config.Submodule {
return s.c
}

// Init initialize the submodule reading the recoreded Entry in the index for
// Init initialize the submodule reading the recorded Entry in the index for
// the given submodule
func (s *Submodule) Init() error {
cfg, err := s.w.r.Storer.Config()
Expand All @@ -45,8 +49,54 @@ func (s *Submodule) Init() error {
return s.w.r.Storer.SetConfig(cfg)
}

// Status returns the status of the submodule.
func (s *Submodule) Status() (*SubmoduleStatus, error) {
idx, err := s.w.r.Storer.Index()
if err != nil {
return nil, err
}

return s.status(idx)
}

func (s *Submodule) status(idx *index.Index) (*SubmoduleStatus, error) {
e, err := idx.Entry(s.c.Path)
if err != nil {
return nil, err
}

status := &SubmoduleStatus{
Path: s.c.Path,
Expected: e.Hash,
}

if !s.initialized {
return status, nil
}

r, err := s.Repository()
if err != nil {
return nil, err
}

head, err := r.Head()
if err == nil {
status.Current = head.Hash()
}

if err != nil && err == plumbing.ErrReferenceNotFound {
err = nil
}

return status, err
}

// Repository returns the Repository represented by this submodule
func (s *Submodule) Repository() (*Repository, error) {
if !s.initialized {
return nil, ErrSubmoduleNotInitialized
}

storer, err := s.w.r.Storer.Module(s.c.Name)
if err != nil {
return nil, err
Expand Down Expand Up @@ -76,9 +126,13 @@ func (s *Submodule) Repository() (*Repository, error) {
}

// Update the registered submodule to match what the superproject expects, the
// submodule should be initilized first calling the Init method or setting in
// submodule should be initialized first calling the Init method or setting in
// the options SubmoduleUpdateOptions.Init equals true
func (s *Submodule) Update(o *SubmoduleUpdateOptions) error {
return s.update(o, plumbing.ZeroHash)
}

func (s *Submodule) update(o *SubmoduleUpdateOptions, forceHash plumbing.Hash) error {
if !s.initialized && !o.Init {
return ErrSubmoduleNotInitialized
}
Expand All @@ -89,17 +143,27 @@ func (s *Submodule) Update(o *SubmoduleUpdateOptions) error {
}
}

e, err := s.w.readIndexEntry(s.c.Path)
idx, err := s.w.r.Storer.Index()
if err != nil {
return err
}

hash := forceHash
if hash.IsZero() {
e, err := idx.Entry(s.c.Path)
if err != nil {
return err
}

hash = e.Hash
}

r, err := s.Repository()
if err != nil {
return err
}

if err := s.fetchAndCheckout(r, o, e.Hash); err != nil {
if err := s.fetchAndCheckout(r, o, hash); err != nil {
return err
}

Expand All @@ -123,6 +187,7 @@ func (s *Submodule) doRecursiveUpdate(r *Repository, o *SubmoduleUpdateOptions)

new := &SubmoduleUpdateOptions{}
*new = *o

new.RecurseSubmodules--
return l.Update(new)
}
Expand All @@ -148,10 +213,10 @@ func (s *Submodule) fetchAndCheckout(r *Repository, o *SubmoduleUpdateOptions, h
return r.Storer.SetReference(head)
}

// Submodules list of several submodules from the same repository
// Submodules list of several submodules from the same repository.
type Submodules []*Submodule

// Init initializes the submodules in this list
// Init initializes the submodules in this list.
func (s Submodules) Init() error {
for _, sub := range s {
if err := sub.Init(); err != nil {
Expand All @@ -162,7 +227,7 @@ func (s Submodules) Init() error {
return nil
}

// Update updates all the submodules in this list
// 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 {
Expand All @@ -172,3 +237,85 @@ func (s Submodules) Update(o *SubmoduleUpdateOptions) error {

return nil
}

// Status returns the status of the submodules.
func (s Submodules) Status() (SubmodulesStatus, error) {
var list SubmodulesStatus

var r *Repository
for _, sub := range s {
if r == nil {
r = sub.w.r
}

idx, err := r.Storer.Index()
if err != nil {
return nil, err
}

status, err := sub.status(idx)
if err != nil {
return nil, err
}

list = append(list, status)
}

return list, nil
}

// SubmodulesStatus contains the status for all submodiles in the worktree
type SubmodulesStatus []*SubmoduleStatus

// String is equivalent to `git submodule status`
func (s SubmodulesStatus) String() string {
buf := bytes.NewBuffer(nil)
for _, sub := range s {
fmt.Fprintln(buf, sub)
}

return buf.String()
}

// SubmoduleStatus contains the status for a submodule in the worktree
type SubmoduleStatus struct {
Path string
Current plumbing.Hash
Expected plumbing.Hash
Branch plumbing.ReferenceName
}

// IsClean is the HEAD of the submodule is equals to the expected commit
func (s *SubmoduleStatus) IsClean() bool {
return s.Current == s.Expected
}

// String is equivalent to `git submodule status <submodule>`
//
// This will print the SHA-1 of the currently checked out commit for a
// submodule, along with the submodule path and the output of git describe fo
// the SHA-1. Each SHA-1 will be prefixed with - if the submodule is not
// initialized, + if the currently checked out submodule commit does not match
// the SHA-1 found in the index of the containing repository.
func (s *SubmoduleStatus) String() string {
var extra string
var status = ' '

if s.Current.IsZero() {
status = '-'
} else if !s.IsClean() {
status = '+'
}

if len(s.Branch) != 0 {
extra = string(s.Branch[5:])
} else if !s.Current.IsZero() {
extra = s.Current.String()[:7]
}

if extra != "" {
extra = fmt.Sprintf(" (%s)", extra)
}

return fmt.Sprintf("%c%s %s%s", status, s.Expected, s.Path, extra)
}
29 changes: 29 additions & 0 deletions submodule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,21 @@ func (s *SubmoduleSuite) TestInit(c *C) {
sm, err := s.Worktree.Submodule("basic")
c.Assert(err, IsNil)

c.Assert(sm.initialized, Equals, false)
err = sm.Init()
c.Assert(err, IsNil)

c.Assert(sm.initialized, Equals, true)

cfg, err := s.Repository.Config()
c.Assert(err, IsNil)

c.Assert(cfg.Submodules, HasLen, 1)
c.Assert(cfg.Submodules["basic"], NotNil)

status, err := sm.Status()
c.Assert(err, IsNil)
c.Assert(status.IsClean(), Equals, false)
}

func (s *SubmoduleSuite) TestUpdate(c *C) {
Expand All @@ -74,6 +81,19 @@ func (s *SubmoduleSuite) TestUpdate(c *C) {
ref, err := r.Reference(plumbing.HEAD, true)
c.Assert(err, IsNil)
c.Assert(ref.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")

status, err := sm.Status()
c.Assert(err, IsNil)
c.Assert(status.IsClean(), Equals, true)
}

func (s *SubmoduleSuite) TestRepositoryWithoutInit(c *C) {
sm, err := s.Worktree.Submodule("basic")
c.Assert(err, IsNil)

r, err := sm.Repository()
c.Assert(err, Equals, ErrSubmoduleNotInitialized)
c.Assert(r, IsNil)
}

func (s *SubmoduleSuite) TestUpdateWithoutInit(c *C) {
Expand Down Expand Up @@ -161,3 +181,12 @@ func (s *SubmoduleSuite) TestSubmodulesInit(c *C) {
c.Assert(m.initialized, Equals, true)
}
}

func (s *SubmoduleSuite) TestSubmodulesStatus(c *C) {
sm, err := s.Worktree.Submodules()
c.Assert(err, IsNil)

status, err := sm.Status()
c.Assert(err, IsNil)
c.Assert(status, HasLen, 2)
}
Loading