@@ -27,6 +27,7 @@ func fatalf(fmt string, args ...interface{}) {
27
27
type Trac struct {
28
28
name string
29
29
hash plumbing.Hash
30
+ parents []* Trac
30
31
subHeads []* Trac
31
32
tracCommit * object.Commit
32
33
}
@@ -47,24 +48,22 @@ func (t Trac) String() string {
47
48
}
48
49
49
50
type Cache struct {
50
- repoDir string
51
- repo * git.Repository
52
- tracs map [plumbing.Hash ]* Trac
51
+ repoDir string
52
+ repo * git.Repository
53
+ excludes map [plumbing.Hash ]bool
54
+ tracs map [plumbing.Hash ]* Trac
53
55
}
54
56
55
57
func NewCache (rdir string , r * git.Repository , excludes []string ) * Cache {
56
58
c := Cache {
57
- repoDir : rdir ,
58
- repo : r ,
59
- tracs : make (map [plumbing.Hash ]* Trac ),
59
+ repoDir : rdir ,
60
+ repo : r ,
61
+ excludes : make (map [plumbing.Hash ]bool ),
62
+ tracs : make (map [plumbing.Hash ]* Trac ),
60
63
}
61
64
for _ , x := range excludes {
62
65
hash := plumbing .NewHash (x )
63
- trac := & Trac {
64
- name : "[excluded]" ,
65
- hash : hash ,
66
- }
67
- c .add (trac )
66
+ c .excludes [hash ] = true
68
67
}
69
68
return & c
70
69
}
@@ -102,10 +101,8 @@ func (c *Cache) tracByRef(refname string) (*Trac, error) {
102
101
// any cycles when traversing the commit+submodule hierarchy, although the
103
102
// same sub-objects may occur many times at different points in the tree.
104
103
func (c * Cache ) tracCommit (path string , commit * object.Commit ) (* Trac , error ) {
105
- // debugf("commit %.10v %v\n", commit.Hash, path)
106
104
trac := c .tracs [commit .Hash ]
107
105
if trac != nil {
108
- // debugf(" found: %v\n", trac)
109
106
return trac , nil
110
107
}
111
108
trac = & Trac {
@@ -116,25 +113,125 @@ func (c *Cache) tracCommit(path string, commit *object.Commit) (*Trac, error) {
116
113
if err != nil {
117
114
return nil , fmt .Errorf ("%v:%.10v: %v" , path , commit .Hash , err )
118
115
}
119
- _ , err = c .tracTree (path + "/" , tree )
116
+ ttrac , err : = c .tracTree (path + "/" , tree )
120
117
if err != nil {
121
118
return nil , err
122
119
}
120
+
121
+ // The list of submodules owned by the tree is the same as the list
122
+ // owned by the commit.
123
+ trac .subHeads = ttrac .subHeads
124
+
123
125
for i , parent := range commit .ParentHashes {
124
126
pc , err := c .repo .CommitObject (parent )
125
127
if err != nil {
126
128
return nil , fmt .Errorf ("%v:%.10v: %v" , path , pc .Hash , err )
127
129
}
128
130
np := commitPath (path , i + 1 )
129
- _ , err = c .tracCommit (np , pc )
131
+ ptrac , err : = c .tracCommit (np , pc )
130
132
if err != nil {
131
133
return nil , err
132
134
}
135
+ trac .parents = append (trac .parents , ptrac )
136
+ }
137
+
138
+ seenHeads := make (map [plumbing.Hash ]bool )
139
+ seenTracs := make (map [plumbing.Hash ]bool )
140
+ var heads []* Trac
141
+ var tracs []* object.Commit
142
+
143
+ for _ , h := range trac .subHeads {
144
+ if ! seenHeads [h .hash ] {
145
+ seenHeads [h .hash ] = true
146
+ heads = append (heads , h )
147
+ }
133
148
}
149
+ for _ , p := range trac .parents {
150
+ if p .tracCommit != nil {
151
+ if ! seenTracs [p .tracCommit .Hash ] {
152
+ seenTracs [p .tracCommit .Hash ] = true
153
+ tracs = append (tracs , p .tracCommit )
154
+ }
155
+ }
156
+ }
157
+
158
+ if len (trac .parents ) == 1 && equalSubs (trac .subHeads , trac .parents [0 ].subHeads ) {
159
+ // Nothing has changed since our parent, no new commit needed.
160
+ trac .tracCommit = trac .parents [0 ].tracCommit
161
+ } else {
162
+ // Generate a new commit that includes our parent(s) and all
163
+ // our submodules.
164
+ trac .tracCommit , err = c .newTracCommit (commit , tracs , heads )
165
+ if err != nil {
166
+ return nil , err
167
+ }
168
+ }
169
+
134
170
c .add (trac )
135
171
return trac , nil
136
172
}
137
173
174
+ func equalSubs (a , b []* Trac ) bool {
175
+ if len (a ) != len (b ) {
176
+ return false
177
+ }
178
+ for i := range a {
179
+ if a [i ].hash != b [i ].hash {
180
+ return false
181
+ }
182
+ }
183
+ return true
184
+ }
185
+
186
+ func (c * Cache ) newTracCommit (commit * object.Commit , tracs []* object.Commit , heads []* Trac ) (* object.Commit , error ) {
187
+ var parents []plumbing.Hash
188
+
189
+ // Inherit from our parent tracCommits
190
+ for _ , c := range tracs {
191
+ parents = append (parents , c .Hash )
192
+ }
193
+ // *Also* inherit from the actual submodule heads included in the
194
+ // current commit
195
+ for _ , h := range heads {
196
+ parents = append (parents , h .hash )
197
+ }
198
+
199
+ sig := object.Signature {
200
+ Name : "git-subtrac" ,
201
+ Email : "git-subtrac@" ,
202
+ When : commit .Committer .When ,
203
+ }
204
+ emptyTree := object.Tree {}
205
+ nec := c .repo .Storer .NewEncodedObject ()
206
+ err := emptyTree .Encode (nec )
207
+ if err != nil {
208
+ return nil , fmt .Errorf ("emptyTree.Encode: %v" , err )
209
+ }
210
+ emptyTreeHash , err := c .repo .Storer .SetEncodedObject (nec )
211
+ if err != nil {
212
+ return nil , fmt .Errorf ("emptyTree.Store: %v" , err )
213
+ }
214
+
215
+ tc := & object.Commit {
216
+ Author : sig ,
217
+ Committer : sig ,
218
+ TreeHash : emptyTreeHash ,
219
+ ParentHashes : parents ,
220
+ Message : "[git-subtrac merge]" ,
221
+ }
222
+ nec = c .repo .Storer .NewEncodedObject ()
223
+ err = tc .Encode (nec )
224
+ if err != nil {
225
+ return nil , fmt .Errorf ("commit.Encode: %v" , err )
226
+ }
227
+ tch , err := c .repo .Storer .SetEncodedObject (nec )
228
+ if err != nil {
229
+ return nil , fmt .Errorf ("commit.Store: %v" , err )
230
+ }
231
+ tc .Hash = tch
232
+ return tc , nil
233
+ }
234
+
138
235
func commitPath (path string , sub int ) string {
139
236
if sub != 1 {
140
237
return fmt .Sprintf ("%s^%d" , path , sub )
@@ -216,45 +313,54 @@ func (c *Cache) tracTree(path string, tree *object.Tree) (*Trac, error) {
216
313
if trac != nil {
217
314
return trac , nil
218
315
}
316
+ trac = & Trac {
317
+ name : path ,
318
+ hash : tree .Hash ,
319
+ }
219
320
for _ , e := range tree .Entries {
220
321
if e .Mode == filemode .Submodule {
221
- if c .tracs [e .Hash ] != nil {
222
- // already handled
322
+ if c .excludes [e .Hash ] {
323
+ // Pretend it doesn't exist; don't link to it.
223
324
continue
224
325
}
225
- subpath := fmt . Sprintf ( "%s%s@%.10v" , path , e . Name , e . Hash )
226
- sc , err := c . repo . CommitObject ( e . Hash )
227
- if err != nil {
228
- err = c .tryFetchFromSubmodules ( subpath , e .Hash )
326
+ subtrac := c . tracs [ e . Hash ]
327
+ if subtrac == nil {
328
+ subpath := fmt . Sprintf ( "%s%s@%.10v" , path , e . Name , e . Hash )
329
+ sc , err : = c .repo . CommitObject ( e .Hash )
229
330
if err != nil {
230
- return nil , fmt .Errorf ("%v (maybe fetch it manually?)" , err )
331
+ err = c .tryFetchFromSubmodules (subpath , e .Hash )
332
+ if err != nil {
333
+ return nil , fmt .Errorf ("%v (maybe fetch it manually?)" , err )
334
+ }
335
+ }
336
+ sc , err = c .repo .CommitObject (e .Hash )
337
+ if err != nil {
338
+ return nil , fmt .Errorf ("%v: %v" ,
339
+ subpath , err )
340
+ }
341
+ subtrac , err = c .tracCommit (subpath , sc )
342
+ if err != nil {
343
+ return nil , err
231
344
}
232
345
}
233
- sc , err = c .repo .CommitObject (e .Hash )
234
- if err != nil {
235
- return nil , fmt .Errorf ("%v: %v" ,
236
- subpath , err )
237
- }
238
- _ , err = c .tracCommit (subpath , sc )
239
- if err != nil {
240
- return nil , err
241
- }
346
+ // Add exactly one submodule.
347
+ // subtrac.tracCommit includes any submodules which
348
+ // that submodule itself depends on.
349
+ trac .subHeads = append (trac .subHeads , subtrac )
242
350
} else if e .Mode == filemode .Dir {
243
351
t , err := c .repo .TreeObject (e .Hash )
244
352
if err != nil {
245
353
return nil , fmt .Errorf ("%v:%.10v: %v" ,
246
354
path + e .Name , e .Hash , err )
247
355
}
248
- _ , err = c .tracTree (path + e .Name + "/" , t )
356
+ subtrac , err : = c .tracTree (path + e .Name + "/" , t )
249
357
if err != nil {
250
358
return nil , err
251
359
}
360
+ // Collect the list of submodules all the way down the tree.
361
+ trac .subHeads = append (trac .subHeads , subtrac .subHeads ... )
252
362
}
253
363
}
254
- trac = & Trac {
255
- name : path ,
256
- hash : tree .Hash ,
257
- }
258
364
c .add (trac )
259
365
return trac , nil
260
366
}
@@ -301,11 +407,11 @@ func main() {
301
407
}
302
408
c := NewCache (* repodir , r , * excludes )
303
409
refname := args [1 ]
304
- _ , err = c .tracByRef (refname )
410
+ trac , err : = c .tracByRef (refname )
305
411
if err != nil {
306
412
fatalf ("%v\n " , err )
307
413
}
308
- fmt .Printf ("%v\n " , c )
414
+ fmt .Printf ("%v\n " , trac . tracCommit . Hash )
309
415
default :
310
416
usagef ("unknown command %v" , args [0 ])
311
417
}
0 commit comments