1
1
package git
2
2
3
3
import (
4
+ "bytes"
4
5
"errors"
6
+ "fmt"
5
7
6
8
"gopkg.in/src-d/go-git.v4/config"
7
9
"gopkg.in/src-d/go-git.v4/plumbing"
10
+ "gopkg.in/src-d/go-git.v4/plumbing/format/index"
8
11
)
9
12
10
13
var (
15
18
// Submodule a submodule allows you to keep another Git repository in a
16
19
// subdirectory of your repository.
17
20
type Submodule struct {
21
+ // initialized defines if a submodule was already initialized.
18
22
initialized bool
19
23
20
24
c * config.Submodule
@@ -26,7 +30,7 @@ func (s *Submodule) Config() *config.Submodule {
26
30
return s .c
27
31
}
28
32
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
30
34
// the given submodule
31
35
func (s * Submodule ) Init () error {
32
36
cfg , err := s .w .r .Storer .Config ()
@@ -45,8 +49,54 @@ func (s *Submodule) Init() error {
45
49
return s .w .r .Storer .SetConfig (cfg )
46
50
}
47
51
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
+
48
94
// Repository returns the Repository represented by this submodule
49
95
func (s * Submodule ) Repository () (* Repository , error ) {
96
+ if ! s .initialized {
97
+ return nil , ErrSubmoduleNotInitialized
98
+ }
99
+
50
100
storer , err := s .w .r .Storer .Module (s .c .Name )
51
101
if err != nil {
52
102
return nil , err
@@ -76,9 +126,13 @@ func (s *Submodule) Repository() (*Repository, error) {
76
126
}
77
127
78
128
// 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
80
130
// the options SubmoduleUpdateOptions.Init equals true
81
131
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 {
82
136
if ! s .initialized && ! o .Init {
83
137
return ErrSubmoduleNotInitialized
84
138
}
@@ -89,17 +143,27 @@ func (s *Submodule) Update(o *SubmoduleUpdateOptions) error {
89
143
}
90
144
}
91
145
92
- e , err := s .w .readIndexEntry ( s . c . Path )
146
+ idx , err := s .w .r . Storer . Index ( )
93
147
if err != nil {
94
148
return err
95
149
}
96
150
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
+
97
161
r , err := s .Repository ()
98
162
if err != nil {
99
163
return err
100
164
}
101
165
102
- if err := s .fetchAndCheckout (r , o , e . Hash ); err != nil {
166
+ if err := s .fetchAndCheckout (r , o , hash ); err != nil {
103
167
return err
104
168
}
105
169
@@ -123,6 +187,7 @@ func (s *Submodule) doRecursiveUpdate(r *Repository, o *SubmoduleUpdateOptions)
123
187
124
188
new := & SubmoduleUpdateOptions {}
125
189
* new = * o
190
+
126
191
new .RecurseSubmodules --
127
192
return l .Update (new )
128
193
}
@@ -148,10 +213,10 @@ func (s *Submodule) fetchAndCheckout(r *Repository, o *SubmoduleUpdateOptions, h
148
213
return r .Storer .SetReference (head )
149
214
}
150
215
151
- // Submodules list of several submodules from the same repository
216
+ // Submodules list of several submodules from the same repository.
152
217
type Submodules []* Submodule
153
218
154
- // Init initializes the submodules in this list
219
+ // Init initializes the submodules in this list.
155
220
func (s Submodules ) Init () error {
156
221
for _ , sub := range s {
157
222
if err := sub .Init (); err != nil {
@@ -162,7 +227,7 @@ func (s Submodules) Init() error {
162
227
return nil
163
228
}
164
229
165
- // Update updates all the submodules in this list
230
+ // Update updates all the submodules in this list.
166
231
func (s Submodules ) Update (o * SubmoduleUpdateOptions ) error {
167
232
for _ , sub := range s {
168
233
if err := sub .Update (o ); err != nil {
@@ -172,3 +237,85 @@ func (s Submodules) Update(o *SubmoduleUpdateOptions) error {
172
237
173
238
return nil
174
239
}
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
+ }
0 commit comments