mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-25 15:26:17 +02:00
283 lines
9.7 KiB
Diff
283 lines
9.7 KiB
Diff
|
commit 9bacade4a3ef4b6b26e2c02f549eef0e9eb9eaa2
|
||
|
Author: Robert Hensing <robert@roberthensing.nl>
|
||
|
Date: Sun Aug 18 20:20:36 2024 +0200
|
||
|
|
||
|
Add unoptimized git_mempack_write_thin_pack
|
||
|
|
||
|
diff --git a/include/git2/sys/mempack.h b/include/git2/sys/mempack.h
|
||
|
index 17da590a3..3688bdd50 100644
|
||
|
--- a/include/git2/sys/mempack.h
|
||
|
+++ b/include/git2/sys/mempack.h
|
||
|
@@ -44,6 +44,29 @@ GIT_BEGIN_DECL
|
||
|
*/
|
||
|
GIT_EXTERN(int) git_mempack_new(git_odb_backend **out);
|
||
|
|
||
|
+/**
|
||
|
+ * Write a thin packfile with the objects in the memory store.
|
||
|
+ *
|
||
|
+ * A thin packfile is a packfile that does not contain its transitive closure of
|
||
|
+ * references. This is useful for efficiently distributing additions to a
|
||
|
+ * repository over the network, but also finds use in the efficient bulk
|
||
|
+ * addition of objects to a repository, locally.
|
||
|
+ *
|
||
|
+ * This operation performs the (shallow) insert operations into the
|
||
|
+ * `git_packbuilder`, but does not write the packfile to disk;
|
||
|
+ * see `git_packbuilder_write_buf`.
|
||
|
+ *
|
||
|
+ * It also does not reset the memory store; see `git_mempack_reset`.
|
||
|
+ *
|
||
|
+ * @note This function may or may not write trees and blobs that are not
|
||
|
+ * referenced by commits. Currently everything is written, but this
|
||
|
+ * behavior may change in the future as the packer is optimized.
|
||
|
+ *
|
||
|
+ * @param backend The mempack backend
|
||
|
+ * @param pb The packbuilder to use to write the packfile
|
||
|
+ */
|
||
|
+GIT_EXTERN(int) git_mempack_write_thin_pack(git_odb_backend *backend, git_packbuilder *pb);
|
||
|
+
|
||
|
/**
|
||
|
* Dump all the queued in-memory writes to a packfile.
|
||
|
*
|
||
|
diff --git a/src/libgit2/odb_mempack.c b/src/libgit2/odb_mempack.c
|
||
|
index 6f27f45f8..0b61e2b66 100644
|
||
|
--- a/src/libgit2/odb_mempack.c
|
||
|
+++ b/src/libgit2/odb_mempack.c
|
||
|
@@ -132,6 +132,35 @@ cleanup:
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
+int git_mempack_write_thin_pack(git_odb_backend *backend, git_packbuilder *pb)
|
||
|
+{
|
||
|
+ struct memory_packer_db *db = (struct memory_packer_db *)backend;
|
||
|
+ const git_oid *oid;
|
||
|
+ size_t iter = 0;
|
||
|
+ int err = -1;
|
||
|
+
|
||
|
+ /* TODO: Implement the recency heuristics.
|
||
|
+ For this it probably makes sense to only write what's referenced
|
||
|
+ through commits, an option I've carved out for you in the docs.
|
||
|
+ wrt heuristics: ask your favorite LLM to translate https://git-scm.com/docs/pack-heuristics/en
|
||
|
+ to actual normal reference documentation. */
|
||
|
+ while (true) {
|
||
|
+ err = git_oidmap_iterate(NULL, db->objects, &iter, &oid);
|
||
|
+ if (err == GIT_ITEROVER) {
|
||
|
+ err = 0;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ if (err != 0)
|
||
|
+ return err;
|
||
|
+
|
||
|
+ err = git_packbuilder_insert(pb, oid, NULL);
|
||
|
+ if (err != 0)
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
int git_mempack_dump(
|
||
|
git_buf *pack,
|
||
|
git_repository *repo,
|
||
|
diff --git a/tests/libgit2/mempack/thinpack.c b/tests/libgit2/mempack/thinpack.c
|
||
|
new file mode 100644
|
||
|
index 000000000..604a4dda2
|
||
|
--- /dev/null
|
||
|
+++ b/tests/libgit2/mempack/thinpack.c
|
||
|
@@ -0,0 +1,196 @@
|
||
|
+#include "clar_libgit2.h"
|
||
|
+#include "git2/indexer.h"
|
||
|
+#include "git2/odb_backend.h"
|
||
|
+#include "git2/tree.h"
|
||
|
+#include "git2/types.h"
|
||
|
+#include "git2/sys/mempack.h"
|
||
|
+#include "git2/sys/odb_backend.h"
|
||
|
+#include "util.h"
|
||
|
+
|
||
|
+static git_repository *_repo;
|
||
|
+static git_odb_backend * _mempack_backend;
|
||
|
+
|
||
|
+void test_mempack_thinpack__initialize(void)
|
||
|
+{
|
||
|
+ git_odb *odb;
|
||
|
+
|
||
|
+ _repo = cl_git_sandbox_init_new("mempack_thinpack_repo");
|
||
|
+
|
||
|
+ cl_git_pass(git_mempack_new(&_mempack_backend));
|
||
|
+ cl_git_pass(git_repository_odb(&odb, _repo));
|
||
|
+ cl_git_pass(git_odb_add_backend(odb, _mempack_backend, 999));
|
||
|
+ git_odb_free(odb);
|
||
|
+}
|
||
|
+
|
||
|
+void _mempack_thinpack__cleanup(void)
|
||
|
+{
|
||
|
+ cl_git_sandbox_cleanup();
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+ Generating a packfile for an unchanged repo works and produces an empty packfile.
|
||
|
+ Even if we allow this scenario to be detected, it shouldn't misbehave if the
|
||
|
+ application is unaware of it.
|
||
|
+*/
|
||
|
+void test_mempack_thinpack__empty(void)
|
||
|
+{
|
||
|
+ git_packbuilder *pb;
|
||
|
+ int version;
|
||
|
+ int n;
|
||
|
+ git_buf buf = GIT_BUF_INIT;
|
||
|
+
|
||
|
+ git_packbuilder_new(&pb, _repo);
|
||
|
+
|
||
|
+ cl_git_pass(git_mempack_write_thin_pack(_mempack_backend, pb));
|
||
|
+ cl_git_pass(git_packbuilder_write_buf(&buf, pb));
|
||
|
+ cl_assert_in_range(12, buf.size, 1024 /* empty packfile is >0 bytes, but certainly not that big */);
|
||
|
+ cl_assert(buf.ptr[0] == 'P');
|
||
|
+ cl_assert(buf.ptr[1] == 'A');
|
||
|
+ cl_assert(buf.ptr[2] == 'C');
|
||
|
+ cl_assert(buf.ptr[3] == 'K');
|
||
|
+ version = (buf.ptr[4] << 24) | (buf.ptr[5] << 16) | (buf.ptr[6] << 8) | buf.ptr[7];
|
||
|
+ /* Subject to change. https://git-scm.com/docs/pack-format: Git currently accepts version number 2 or 3 but generates version 2 only.*/
|
||
|
+ cl_assert_equal_i(2, version);
|
||
|
+ n = (buf.ptr[8] << 24) | (buf.ptr[9] << 16) | (buf.ptr[10] << 8) | buf.ptr[11];
|
||
|
+ cl_assert_equal_i(0, n);
|
||
|
+ git_buf_dispose(&buf);
|
||
|
+
|
||
|
+ git_packbuilder_free(pb);
|
||
|
+}
|
||
|
+
|
||
|
+#define LIT_LEN(x) x, sizeof(x) - 1
|
||
|
+
|
||
|
+/*
|
||
|
+ Check that git_mempack_write_thin_pack produces a thin packfile.
|
||
|
+*/
|
||
|
+void test_mempack_thinpack__thin(void)
|
||
|
+{
|
||
|
+ /* Outline:
|
||
|
+ - Create tree 1
|
||
|
+ - Flush to packfile A
|
||
|
+ - Create tree 2
|
||
|
+ - Flush to packfile B
|
||
|
+
|
||
|
+ Tree 2 has a new blob and a reference to a blob from tree 1.
|
||
|
+
|
||
|
+ Expectation:
|
||
|
+ - Packfile B is thin and does not contain the objects from packfile A
|
||
|
+ */
|
||
|
+
|
||
|
+
|
||
|
+ git_oid oid_blob_1;
|
||
|
+ git_oid oid_blob_2;
|
||
|
+ git_oid oid_blob_3;
|
||
|
+ git_oid oid_tree_1;
|
||
|
+ git_oid oid_tree_2;
|
||
|
+ git_treebuilder *tb;
|
||
|
+
|
||
|
+ git_packbuilder *pb;
|
||
|
+ git_buf buf = GIT_BUF_INIT;
|
||
|
+ git_indexer *indexer;
|
||
|
+ git_indexer_progress stats;
|
||
|
+ char pack_dir_path[1024];
|
||
|
+
|
||
|
+ char sbuf[1024];
|
||
|
+ const char * repo_path;
|
||
|
+ const char * pack_name_1;
|
||
|
+ const char * pack_name_2;
|
||
|
+ git_str pack_path_1 = GIT_STR_INIT;
|
||
|
+ git_str pack_path_2 = GIT_STR_INIT;
|
||
|
+ git_odb_backend * pack_odb_backend_1;
|
||
|
+ git_odb_backend * pack_odb_backend_2;
|
||
|
+
|
||
|
+
|
||
|
+ cl_assert_in_range(0, snprintf(pack_dir_path, sizeof(pack_dir_path), "%s/objects/pack", git_repository_path(_repo)), sizeof(pack_dir_path));
|
||
|
+
|
||
|
+ /* Create tree 1 */
|
||
|
+
|
||
|
+ cl_git_pass(git_blob_create_from_buffer(&oid_blob_1, _repo, LIT_LEN("thinpack blob 1")));
|
||
|
+ cl_git_pass(git_blob_create_from_buffer(&oid_blob_2, _repo, LIT_LEN("thinpack blob 2")));
|
||
|
+
|
||
|
+
|
||
|
+ cl_git_pass(git_treebuilder_new(&tb, _repo, NULL));
|
||
|
+ cl_git_pass(git_treebuilder_insert(NULL, tb, "blob1", &oid_blob_1, GIT_FILEMODE_BLOB));
|
||
|
+ cl_git_pass(git_treebuilder_insert(NULL, tb, "blob2", &oid_blob_2, GIT_FILEMODE_BLOB));
|
||
|
+ cl_git_pass(git_treebuilder_write(&oid_tree_1, tb));
|
||
|
+
|
||
|
+ /* Flush */
|
||
|
+
|
||
|
+ cl_git_pass(git_packbuilder_new(&pb, _repo));
|
||
|
+ cl_git_pass(git_mempack_write_thin_pack(_mempack_backend, pb));
|
||
|
+ cl_git_pass(git_packbuilder_write_buf(&buf, pb));
|
||
|
+ cl_git_pass(git_indexer_new(&indexer, pack_dir_path, 0, NULL, NULL));
|
||
|
+ cl_git_pass(git_indexer_append(indexer, buf.ptr, buf.size, &stats));
|
||
|
+ cl_git_pass(git_indexer_commit(indexer, &stats));
|
||
|
+ pack_name_1 = strdup(git_indexer_name(indexer));
|
||
|
+ cl_assert(pack_name_1);
|
||
|
+ git_buf_dispose(&buf);
|
||
|
+ git_mempack_reset(_mempack_backend);
|
||
|
+ git_indexer_free(indexer);
|
||
|
+ git_packbuilder_free(pb);
|
||
|
+
|
||
|
+ /* Create tree 2 */
|
||
|
+
|
||
|
+ cl_git_pass(git_treebuilder_clear(tb));
|
||
|
+ /* blob 1 won't be used, but we add it anyway to test that just "declaring" an object doesn't
|
||
|
+ necessarily cause its inclusion in the next thin packfile. It must only be included if new. */
|
||
|
+ cl_git_pass(git_blob_create_from_buffer(&oid_blob_1, _repo, LIT_LEN("thinpack blob 1")));
|
||
|
+ cl_git_pass(git_blob_create_from_buffer(&oid_blob_3, _repo, LIT_LEN("thinpack blob 3")));
|
||
|
+ cl_git_pass(git_treebuilder_insert(NULL, tb, "blob1", &oid_blob_1, GIT_FILEMODE_BLOB));
|
||
|
+ cl_git_pass(git_treebuilder_insert(NULL, tb, "blob3", &oid_blob_3, GIT_FILEMODE_BLOB));
|
||
|
+ cl_git_pass(git_treebuilder_write(&oid_tree_2, tb));
|
||
|
+
|
||
|
+ /* Flush */
|
||
|
+
|
||
|
+ cl_git_pass(git_packbuilder_new(&pb, _repo));
|
||
|
+ cl_git_pass(git_mempack_write_thin_pack(_mempack_backend, pb));
|
||
|
+ cl_git_pass(git_packbuilder_write_buf(&buf, pb));
|
||
|
+ cl_git_pass(git_indexer_new(&indexer, pack_dir_path, 0, NULL, NULL));
|
||
|
+ cl_git_pass(git_indexer_append(indexer, buf.ptr, buf.size, &stats));
|
||
|
+ cl_git_pass(git_indexer_commit(indexer, &stats));
|
||
|
+ pack_name_2 = strdup(git_indexer_name(indexer));
|
||
|
+ cl_assert(pack_name_2);
|
||
|
+ git_buf_dispose(&buf);
|
||
|
+ git_mempack_reset(_mempack_backend);
|
||
|
+ git_indexer_free(indexer);
|
||
|
+ git_packbuilder_free(pb);
|
||
|
+ git_treebuilder_free(tb);
|
||
|
+
|
||
|
+ /* Assertions */
|
||
|
+
|
||
|
+ assert(pack_name_1);
|
||
|
+ assert(pack_name_2);
|
||
|
+
|
||
|
+ repo_path = git_repository_path(_repo);
|
||
|
+
|
||
|
+ snprintf(sbuf, sizeof(sbuf), "objects/pack/pack-%s.pack", pack_name_1);
|
||
|
+ git_str_joinpath(&pack_path_1, repo_path, sbuf);
|
||
|
+ snprintf(sbuf, sizeof(sbuf), "objects/pack/pack-%s.pack", pack_name_2);
|
||
|
+ git_str_joinpath(&pack_path_2, repo_path, sbuf);
|
||
|
+
|
||
|
+ /* If they're the same, something definitely went wrong. */
|
||
|
+ cl_assert(strcmp(pack_name_1, pack_name_2) != 0);
|
||
|
+
|
||
|
+ cl_git_pass(git_odb_backend_one_pack(&pack_odb_backend_1, pack_path_1.ptr));
|
||
|
+ cl_assert(pack_odb_backend_1->exists(pack_odb_backend_1, &oid_blob_1));
|
||
|
+ cl_assert(pack_odb_backend_1->exists(pack_odb_backend_1, &oid_blob_2));
|
||
|
+ cl_assert(!pack_odb_backend_1->exists(pack_odb_backend_1, &oid_blob_3));
|
||
|
+ cl_assert(pack_odb_backend_1->exists(pack_odb_backend_1, &oid_tree_1));
|
||
|
+ cl_assert(!pack_odb_backend_1->exists(pack_odb_backend_1, &oid_tree_2));
|
||
|
+
|
||
|
+ cl_git_pass(git_odb_backend_one_pack(&pack_odb_backend_2, pack_path_2.ptr));
|
||
|
+ /* blob 1 is already in the packfile 1, so packfile 2 must not include it, in order to be _thin_. */
|
||
|
+ cl_assert(!pack_odb_backend_2->exists(pack_odb_backend_2, &oid_blob_1));
|
||
|
+ cl_assert(!pack_odb_backend_2->exists(pack_odb_backend_2, &oid_blob_2));
|
||
|
+ cl_assert(pack_odb_backend_2->exists(pack_odb_backend_2, &oid_blob_3));
|
||
|
+ cl_assert(!pack_odb_backend_2->exists(pack_odb_backend_2, &oid_tree_1));
|
||
|
+ cl_assert(pack_odb_backend_2->exists(pack_odb_backend_2, &oid_tree_2));
|
||
|
+
|
||
|
+ pack_odb_backend_1->free(pack_odb_backend_1);
|
||
|
+ pack_odb_backend_2->free(pack_odb_backend_2);
|
||
|
+ free((void *)pack_name_1);
|
||
|
+ free((void *)pack_name_2);
|
||
|
+ git_str_dispose(&pack_path_1);
|
||
|
+ git_str_dispose(&pack_path_2);
|
||
|
+
|
||
|
+}
|