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

Commit c991d2d

Browse files
authored
Merge pull request #344 from mcuadros/submodules-checkout
worktree: reset and checkout support for submodules
2 parents 057f1dd + 4b0fc1e commit c991d2d

File tree

10 files changed

+483
-58
lines changed

10 files changed

+483
-58
lines changed

plumbing/format/index/index.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
package index
22

33
import (
4+
"bytes"
45
"errors"
56
"fmt"
67
"time"
78

8-
"bytes"
9-
109
"gopkg.in/src-d/go-git.v4/plumbing"
1110
"gopkg.in/src-d/go-git.v4/plumbing/filemode"
1211
)
1312

1413
var (
15-
// ErrUnsupportedVersion is returned by Decode when the idxindex file
16-
// version is not supported.
17-
ErrUnsupportedVersion = errors.New("Unsuported version")
14+
// ErrUnsupportedVersion is returned by Decode when the index file version
15+
// is not supported.
16+
ErrUnsupportedVersion = errors.New("unsupported version")
17+
// ErrEntryNotFound is returned by Index.Entry, if an entry is not found.
18+
ErrEntryNotFound = errors.New("entry not found")
1819

1920
indexSignature = []byte{'D', 'I', 'R', 'C'}
2021
treeExtSignature = []byte{'T', 'R', 'E', 'E'}
@@ -50,6 +51,17 @@ type Index struct {
5051
ResolveUndo *ResolveUndo
5152
}
5253

54+
// Entry returns the entry that match the given path, if any.
55+
func (i *Index) Entry(path string) (Entry, error) {
56+
for _, e := range i.Entries {
57+
if e.Name == path {
58+
return e, nil
59+
}
60+
}
61+
62+
return Entry{}, ErrEntryNotFound
63+
}
64+
5365
// String is equivalent to `git ls-files --stage --debug`
5466
func (i *Index) String() string {
5567
buf := bytes.NewBuffer(nil)

plumbing/format/index/index_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package index
2+
3+
import (
4+
. "gopkg.in/check.v1"
5+
)
6+
7+
func (s *IndexSuite) TestIndexEntry(c *C) {
8+
idx := &Index{
9+
Entries: []Entry{
10+
{Name: "foo", Size: 42},
11+
{Name: "bar", Size: 82},
12+
},
13+
}
14+
15+
e, err := idx.Entry("foo")
16+
c.Assert(err, IsNil)
17+
c.Assert(e.Name, Equals, "foo")
18+
19+
e, err = idx.Entry("missing")
20+
c.Assert(err, Equals, ErrEntryNotFound)
21+
c.Assert(e.Name, Equals, "")
22+
}

storage/storer.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type Storer interface {
2020

2121
// ModuleStorer allows interact with the modules' Storers
2222
type ModuleStorer interface {
23-
// Module returns a Storer reprensting a submodule, if not exists returns a
23+
// Module returns a Storer representing a submodule, if not exists returns a
2424
// new empty Storer is returned
2525
Module(name string) (Storer, error)
2626
}

submodule.go

+154-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package git
22

33
import (
4+
"bytes"
45
"errors"
6+
"fmt"
57

68
"gopkg.in/src-d/go-git.v4/config"
79
"gopkg.in/src-d/go-git.v4/plumbing"
10+
"gopkg.in/src-d/go-git.v4/plumbing/format/index"
811
)
912

1013
var (
@@ -15,6 +18,7 @@ var (
1518
// Submodule a submodule allows you to keep another Git repository in a
1619
// subdirectory of your repository.
1720
type Submodule struct {
21+
// initialized defines if a submodule was already initialized.
1822
initialized bool
1923

2024
c *config.Submodule
@@ -26,7 +30,7 @@ func (s *Submodule) Config() *config.Submodule {
2630
return s.c
2731
}
2832

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

52+
// Status returns the status of the submodule.
53+
func (s *Submodule) Status() (*SubmoduleStatus, error) {
54+
idx, err := s.w.r.Storer.Index()
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
return s.status(idx)
60+
}
61+
62+
func (s *Submodule) status(idx *index.Index) (*SubmoduleStatus, error) {
63+
e, err := idx.Entry(s.c.Path)
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
status := &SubmoduleStatus{
69+
Path: s.c.Path,
70+
Expected: e.Hash,
71+
}
72+
73+
if !s.initialized {
74+
return status, nil
75+
}
76+
77+
r, err := s.Repository()
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
head, err := r.Head()
83+
if err == nil {
84+
status.Current = head.Hash()
85+
}
86+
87+
if err != nil && err == plumbing.ErrReferenceNotFound {
88+
err = nil
89+
}
90+
91+
return status, err
92+
}
93+
4894
// Repository returns the Repository represented by this submodule
4995
func (s *Submodule) Repository() (*Repository, error) {
96+
if !s.initialized {
97+
return nil, ErrSubmoduleNotInitialized
98+
}
99+
50100
storer, err := s.w.r.Storer.Module(s.c.Name)
51101
if err != nil {
52102
return nil, err
@@ -76,9 +126,13 @@ func (s *Submodule) Repository() (*Repository, error) {
76126
}
77127

78128
// Update the registered submodule to match what the superproject expects, the
79-
// submodule should be initilized first calling the Init method or setting in
129+
// submodule should be initialized first calling the Init method or setting in
80130
// the options SubmoduleUpdateOptions.Init equals true
81131
func (s *Submodule) Update(o *SubmoduleUpdateOptions) error {
132+
return s.update(o, plumbing.ZeroHash)
133+
}
134+
135+
func (s *Submodule) update(o *SubmoduleUpdateOptions, forceHash plumbing.Hash) error {
82136
if !s.initialized && !o.Init {
83137
return ErrSubmoduleNotInitialized
84138
}
@@ -89,17 +143,27 @@ func (s *Submodule) Update(o *SubmoduleUpdateOptions) error {
89143
}
90144
}
91145

92-
e, err := s.w.readIndexEntry(s.c.Path)
146+
idx, err := s.w.r.Storer.Index()
93147
if err != nil {
94148
return err
95149
}
96150

151+
hash := forceHash
152+
if hash.IsZero() {
153+
e, err := idx.Entry(s.c.Path)
154+
if err != nil {
155+
return err
156+
}
157+
158+
hash = e.Hash
159+
}
160+
97161
r, err := s.Repository()
98162
if err != nil {
99163
return err
100164
}
101165

102-
if err := s.fetchAndCheckout(r, o, e.Hash); err != nil {
166+
if err := s.fetchAndCheckout(r, o, hash); err != nil {
103167
return err
104168
}
105169

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

124188
new := &SubmoduleUpdateOptions{}
125189
*new = *o
190+
126191
new.RecurseSubmodules--
127192
return l.Update(new)
128193
}
@@ -148,10 +213,10 @@ func (s *Submodule) fetchAndCheckout(r *Repository, o *SubmoduleUpdateOptions, h
148213
return r.Storer.SetReference(head)
149214
}
150215

151-
// Submodules list of several submodules from the same repository
216+
// Submodules list of several submodules from the same repository.
152217
type Submodules []*Submodule
153218

154-
// Init initializes the submodules in this list
219+
// Init initializes the submodules in this list.
155220
func (s Submodules) Init() error {
156221
for _, sub := range s {
157222
if err := sub.Init(); err != nil {
@@ -162,7 +227,7 @@ func (s Submodules) Init() error {
162227
return nil
163228
}
164229

165-
// Update updates all the submodules in this list
230+
// Update updates all the submodules in this list.
166231
func (s Submodules) Update(o *SubmoduleUpdateOptions) error {
167232
for _, sub := range s {
168233
if err := sub.Update(o); err != nil {
@@ -172,3 +237,85 @@ func (s Submodules) Update(o *SubmoduleUpdateOptions) error {
172237

173238
return nil
174239
}
240+
241+
// Status returns the status of the submodules.
242+
func (s Submodules) Status() (SubmodulesStatus, error) {
243+
var list SubmodulesStatus
244+
245+
var r *Repository
246+
for _, sub := range s {
247+
if r == nil {
248+
r = sub.w.r
249+
}
250+
251+
idx, err := r.Storer.Index()
252+
if err != nil {
253+
return nil, err
254+
}
255+
256+
status, err := sub.status(idx)
257+
if err != nil {
258+
return nil, err
259+
}
260+
261+
list = append(list, status)
262+
}
263+
264+
return list, nil
265+
}
266+
267+
// SubmodulesStatus contains the status for all submodiles in the worktree
268+
type SubmodulesStatus []*SubmoduleStatus
269+
270+
// String is equivalent to `git submodule status`
271+
func (s SubmodulesStatus) String() string {
272+
buf := bytes.NewBuffer(nil)
273+
for _, sub := range s {
274+
fmt.Fprintln(buf, sub)
275+
}
276+
277+
return buf.String()
278+
}
279+
280+
// SubmoduleStatus contains the status for a submodule in the worktree
281+
type SubmoduleStatus struct {
282+
Path string
283+
Current plumbing.Hash
284+
Expected plumbing.Hash
285+
Branch plumbing.ReferenceName
286+
}
287+
288+
// IsClean is the HEAD of the submodule is equals to the expected commit
289+
func (s *SubmoduleStatus) IsClean() bool {
290+
return s.Current == s.Expected
291+
}
292+
293+
// String is equivalent to `git submodule status <submodule>`
294+
//
295+
// This will print the SHA-1 of the currently checked out commit for a
296+
// submodule, along with the submodule path and the output of git describe fo
297+
// the SHA-1. Each SHA-1 will be prefixed with - if the submodule is not
298+
// initialized, + if the currently checked out submodule commit does not match
299+
// the SHA-1 found in the index of the containing repository.
300+
func (s *SubmoduleStatus) String() string {
301+
var extra string
302+
var status = ' '
303+
304+
if s.Current.IsZero() {
305+
status = '-'
306+
} else if !s.IsClean() {
307+
status = '+'
308+
}
309+
310+
if len(s.Branch) != 0 {
311+
extra = string(s.Branch[5:])
312+
} else if !s.Current.IsZero() {
313+
extra = s.Current.String()[:7]
314+
}
315+
316+
if extra != "" {
317+
extra = fmt.Sprintf(" (%s)", extra)
318+
}
319+
320+
return fmt.Sprintf("%c%s %s%s", status, s.Expected, s.Path, extra)
321+
}

submodule_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,21 @@ func (s *SubmoduleSuite) TestInit(c *C) {
4848
sm, err := s.Worktree.Submodule("basic")
4949
c.Assert(err, IsNil)
5050

51+
c.Assert(sm.initialized, Equals, false)
5152
err = sm.Init()
5253
c.Assert(err, IsNil)
5354

55+
c.Assert(sm.initialized, Equals, true)
56+
5457
cfg, err := s.Repository.Config()
5558
c.Assert(err, IsNil)
5659

5760
c.Assert(cfg.Submodules, HasLen, 1)
5861
c.Assert(cfg.Submodules["basic"], NotNil)
62+
63+
status, err := sm.Status()
64+
c.Assert(err, IsNil)
65+
c.Assert(status.IsClean(), Equals, false)
5966
}
6067

6168
func (s *SubmoduleSuite) TestUpdate(c *C) {
@@ -74,6 +81,19 @@ func (s *SubmoduleSuite) TestUpdate(c *C) {
7481
ref, err := r.Reference(plumbing.HEAD, true)
7582
c.Assert(err, IsNil)
7683
c.Assert(ref.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
84+
85+
status, err := sm.Status()
86+
c.Assert(err, IsNil)
87+
c.Assert(status.IsClean(), Equals, true)
88+
}
89+
90+
func (s *SubmoduleSuite) TestRepositoryWithoutInit(c *C) {
91+
sm, err := s.Worktree.Submodule("basic")
92+
c.Assert(err, IsNil)
93+
94+
r, err := sm.Repository()
95+
c.Assert(err, Equals, ErrSubmoduleNotInitialized)
96+
c.Assert(r, IsNil)
7797
}
7898

7999
func (s *SubmoduleSuite) TestUpdateWithoutInit(c *C) {
@@ -161,3 +181,12 @@ func (s *SubmoduleSuite) TestSubmodulesInit(c *C) {
161181
c.Assert(m.initialized, Equals, true)
162182
}
163183
}
184+
185+
func (s *SubmoduleSuite) TestSubmodulesStatus(c *C) {
186+
sm, err := s.Worktree.Submodules()
187+
c.Assert(err, IsNil)
188+
189+
status, err := sm.Status()
190+
c.Assert(err, IsNil)
191+
c.Assert(status, HasLen, 2)
192+
}

0 commit comments

Comments
 (0)