5
5
"encoding/json"
6
6
"fmt"
7
7
"git-bundle-server/internal/core"
8
+ "git-bundle-server/internal/git"
8
9
"os"
10
+ "sort"
9
11
"strconv"
10
12
"strings"
11
13
"time"
@@ -34,11 +36,11 @@ type BundleList struct {
34
36
Bundles map [int64 ]Bundle
35
37
}
36
38
37
- func addBundleToList (bundle Bundle , list BundleList ) {
39
+ func addBundleToList (bundle Bundle , list * BundleList ) {
38
40
list .Bundles [bundle .CreationToken ] = bundle
39
41
}
40
42
41
- func CreateInitialBundle (repo core.Repository ) Bundle {
43
+ func CreateInitialBundle (repo * core.Repository ) Bundle {
42
44
timestamp := time .Now ().UTC ().Unix ()
43
45
bundleName := "bundle-" + fmt .Sprint (timestamp ) + ".bundle"
44
46
bundleFile := repo .WebDir + "/" + bundleName
@@ -51,14 +53,14 @@ func CreateInitialBundle(repo core.Repository) Bundle {
51
53
return bundle
52
54
}
53
55
54
- func CreateDistinctBundle (repo core.Repository , list BundleList ) Bundle {
56
+ func CreateDistinctBundle (repo * core.Repository , list * BundleList ) Bundle {
55
57
timestamp := time .Now ().UTC ().Unix ()
56
58
57
- _ , c := list . Bundles [ timestamp ]
59
+ keys := GetSortedCreationTokens ( list )
58
60
59
- for c {
60
- timestamp ++
61
- _ , c = list . Bundles [ timestamp ]
61
+ maxTimestamp := keys [ len ( keys ) - 1 ]
62
+ if timestamp <= maxTimestamp {
63
+ timestamp = maxTimestamp + 1
62
64
}
63
65
64
66
bundleName := "bundle-" + fmt .Sprint (timestamp ) + ".bundle"
@@ -72,21 +74,21 @@ func CreateDistinctBundle(repo core.Repository, list BundleList) Bundle {
72
74
return bundle
73
75
}
74
76
75
- func SingletonList (bundle Bundle ) BundleList {
77
+ func CreateSingletonList (bundle Bundle ) * BundleList {
76
78
list := BundleList {1 , "all" , make (map [int64 ]Bundle )}
77
79
78
- addBundleToList (bundle , list )
80
+ addBundleToList (bundle , & list )
79
81
80
- return list
82
+ return & list
81
83
}
82
84
83
85
// Given a BundleList
84
- func WriteBundleList (list BundleList , repo core.Repository ) error {
86
+ func WriteBundleList (list * BundleList , repo * core.Repository ) error {
85
87
listFile := repo .WebDir + "/bundle-list"
86
88
jsonFile := repo .RepoDir + "/bundle-list.json"
87
89
88
90
// TODO: Formalize lockfile concept.
89
- f , err := os .OpenFile (listFile + ".lock" , os .O_WRONLY | os .O_CREATE , 0600 )
91
+ f , err := os .OpenFile (listFile + ".lock" , os .O_WRONLY | os .O_CREATE , 0o600 )
90
92
if err != nil {
91
93
return fmt .Errorf ("failure to open file: %w" , err )
92
94
}
@@ -97,7 +99,10 @@ func WriteBundleList(list BundleList, repo core.Repository) error {
97
99
out , "[bundle]\n \t version = %d\n \t mode = %s\n \n " ,
98
100
list .Version , list .Mode )
99
101
100
- for token , bundle := range list .Bundles {
102
+ keys := GetSortedCreationTokens (list )
103
+
104
+ for _ , token := range keys {
105
+ bundle := list .Bundles [token ]
101
106
fmt .Fprintf (
102
107
out , "[bundle \" %d\" ]\n \t uri = %s\n \t creationToken = %d\n \n " ,
103
108
token , bundle .URI , token )
@@ -109,7 +114,7 @@ func WriteBundleList(list BundleList, repo core.Repository) error {
109
114
return fmt .Errorf ("failed to close lock file: %w" , err )
110
115
}
111
116
112
- f , err = os .OpenFile (jsonFile + ".lock" , os .O_WRONLY | os .O_CREATE , 0600 )
117
+ f , err = os .OpenFile (jsonFile + ".lock" , os .O_WRONLY | os .O_CREATE , 0o600 )
113
118
if err != nil {
114
119
return fmt .Errorf ("failed to open JSON file: %w" , err )
115
120
}
@@ -139,7 +144,7 @@ func WriteBundleList(list BundleList, repo core.Repository) error {
139
144
return os .Rename (listFile + ".lock" , listFile )
140
145
}
141
146
142
- func GetBundleList (repo core.Repository ) (* BundleList , error ) {
147
+ func GetBundleList (repo * core.Repository ) (* BundleList , error ) {
143
148
jsonFile := repo .RepoDir + "/bundle-list.json"
144
149
145
150
reader , err := os .Open (jsonFile )
@@ -182,8 +187,8 @@ func GetBundleHeader(bundle Bundle) (*BundleHeader, error) {
182
187
183
188
if line [0 ] == '#' &&
184
189
strings .HasPrefix (line , "# v" ) &&
185
- strings .HasSuffix (line , " git bundle\n " ) {
186
- header .Version , err = strconv .ParseInt (line [3 :len (line )- len (" git bundle\n " )], 10 , 64 )
190
+ strings .HasSuffix (line , " git bundle" ) {
191
+ header .Version , err = strconv .ParseInt (line [3 :len (line )- len (" git bundle" )], 10 , 64 )
187
192
if err != nil {
188
193
return nil , fmt .Errorf ("failed to parse bundle version: %s" , err )
189
194
}
@@ -226,7 +231,7 @@ func GetBundleHeader(bundle Bundle) (*BundleHeader, error) {
226
231
return & header , nil
227
232
}
228
233
229
- func GetAllPrereqsForIncrementalBundle (list BundleList ) ([]string , error ) {
234
+ func GetAllPrereqsForIncrementalBundle (list * BundleList ) ([]string , error ) {
230
235
prereqs := []string {}
231
236
232
237
for _ , bundle := range list .Bundles {
@@ -242,3 +247,93 @@ func GetAllPrereqsForIncrementalBundle(list BundleList) ([]string, error) {
242
247
243
248
return prereqs , nil
244
249
}
250
+
251
+ func CreateIncrementalBundle (repo * core.Repository , list * BundleList ) (* Bundle , error ) {
252
+ bundle := CreateDistinctBundle (repo , list )
253
+
254
+ lines , err := GetAllPrereqsForIncrementalBundle (list )
255
+ if err != nil {
256
+ return nil , err
257
+ }
258
+
259
+ written , err := git .CreateIncrementalBundle (repo .RepoDir , bundle .Filename , lines )
260
+ if err != nil {
261
+ return nil , fmt .Errorf ("failed to create incremental bundle: %w" , err )
262
+ }
263
+
264
+ if ! written {
265
+ return nil , nil
266
+ }
267
+
268
+ return & bundle , nil
269
+ }
270
+
271
+ func CollapseList (repo * core.Repository , list * BundleList ) error {
272
+ maxBundles := 5
273
+
274
+ if len (list .Bundles ) <= maxBundles {
275
+ return nil
276
+ }
277
+
278
+ keys := GetSortedCreationTokens (list )
279
+
280
+ refs := make (map [string ]string )
281
+
282
+ maxTimestamp := int64 (0 )
283
+
284
+ for i := range keys [0 : len (keys )- maxBundles + 1 ] {
285
+ bundle := list.Bundles [keys [i ]]
286
+
287
+ if bundle .CreationToken > maxTimestamp {
288
+ maxTimestamp = bundle .CreationToken
289
+ }
290
+
291
+ header , err := GetBundleHeader (bundle )
292
+ if err != nil {
293
+ return fmt .Errorf ("failed to parse bundle file %s: %w" , bundle .Filename , err )
294
+ }
295
+
296
+ // Ignore the old ref name and instead use the OID
297
+ // to generate the ref name. This allows us to create new
298
+ // refs that point to exactly these objects without disturbing
299
+ // refs/heads/ which is tracking the remote refs.
300
+ for _ , oid := range header .Refs {
301
+ refs ["refs/base/" + oid ] = oid
302
+ }
303
+
304
+ delete (list .Bundles , keys [i ])
305
+ }
306
+
307
+ // TODO: Use Git to determine which OIDs are "maximal" in the set
308
+ // and which are not implied by the previous ones.
309
+
310
+ // TODO: Use Git to determine which OIDs are required as prerequisites
311
+ // of the remaining bundles and latest ref tips, so we can "GC" the
312
+ // branches that were never merged and may have been force-pushed or
313
+ // deleted.
314
+
315
+ bundle := Bundle {
316
+ CreationToken : maxTimestamp ,
317
+ Filename : fmt .Sprintf ("%s/base-%d.bundle" , repo .WebDir , maxTimestamp ),
318
+ URI : fmt .Sprintf ("./base-%d.bundle" , maxTimestamp ),
319
+ }
320
+
321
+ err := git .CreateBundleFromRefs (repo .RepoDir , bundle .Filename , refs )
322
+ if err != nil {
323
+ return err
324
+ }
325
+
326
+ list .Bundles [maxTimestamp ] = bundle
327
+ return nil
328
+ }
329
+
330
+ func GetSortedCreationTokens (list * BundleList ) []int64 {
331
+ keys := make ([]int64 , 0 , len (list .Bundles ))
332
+ for timestamp := range list .Bundles {
333
+ keys = append (keys , timestamp )
334
+ }
335
+
336
+ sort .Slice (keys , func (i , j int ) bool { return keys [i ] < keys [j ] })
337
+
338
+ return keys
339
+ }
0 commit comments