Skip to content

Commit 501f64f

Browse files
committed
update: collapse list if it grows longer than 5 items
This constant of "5" will one day be replaced with a configurable value whose default is probably "30". It has other issues when we add the concept of "daily" and "hourly" bundles. For now, 5 is sufficient. Convert the oldest bundles together into a single bundle, now called "base-<token>.bundle". We need to be careful when defining the refs to use for each of these tips, so create a new 'refs/base/' ref namespace. Some TODOs are marked to use 'git merge-base --independent' to reduce the number of tips used by this base bundle. We need to be careful, but it is possible that we could also "GC" the base bundle if the tip commits are not reachable from any tips in the later bundles. In such a case, we might need to rebuild all of those bundles to ensure that the REF_DELTA bases exist as needed (perhaps this is part of the prereq commits, so it might not be a real worry).
1 parent f9fe205 commit 501f64f

File tree

4 files changed

+147
-37
lines changed

4 files changed

+147
-37
lines changed

cmd/git-bundle-server/init.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ func (Init) run(args []string) error {
3535
bundle := bundles.CreateInitialBundle(repo)
3636
fmt.Printf("Constructing base bundle file at %s\n", bundle.Filename)
3737

38-
written, gitErr := git.CreateBundle(repo, bundle)
38+
written, gitErr := git.CreateBundle(repo, bundle.Filename)
3939
if gitErr != nil {
4040
return fmt.Errorf("failed to create bundle: %w", gitErr)
4141
}
4242
if !written {
4343
return fmt.Errorf("refused to write empty bundle. Is the repo empty?")
4444
}
4545

46-
list := bundles.SingletonList(bundle)
46+
list := bundles.CreateSingletonList(bundle)
4747
listErr := bundles.WriteBundleList(list, repo)
4848
if listErr != nil {
4949
return fmt.Errorf("failed to write bundle list: %w", listErr)

cmd/git-bundle-server/update.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"git-bundle-server/internal/bundles"
77
"git-bundle-server/internal/core"
8-
"git-bundle-server/internal/git"
98
)
109

1110
type Update struct{}
@@ -28,24 +27,27 @@ func (Update) run(args []string) error {
2827
return fmt.Errorf("failed to load bundle list: %w", err)
2928
}
3029

31-
bundle := bundles.CreateDistinctBundle(repo, *list)
32-
33-
fmt.Printf("Constructing incremental bundle file at %s\n", bundle.Filename)
34-
35-
written, err := git.CreateIncrementalBundle(repo, bundle, *list)
30+
fmt.Printf("Creating new incremental bundle\n")
31+
bundle, err := bundles.CreateIncrementalBundle(repo, list)
3632
if err != nil {
37-
return fmt.Errorf("failed to create incremental bundle: %w", err)
33+
return err
3834
}
3935

40-
// Nothing to update
41-
if !written {
36+
// Nothing new!
37+
if bundle == nil {
4238
return nil
4339
}
4440

45-
list.Bundles[bundle.CreationToken] = bundle
41+
list.Bundles[bundle.CreationToken] = *bundle
42+
43+
fmt.Printf("Collapsing bundle list\n")
44+
err = bundles.CollapseList(repo, list)
45+
if err != nil {
46+
return err
47+
}
4648

4749
fmt.Printf("Writing updated bundle list\n")
48-
listErr := bundles.WriteBundleList(*list, repo)
50+
listErr := bundles.WriteBundleList(list, repo)
4951
if listErr != nil {
5052
return fmt.Errorf("failed to write bundle list: %w", listErr)
5153
}

internal/bundles/bundles.go

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"encoding/json"
66
"fmt"
77
"git-bundle-server/internal/core"
8+
"git-bundle-server/internal/git"
89
"os"
10+
"sort"
911
"strconv"
1012
"strings"
1113
"time"
@@ -34,7 +36,7 @@ type BundleList struct {
3436
Bundles map[int64]Bundle
3537
}
3638

37-
func addBundleToList(bundle Bundle, list BundleList) {
39+
func addBundleToList(bundle Bundle, list *BundleList) {
3840
list.Bundles[bundle.CreationToken] = bundle
3941
}
4042

@@ -51,14 +53,14 @@ func CreateInitialBundle(repo core.Repository) Bundle {
5153
return bundle
5254
}
5355

54-
func CreateDistinctBundle(repo core.Repository, list BundleList) Bundle {
56+
func CreateDistinctBundle(repo core.Repository, list *BundleList) Bundle {
5557
timestamp := time.Now().UTC().Unix()
5658

57-
_, c := list.Bundles[timestamp]
59+
keys := GetSortedCreationTokens(list)
5860

59-
for c {
60-
timestamp++
61-
_, c = list.Bundles[timestamp]
61+
maxTimestamp := keys[len(keys)-1]
62+
if timestamp <= maxTimestamp {
63+
timestamp = maxTimestamp + 1
6264
}
6365

6466
bundleName := "bundle-" + fmt.Sprint(timestamp) + ".bundle"
@@ -72,16 +74,16 @@ func CreateDistinctBundle(repo core.Repository, list BundleList) Bundle {
7274
return bundle
7375
}
7476

75-
func SingletonList(bundle Bundle) BundleList {
77+
func CreateSingletonList(bundle Bundle) *BundleList {
7678
list := BundleList{1, "all", make(map[int64]Bundle)}
7779

78-
addBundleToList(bundle, list)
80+
addBundleToList(bundle, &list)
7981

80-
return list
82+
return &list
8183
}
8284

8385
// Given a BundleList
84-
func WriteBundleList(list BundleList, repo core.Repository) error {
86+
func WriteBundleList(list *BundleList, repo core.Repository) error {
8587
listFile := repo.WebDir + "/bundle-list"
8688
jsonFile := repo.RepoDir + "/bundle-list.json"
8789

@@ -97,7 +99,10 @@ func WriteBundleList(list BundleList, repo core.Repository) error {
9799
out, "[bundle]\n\tversion = %d\n\tmode = %s\n\n",
98100
list.Version, list.Mode)
99101

100-
for token, bundle := range list.Bundles {
102+
keys := GetSortedCreationTokens(list)
103+
104+
for _, token := range keys {
105+
bundle := list.Bundles[token]
101106
fmt.Fprintf(
102107
out, "[bundle \"%d\"]\n\turi = %s\n\tcreationToken = %d\n\n",
103108
token, bundle.URI, token)
@@ -226,7 +231,7 @@ func GetBundleHeader(bundle Bundle) (*BundleHeader, error) {
226231
return &header, nil
227232
}
228233

229-
func GetAllPrereqsForIncrementalBundle(list BundleList) ([]string, error) {
234+
func GetAllPrereqsForIncrementalBundle(list *BundleList) ([]string, error) {
230235
prereqs := []string{}
231236

232237
for _, bundle := range list.Bundles {
@@ -242,3 +247,93 @@ func GetAllPrereqsForIncrementalBundle(list BundleList) ([]string, error) {
242247

243248
return prereqs, nil
244249
}
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, 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, 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+
}

internal/git/git.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package git
33
import (
44
"bytes"
55
"fmt"
6-
"git-bundle-server/internal/bundles"
76
"git-bundle-server/internal/core"
87
"os"
98
"os/exec"
@@ -67,10 +66,10 @@ func GitCommandWithStdin(stdinLines []string, args ...string) error {
6766
return err
6867
}
6968

70-
func CreateBundle(repo core.Repository, bundle bundles.Bundle) (bool, error) {
69+
func CreateBundle(repo core.Repository, filename string) (bool, error) {
7170
err := GitCommand(
7271
"-C", repo.RepoDir, "bundle", "create",
73-
bundle.Filename, "--all")
72+
filename, "--all")
7473
if err != nil {
7574
if strings.Contains(err.Error(), "Refusing to create empty bundle") {
7675
return false, nil
@@ -81,19 +80,33 @@ func CreateBundle(repo core.Repository, bundle bundles.Bundle) (bool, error) {
8180
return true, nil
8281
}
8382

84-
func CreateIncrementalBundle(repo core.Repository, bundle bundles.Bundle, list bundles.BundleList) (bool, error) {
85-
lines, err := bundles.GetAllPrereqsForIncrementalBundle(list)
86-
if err != nil {
87-
return false, err
83+
func CreateBundleFromRefs(repo core.Repository, filename string, refs map[string]string) error {
84+
refNames := []string{}
85+
86+
for ref, oid := range refs {
87+
err := GitCommand("-C", repo.RepoDir, "branch", "-f", ref, oid)
88+
if err != nil {
89+
return fmt.Errorf("failed to create ref %s: %w", ref, err)
90+
}
91+
92+
refNames = append(refNames, ref)
8893
}
8994

90-
for _, line := range lines {
91-
fmt.Printf("Sending prereq: %s\n", line)
95+
err := GitCommandWithStdin(
96+
refNames,
97+
"-C", repo.RepoDir, "bundle", "create",
98+
filename, "--stdin")
99+
if err != nil {
100+
return err
92101
}
93102

94-
err = GitCommandWithStdin(
95-
lines, "-C", repo.RepoDir, "bundle", "create",
96-
bundle.Filename, "--stdin", "--all")
103+
return nil
104+
}
105+
106+
func CreateIncrementalBundle(repo core.Repository, filename string, prereqs []string) (bool, error) {
107+
err := GitCommandWithStdin(
108+
prereqs, "-C", repo.RepoDir, "bundle", "create",
109+
filename, "--stdin", "--all")
97110
if err != nil {
98111
if strings.Contains(err.Error(), "Refusing to create empty bundle") {
99112
return false, nil

0 commit comments

Comments
 (0)