Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Steinhardt <psteinhardt@gitlab.com>2023-01-18 15:59:24 +0300
committerPatrick Steinhardt <psteinhardt@gitlab.com>2023-01-18 15:59:24 +0300
commit39bb9d29c7fbc9248a351b011874b747ced03826 (patch)
treedf9b7694bf195d7be9e40899a874f321fe9010d8
parentd140b0fe949ae58efcdc1cf492d4046650fbb9b4 (diff)
parentab8b4a46a2e3bcaeae9bde283af43f325eb5f832 (diff)
Merge branch 'pks-housekeeping-multi-pack-index-infrastructure' into 'master'
housekeeping: Add infrastructure for multi-pack indices See merge request https://gitlab.com/gitlab-org/gitaly/-/merge_requests/5266 Merged-by: Patrick Steinhardt <psteinhardt@gitlab.com> Approved-by: karthik nayak <knayak@gitlab.com> Reviewed-by: Patrick Steinhardt <psteinhardt@gitlab.com> Reviewed-by: Quang-Minh Nguyen <qmnguyen@gitlab.com> Reviewed-by: karthik nayak <knayak@gitlab.com>
-rw-r--r--internal/git/housekeeping/objects.go11
-rw-r--r--internal/git/housekeeping/objects_test.go294
-rw-r--r--internal/git/housekeeping/optimize_repository_test.go23
-rw-r--r--internal/git/housekeeping/testhelper_test.go26
-rw-r--r--internal/git/stats/repository_info.go41
-rw-r--r--internal/git/stats/repository_info_test.go38
-rw-r--r--internal/git/version.go10
-rw-r--r--internal/git/version_test.go18
8 files changed, 397 insertions, 64 deletions
diff --git a/internal/git/housekeeping/objects.go b/internal/git/housekeeping/objects.go
index 398401315..54b474d6f 100644
--- a/internal/git/housekeeping/objects.go
+++ b/internal/git/housekeeping/objects.go
@@ -6,6 +6,7 @@ import (
"gitlab.com/gitlab-org/gitaly/v15/internal/git"
"gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo"
"gitlab.com/gitlab-org/gitaly/v15/internal/git/repository"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/structerr"
)
const (
@@ -24,11 +25,17 @@ type RepackObjectsConfig struct {
// reason to set this to `false`, except for legacy compatibility reasons with existing RPC
// behaviour
WriteBitmap bool
+ // WriteMultiPackIndex determines whether a multi-pack index should be written or not.
+ WriteMultiPackIndex bool
}
// RepackObjects repacks objects in the given repository and updates the commit-graph. The way
// objects are repacked is determined via the RepackObjectsConfig.
func RepackObjects(ctx context.Context, repo *localrepo.Repo, cfg RepackObjectsConfig) error {
+ if !cfg.FullRepack && !cfg.WriteMultiPackIndex && cfg.WriteBitmap {
+ return structerr.NewInvalidArgument("cannot write packfile bitmap for an incremental repack")
+ }
+
var options []git.Option
if cfg.FullRepack {
options = append(options,
@@ -38,6 +45,10 @@ func RepackObjects(ctx context.Context, repo *localrepo.Repo, cfg RepackObjectsC
)
}
+ if cfg.WriteMultiPackIndex {
+ options = append(options, git.Flag{Name: "--write-midx"})
+ }
+
if err := repo.ExecAndWait(ctx, git.Command{
Name: "repack",
Flags: append([]git.Option{
diff --git a/internal/git/housekeeping/objects_test.go b/internal/git/housekeeping/objects_test.go
index 9076f3a14..5d0ba1622 100644
--- a/internal/git/housekeeping/objects_test.go
+++ b/internal/git/housekeeping/objects_test.go
@@ -7,54 +7,284 @@ import (
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest"
"gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo"
- "gitlab.com/gitlab-org/gitaly/v15/internal/git/stats"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/structerr"
"gitlab.com/gitlab-org/gitaly/v15/internal/testhelper"
"gitlab.com/gitlab-org/gitaly/v15/internal/testhelper/testcfg"
)
-func requireObjectCount(t *testing.T, repo *localrepo.Repo, expectedObjects uint64) {
- t.Helper()
-
- objects, err := stats.LooseObjects(repo)
- require.NoError(t, err)
- require.Equal(t, expectedObjects, objects)
-}
-
-func requirePackfileCount(t *testing.T, repo *localrepo.Repo, expectedPackfiles uint64) {
- t.Helper()
-
- packfiles, err := stats.PackfilesCount(repo)
- require.NoError(t, err)
- require.Equal(t, expectedPackfiles, packfiles)
-}
-
func TestRepackObjects(t *testing.T) {
t.Parallel()
ctx := testhelper.Context(t)
cfg := testcfg.Build(t)
- t.Run("no server info is written", func(t *testing.T) {
- t.Parallel()
+ gitVersion, err := gittest.NewCommandFactory(t, cfg).GitVersion(ctx)
+ require.NoError(t, err)
- repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{
- SkipCreationViaService: true,
- })
- repo := localrepo.NewTestRepo(t, cfg, repoProto)
+ // repack is a custom helper function that repacks while explicitly disabling the update of
+ // server info. This is done so that we assert that the actual repacking logic doesn't write
+ // the server info.
+ repack := func(t *testing.T, repoPath string, args ...string) {
+ gittest.Exec(t, cfg, append([]string{
+ "-c", "repack.updateServerInfo=false", "-C", repoPath, "repack",
+ }, args...)...)
+ }
- gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("main"))
+ for _, tc := range []struct {
+ desc string
+ setup func(t *testing.T, repoPath string)
+ repackCfg RepackObjectsConfig
+ stateBeforeRepack objectsState
+ stateAfterRepack objectsState
+ expectedErr error
+ }{
+ {
+ desc: "incremental repack packs objects",
+ setup: func(t *testing.T, repoPath string) {
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("main"))
+ },
+ stateBeforeRepack: objectsState{
+ looseObjects: 2,
+ },
+ stateAfterRepack: objectsState{
+ packfiles: 1,
+ },
+ },
+ {
+ desc: "incremental repack does not repack packfiles",
+ setup: func(t *testing.T, repoPath string) {
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("first"), gittest.WithBranch("first"))
+ repack(t, repoPath, "-d")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("second"), gittest.WithBranch("second"))
+ repack(t, repoPath, "-d")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("third"), gittest.WithBranch("third"))
+ },
+ stateBeforeRepack: objectsState{
+ packfiles: 2,
+ looseObjects: 1,
+ },
+ stateAfterRepack: objectsState{
+ packfiles: 3,
+ },
+ },
+ {
+ desc: "incremental repack with bitmap returns an error",
+ setup: func(t *testing.T, repoPath string) {
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("first"), gittest.WithBranch("first"))
+ },
+ repackCfg: RepackObjectsConfig{
+ WriteBitmap: true,
+ },
+ stateBeforeRepack: objectsState{
+ looseObjects: 2,
+ },
+ stateAfterRepack: objectsState{
+ looseObjects: 2,
+ },
+ expectedErr: structerr.NewInvalidArgument("cannot write packfile bitmap for an incremental repack"),
+ },
+ {
+ desc: "full repack packs packfiles and loose objects",
+ setup: func(t *testing.T, repoPath string) {
+ // We seed the repository so that it contains two packfiles and one loose object.
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("first"), gittest.WithBranch("first"))
+ repack(t, repoPath, "-d")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("second"), gittest.WithBranch("second"))
+ repack(t, repoPath, "-d")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("third"), gittest.WithBranch("third"))
+ },
+ repackCfg: RepackObjectsConfig{
+ FullRepack: true,
+ },
+ stateBeforeRepack: objectsState{
+ looseObjects: 1,
+ packfiles: 2,
+ },
+ stateAfterRepack: objectsState{
+ packfiles: 1,
+ },
+ },
+ {
+ desc: "full repack packs packfiles and loose objects and writes a bitmap",
+ setup: func(t *testing.T, repoPath string) {
+ // We seed the repository so that it contains two packfiles and one loose object.
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("first"), gittest.WithBranch("first"))
+ repack(t, repoPath, "-d")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("second"), gittest.WithBranch("second"))
+ repack(t, repoPath, "-d")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("third"), gittest.WithBranch("third"))
+ },
+ repackCfg: RepackObjectsConfig{
+ FullRepack: true,
+ WriteBitmap: true,
+ },
+ stateBeforeRepack: objectsState{
+ looseObjects: 1,
+ packfiles: 2,
+ },
+ stateAfterRepack: objectsState{
+ packfiles: 1,
+ hasBitmap: true,
+ },
+ },
+ {
+ desc: "multi-pack-index with incremental repack",
+ setup: func(t *testing.T, repoPath string) {
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("master"))
+ },
+ repackCfg: RepackObjectsConfig{
+ WriteMultiPackIndex: true,
+ },
+ stateBeforeRepack: objectsState{
+ looseObjects: 2,
+ },
+ stateAfterRepack: objectsState{
+ packfiles: 1,
+ hasMultiPackIndex: true,
+ },
+ },
+ {
+ desc: "multi-pack-index allows incremental repacks with bitmaps",
+ setup: func(t *testing.T, repoPath string) {
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("master"))
+ },
+ repackCfg: RepackObjectsConfig{
+ WriteMultiPackIndex: true,
+ WriteBitmap: true,
+ },
+ stateBeforeRepack: objectsState{
+ looseObjects: 2,
+ },
+ stateAfterRepack: objectsState{
+ packfiles: 1,
+ hasMultiPackIndex: true,
+ hasMultiPackIndexBitmap: true,
+ },
+ },
+ {
+ desc: "multi-pack-index with full repack packs packfiles and loose objects",
+ setup: func(t *testing.T, repoPath string) {
+ // We seed the repository so that it contains two packfiles and one loose object.
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("first"), gittest.WithBranch("first"))
+ repack(t, repoPath, "-d")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("second"), gittest.WithBranch("second"))
+ repack(t, repoPath, "-d")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("third"), gittest.WithBranch("third"))
+ },
+ repackCfg: RepackObjectsConfig{
+ FullRepack: true,
+ WriteMultiPackIndex: true,
+ },
+ stateBeforeRepack: objectsState{
+ looseObjects: 1,
+ packfiles: 2,
+ },
+ stateAfterRepack: objectsState{
+ packfiles: 1,
+ hasMultiPackIndex: true,
+ },
+ },
+ {
+ desc: "multi-pack-index with full repack and bitmap writes bitmap",
+ setup: func(t *testing.T, repoPath string) {
+ // We seed the repository so that it contains two packfiles and one loose object.
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("first"), gittest.WithBranch("first"))
+ repack(t, repoPath, "-d")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("second"), gittest.WithBranch("second"))
+ repack(t, repoPath, "-d")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("third"), gittest.WithBranch("third"))
+ },
+ repackCfg: RepackObjectsConfig{
+ FullRepack: true,
+ WriteBitmap: true,
+ WriteMultiPackIndex: true,
+ },
+ stateBeforeRepack: objectsState{
+ looseObjects: 1,
+ packfiles: 2,
+ },
+ stateAfterRepack: objectsState{
+ packfiles: 1,
+ hasMultiPackIndex: true,
+ hasMultiPackIndexBitmap: true,
+ },
+ },
+ {
+ desc: "multi-pack-index with incremental repack removes preexisting bitmaps with newish Git",
+ setup: func(t *testing.T, repoPath string) {
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("first"), gittest.WithBranch("first"))
+ repack(t, repoPath, "-A", "-d", "-b")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("second"), gittest.WithBranch("second"))
+ },
+ repackCfg: RepackObjectsConfig{
+ WriteBitmap: true,
+ WriteMultiPackIndex: true,
+ },
+ stateBeforeRepack: objectsState{
+ looseObjects: 1,
+ packfiles: 1,
+ hasBitmap: true,
+ },
+ stateAfterRepack: objectsState{
+ packfiles: 2,
+ // Git v2.38.0 does not yet remove redundant pack-based bitmaps.
+ // This is getting fixed via 55d902cd61 (builtin/repack.c: remove
+ // redundant pack-based bitmaps, 2022-10-17), which is part of Git
+ // v2.39.0 and newer.
+ //
+ // Local tests don't show that this is a problem. Most importantly,
+ // Git does not seem to warn about these bitmaps. So let's just
+ // ignore them for now.
+ hasBitmap: !gitVersion.MidxDeletesRedundantBitmaps(),
+ hasMultiPackIndex: true,
+ hasMultiPackIndexBitmap: true,
+ },
+ },
+ {
+ desc: "multi-pack-index with full repack removes preexisting bitmaps",
+ setup: func(t *testing.T, repoPath string) {
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("first"), gittest.WithBranch("first"))
+ repack(t, repoPath, "-A", "-d", "-b")
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithMessage("second"), gittest.WithBranch("second"))
+ repack(t, repoPath, "-d")
+ },
+ repackCfg: RepackObjectsConfig{
+ FullRepack: true,
+ WriteBitmap: true,
+ WriteMultiPackIndex: true,
+ },
+ stateBeforeRepack: objectsState{
+ packfiles: 2,
+ hasBitmap: true,
+ },
+ stateAfterRepack: objectsState{
+ packfiles: 1,
+ hasMultiPackIndex: true,
+ hasMultiPackIndexBitmap: true,
+ },
+ },
+ } {
+ tc := tc
- requireObjectCount(t, repo, 2)
- requirePackfileCount(t, repo, 0)
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
- require.NoError(t, RepackObjects(ctx, repo, RepackObjectsConfig{}))
+ repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{
+ SkipCreationViaService: true,
+ })
+ repo := localrepo.NewTestRepo(t, cfg, repoProto)
- requireObjectCount(t, repo, 0)
- requirePackfileCount(t, repo, 1)
+ tc.setup(t, repoPath)
- require.NoFileExists(t, filepath.Join(repoPath, "info", "refs"))
- require.NoFileExists(t, filepath.Join(repoPath, "objects", "info", "packs"))
- })
+ requireObjectsState(t, repo, tc.stateBeforeRepack)
+ require.Equal(t, tc.expectedErr, RepackObjects(ctx, repo, tc.repackCfg))
+ requireObjectsState(t, repo, tc.stateAfterRepack)
+
+ // There should not be any server info data in the repository.
+ require.NoFileExists(t, filepath.Join(repoPath, "info", "refs"))
+ require.NoFileExists(t, filepath.Join(repoPath, "objects", "info", "packs"))
+ })
+ }
testRepoAndPool(t, "delta islands", func(t *testing.T, relativePath string) {
t.Parallel()
diff --git a/internal/git/housekeeping/optimize_repository_test.go b/internal/git/housekeeping/optimize_repository_test.go
index b2caef466..13ddc07a3 100644
--- a/internal/git/housekeeping/optimize_repository_test.go
+++ b/internal/git/housekeeping/optimize_repository_test.go
@@ -30,16 +30,6 @@ func TestRepackIfNeeded(t *testing.T) {
ctx := testhelper.Context(t)
cfg := testcfg.Build(t)
- requirePackfilesAndLooseObjects := func(t *testing.T, repo *localrepo.Repo, expectedPackfiles, expectedLooseObjects uint64) {
- t.Helper()
-
- info, err := stats.RepositoryInfoForRepository(repo)
- require.NoError(t, err)
-
- require.Equal(t, expectedPackfiles, info.Packfiles.Count)
- require.Equal(t, expectedLooseObjects, info.LooseObjects.Count)
- }
-
t.Run("no repacking", func(t *testing.T) {
repoProto, repoPath := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{
SkipCreationViaService: true,
@@ -56,7 +46,9 @@ func TestRepackIfNeeded(t *testing.T) {
require.False(t, didRepack)
require.Equal(t, RepackObjectsConfig{}, repackObjectsCfg)
- requirePackfilesAndLooseObjects(t, repo, 0, 2)
+ requireObjectsState(t, repo, objectsState{
+ looseObjects: 2,
+ })
})
t.Run("incremental repack", func(t *testing.T) {
@@ -79,7 +71,10 @@ func TestRepackIfNeeded(t *testing.T) {
require.True(t, didRepack)
require.Equal(t, RepackObjectsConfig{}, repackObjectsCfg)
- requirePackfilesAndLooseObjects(t, repo, 2, 0)
+ requireObjectsState(t, repo, objectsState{
+ packfiles: 2,
+ hasBitmap: true,
+ })
})
t.Run("full repack", func(t *testing.T) {
@@ -107,7 +102,9 @@ func TestRepackIfNeeded(t *testing.T) {
FullRepack: true,
}, repackObjectsCfg)
- requirePackfilesAndLooseObjects(t, repo, 1, 0)
+ requireObjectsState(t, repo, objectsState{
+ packfiles: 1,
+ })
})
}
diff --git a/internal/git/housekeeping/testhelper_test.go b/internal/git/housekeeping/testhelper_test.go
index 49499923f..b6faaa265 100644
--- a/internal/git/housekeeping/testhelper_test.go
+++ b/internal/git/housekeeping/testhelper_test.go
@@ -3,7 +3,10 @@ package housekeeping
import (
"testing"
+ "github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo"
+ "gitlab.com/gitlab-org/gitaly/v15/internal/git/stats"
"gitlab.com/gitlab-org/gitaly/v15/internal/testhelper"
)
@@ -23,3 +26,26 @@ func testRepoAndPool(t *testing.T, desc string, testFunc func(t *testing.T, rela
})
})
}
+
+type objectsState struct {
+ looseObjects uint64
+ packfiles uint64
+ hasBitmap bool
+ hasMultiPackIndex bool
+ hasMultiPackIndexBitmap bool
+}
+
+func requireObjectsState(tb testing.TB, repo *localrepo.Repo, expectedState objectsState) {
+ tb.Helper()
+
+ repoInfo, err := stats.RepositoryInfoForRepository(repo)
+ require.NoError(tb, err)
+
+ require.Equal(tb, expectedState, objectsState{
+ looseObjects: repoInfo.LooseObjects.Count,
+ packfiles: repoInfo.Packfiles.Count,
+ hasBitmap: repoInfo.Packfiles.HasBitmap,
+ hasMultiPackIndex: repoInfo.Packfiles.HasMultiPackIndex,
+ hasMultiPackIndexBitmap: repoInfo.Packfiles.HasMultiPackIndexBitmap,
+ })
+}
diff --git a/internal/git/stats/repository_info.go b/internal/git/stats/repository_info.go
index 9885ec895..363b267bd 100644
--- a/internal/git/stats/repository_info.go
+++ b/internal/git/stats/repository_info.go
@@ -240,6 +240,10 @@ type PackfilesInfo struct {
GarbageSize uint64 `json:"garbage_size"`
// HasBitmap indicates whether the packfiles have a bitmap.
HasBitmap bool `json:"has_bitmap"`
+ // HasMultiPackIndex indicates whether there is a multi-pack-index.
+ HasMultiPackIndex bool `json:"has_multi_pack_index"`
+ // HasMultiPackIndexBitmap indicates whether the multi-pack-index has a bitmap.
+ HasMultiPackIndexBitmap bool `json:"has_multi_pack_index_bitmap"`
}
// PackfilesInfoForRepository derives various information about packfiles for the given repository.
@@ -269,11 +273,28 @@ func PackfilesInfoForRepository(repo *localrepo.Repo) (PackfilesInfo, error) {
return PackfilesInfo{}, fmt.Errorf("getting packfile info: %w", err)
}
- // We're overly lenient here and only verify for known prefixes. This would already
- // catch things like temporary packfiles, but it wouldn't catch other bogus files.
- // This is on purpose though because Git has grown more and more metadata-style file
- // formats, and we don't want to copy the list here.
- if !strings.HasPrefix(entry.Name(), "pack-") {
+ entryName := entry.Name()
+
+ switch {
+ case strings.HasPrefix(entryName, "pack-"):
+ // We're overly lenient here and only verify packfiles for known suffixes.
+ // As a consequence, we don't catch garbage files here. This is on purpose
+ // though because Git has grown more and more metadata-style file formats,
+ // and we don't want to copy the list here.
+ switch {
+ case strings.HasSuffix(entryName, ".pack"):
+ info.Count++
+ if entryInfo.Size() > 0 {
+ info.Size += uint64(entryInfo.Size())
+ }
+ case strings.HasSuffix(entryName, ".bitmap"):
+ info.HasBitmap = true
+ }
+ case entryName == "multi-pack-index":
+ info.HasMultiPackIndex = true
+ case strings.HasPrefix(entryName, "multi-pack-index-") && strings.HasSuffix(entryName, ".bitmap"):
+ info.HasMultiPackIndexBitmap = true
+ default:
info.GarbageCount++
if entryInfo.Size() > 0 {
info.GarbageSize += uint64(entryInfo.Size())
@@ -281,16 +302,6 @@ func PackfilesInfoForRepository(repo *localrepo.Repo) (PackfilesInfo, error) {
continue
}
-
- switch {
- case strings.HasSuffix(entry.Name(), ".pack"):
- info.Count++
- if entryInfo.Size() > 0 {
- info.Size += uint64(entryInfo.Size())
- }
- case strings.HasSuffix(entry.Name(), ".bitmap"):
- info.HasBitmap = true
- }
}
return info, nil
diff --git a/internal/git/stats/repository_info_test.go b/internal/git/stats/repository_info_test.go
index 8fa25c6fe..1c2be2ead 100644
--- a/internal/git/stats/repository_info_test.go
+++ b/internal/git/stats/repository_info_test.go
@@ -737,6 +737,32 @@ func TestPackfileInfoForRepository(t *testing.T) {
})
})
+ t.Run("multi-pack-index", func(t *testing.T) {
+ repo, repoPath := createRepo(t)
+
+ packfileDir := filepath.Join(repoPath, "objects", "pack")
+ require.NoError(t, os.MkdirAll(packfileDir, 0o755))
+ require.NoError(t, os.WriteFile(filepath.Join(packfileDir, "multi-pack-index"), nil, 0o644))
+
+ requirePackfilesInfo(t, repo, PackfilesInfo{
+ HasMultiPackIndex: true,
+ })
+ })
+
+ t.Run("multi-pack-index with bitmap", func(t *testing.T) {
+ repo, repoPath := createRepo(t)
+
+ packfileDir := filepath.Join(repoPath, "objects", "pack")
+ require.NoError(t, os.MkdirAll(packfileDir, 0o755))
+ require.NoError(t, os.WriteFile(filepath.Join(packfileDir, "multi-pack-index"), nil, 0o644))
+ require.NoError(t, os.WriteFile(filepath.Join(packfileDir, "multi-pack-index-c0363841cc7e5783a996c72f0a4a7ae4440aaa40.bitmap"), nil, 0o644))
+
+ requirePackfilesInfo(t, repo, PackfilesInfo{
+ HasMultiPackIndex: true,
+ HasMultiPackIndexBitmap: true,
+ })
+ })
+
t.Run("multiple packfiles with other data structures", func(t *testing.T) {
repo, repoPath := createRepo(t)
@@ -750,15 +776,19 @@ func TestPackfileInfoForRepository(t *testing.T) {
"pack-foo.pack",
"pack-foo.idx",
"garbage",
+ "multi-pack-index",
+ "multi-pack-index-c0363841cc7e5783a996c72f0a4a7ae4440aaa40.bitmap",
} {
require.NoError(t, os.WriteFile(filepath.Join(packfileDir, file), []byte("1"), 0o644))
}
requirePackfilesInfo(t, repo, PackfilesInfo{
- Count: 2,
- Size: 2,
- GarbageCount: 1,
- GarbageSize: 1,
+ Count: 2,
+ Size: 2,
+ GarbageCount: 1,
+ GarbageSize: 1,
+ HasMultiPackIndex: true,
+ HasMultiPackIndexBitmap: true,
})
})
}
diff --git a/internal/git/version.go b/internal/git/version.go
index ee44ddd32..2d13c1187 100644
--- a/internal/git/version.go
+++ b/internal/git/version.go
@@ -82,6 +82,16 @@ func (v Version) PatchIDRespectsBinaries() bool {
})
}
+// MidxDeletesRedundantBitmaps detects whether the given Git version deletes redundant pack-based
+// bitmaps when writing multi-pack-indices. This feature has been added via 55d902cd61
+// (builtin/repack.c: remove redundant pack-based bitmaps, 2022-10-17), which is part of Git
+// v2.39.0 and newer.
+func (v Version) MidxDeletesRedundantBitmaps() bool {
+ return !v.LessThan(Version{
+ major: 2, minor: 39, patch: 0,
+ })
+}
+
// LessThan determines whether the version is older than another version.
func (v Version) LessThan(other Version) bool {
switch {
diff --git a/internal/git/version_test.go b/internal/git/version_test.go
index ea0777696..93f2e4eef 100644
--- a/internal/git/version_test.go
+++ b/internal/git/version_test.go
@@ -142,3 +142,21 @@ func TestVersion_PatchIDRespectsBinaries(t *testing.T) {
})
}
}
+
+func TestVersion_MidxDeletesRedundantBitmaps(t *testing.T) {
+ for _, tc := range []struct {
+ version string
+ expect bool
+ }{
+ {"1.0.0", false},
+ {"2.38.2", false},
+ {"2.39.0", true},
+ {"3.0.0", true},
+ } {
+ t.Run(tc.version, func(t *testing.T) {
+ version, err := parseVersion(tc.version)
+ require.NoError(t, err)
+ require.Equal(t, tc.expect, version.MidxDeletesRedundantBitmaps())
+ })
+ }
+}