mirror of
https://github.com/git/git.git
synced 2026-08-08 17:11:48 +00:00
builtin/repack: enumerate promisor blobs for --drop-filtered
Add enumeration logic for --drop-filtered. In --dry-run mode, print the OIDs of locally-held promisor blobs that exceed the filter threshold, as candidates for removal. Reading from write_filtered_pack() cannot work for partial clones. git repack routes promisor objects through a separate path: repack_promisor_objects() repacks them first, and the main pack-objects run uses --exclude-promisor-objects. By the time write_filtered_pack() runs, the promisor blobs are already consumed by the main pack. The filtered pack is always empty on a partial clone. Instead, walk promisor objects directly via odb_for_each_object() with ODB_FOR_EACH_OBJECT_PROMISOR_ONLY, collecting all promisor blobs into an oidset. The blobs exceeding the filter threshold are then selected using list_objects_filter__filter_oidset(). Every object enumerated this way is a promisor object, so it is recoverable from the promisor remote in the same sense as the rest of a partial clone, as long as the remote still has it. This holds without a separate is_promisor_object() check. A future implementation can verify availability against the remote directly once a client-side remote-object-info query exists. OBJECT_INFO_SKIP_FETCH_OBJECT is passed to every object info query so enumeration never triggers a lazy fetch. The enumeration collects candidates into a caller-provided oidset and --dry-run prints them. Actually removing the objects, together with the required promisor-remote verification, is written in a later commit. Mentored-by: Christian Couder <christian.couder@gmail.com> Mentored-by: Siddharth Asthana <siddharthasthana31@gmail.com> Signed-off-by: Siddharth Shrimali <r.siddharth.shrimali@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
committed by
Junio C Hamano
parent
948c800418
commit
0bed9fda6c
@@ -15,6 +15,8 @@
|
||||
#include "repack.h"
|
||||
#include "shallow.h"
|
||||
#include "list-objects-filter-options.h"
|
||||
#include "oidset.h"
|
||||
#include "hex.h"
|
||||
|
||||
#define ALL_INTO_ONE 1
|
||||
#define LOOSEN_UNREACHABLE 2
|
||||
@@ -159,6 +161,7 @@ int cmd_repack(int argc,
|
||||
struct string_list_item *item;
|
||||
struct string_list names = STRING_LIST_INIT_DUP;
|
||||
struct existing_packs existing = EXISTING_PACKS_INIT;
|
||||
struct oidset drop_oids = OIDSET_INIT;
|
||||
struct pack_geometry geometry = { 0 };
|
||||
struct tempfile *refs_snapshot = NULL;
|
||||
int i, ret;
|
||||
@@ -317,6 +320,20 @@ int cmd_repack(int argc,
|
||||
die(_("--drop-filtered requires a promisor remote"));
|
||||
|
||||
write_bitmaps = 0;
|
||||
|
||||
ret = enumerate_promisor_blobs(repo, &po_args.filter_options, &drop_oids);
|
||||
|
||||
if (ret)
|
||||
goto cleanup;
|
||||
|
||||
if (dry_run) {
|
||||
struct oidset_iter iter;
|
||||
const struct object_id *oid;
|
||||
|
||||
oidset_iter_init(&drop_oids, &iter);
|
||||
while ((oid = oidset_iter_next(&iter)))
|
||||
printf("%s\n", oid_to_hex(oid));
|
||||
}
|
||||
}
|
||||
|
||||
if (delete_redundant && repo->repository_format_precious_objects)
|
||||
@@ -612,7 +629,7 @@ int cmd_repack(int argc,
|
||||
}
|
||||
}
|
||||
|
||||
if (po_args.filter_options.choice) {
|
||||
if (po_args.filter_options.choice && !drop_filtered) {
|
||||
struct write_pack_opts opts = {
|
||||
.po_args = &po_args,
|
||||
.destination = filter_to,
|
||||
@@ -705,6 +722,7 @@ int cmd_repack(int argc,
|
||||
cleanup:
|
||||
string_list_clear(&keep_pack_list, 0);
|
||||
string_list_clear(&names, 1);
|
||||
oidset_clear(&drop_oids);
|
||||
existing_packs_release(&existing);
|
||||
pack_geometry_release(&geometry);
|
||||
pack_objects_args_release(&po_args);
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
#include "repository.h"
|
||||
#include "run-command.h"
|
||||
#include "string-list.h"
|
||||
#include "hex.h"
|
||||
#include "packfile.h"
|
||||
#include "list-objects-filter-options.h"
|
||||
#include "list-objects-filter.h"
|
||||
#include "odb.h"
|
||||
#include "promisor-remote.h"
|
||||
|
||||
int write_filtered_pack(const struct write_pack_opts *opts,
|
||||
struct existing_packs *existing,
|
||||
@@ -49,3 +55,78 @@ int write_filtered_pack(const struct write_pack_opts *opts,
|
||||
return finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,
|
||||
names);
|
||||
}
|
||||
|
||||
struct collect_cb_data {
|
||||
struct repository *repo;
|
||||
struct oidset *set;
|
||||
};
|
||||
|
||||
static int collect_promisor_blob(const struct object_id *oid,
|
||||
struct object_info *oi UNUSED,
|
||||
void *cb_data)
|
||||
{
|
||||
struct collect_cb_data *data = cb_data;
|
||||
struct object_info info = OBJECT_INFO_INIT;
|
||||
enum object_type type;
|
||||
|
||||
info.typep = &type;
|
||||
|
||||
/*
|
||||
* Use OBJECT_INFO_SKIP_FETCH_OBJECT to avoid triggering a
|
||||
* lazy fetch while collecting promisor blobs.
|
||||
*/
|
||||
if (odb_read_object_info_extended(data->repo->objects, oid, &info,
|
||||
OBJECT_INFO_SKIP_FETCH_OBJECT) < 0)
|
||||
return 0;
|
||||
|
||||
if (type == OBJ_BLOB)
|
||||
oidset_insert(data->set, oid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int enumerate_promisor_blobs(struct repository *repo,
|
||||
const struct list_objects_filter_options *filter,
|
||||
struct oidset *to_drop)
|
||||
{
|
||||
struct oidset all_promisor_blobs = OIDSET_INIT;
|
||||
struct collect_cb_data cb = {
|
||||
.repo = repo,
|
||||
.set = &all_promisor_blobs
|
||||
};
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* The caller (cmd_repack) is responsible for validating that a
|
||||
* blob:limit filter and a promisor remote are present before
|
||||
* calling this function.
|
||||
*
|
||||
* Walk only promisor objects. every object visited here is a
|
||||
* promisor object, so it is recoverable from the promisor remote
|
||||
* as long as the remote still has it, the same assumption the rest
|
||||
* of partial clone relies on
|
||||
|
||||
* We do not use write_filtered_pack() here because git repack
|
||||
* routes promisor objects through repack_promisor_objects()
|
||||
* before the filter machinery runs, so the filtered pack never
|
||||
* contains promisor blobs. Direct enumeration via
|
||||
* ODB_FOR_EACH_OBJECT_PROMISOR_ONLY is the correct approach.
|
||||
*/
|
||||
ret = odb_for_each_object(repo->objects, NULL,
|
||||
collect_promisor_blob, &cb,
|
||||
ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);
|
||||
if (ret)
|
||||
goto cleanup;
|
||||
|
||||
/*
|
||||
* Apply the filter to find which blobs exceed the threshold.
|
||||
*/
|
||||
ret = list_objects_filter__filter_oidset(repo,
|
||||
(struct list_objects_filter_options *)filter,
|
||||
&all_promisor_blobs,
|
||||
to_drop);
|
||||
|
||||
cleanup:
|
||||
oidset_clear(&all_promisor_blobs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
4
repack.h
4
repack.h
@@ -167,6 +167,10 @@ int write_filtered_pack(const struct write_pack_opts *opts,
|
||||
struct existing_packs *existing,
|
||||
struct string_list *names);
|
||||
|
||||
int enumerate_promisor_blobs(struct repository *repo,
|
||||
const struct list_objects_filter_options *filter,
|
||||
struct oidset *to_drop);
|
||||
|
||||
int write_cruft_pack(const struct write_pack_opts *opts,
|
||||
const char *cruft_expiration,
|
||||
unsigned long combine_cruft_below_size,
|
||||
|
||||
@@ -1,9 +1,36 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='git repack --drop-filtered option validation'
|
||||
test_description='git repack --drop-filtered enumerates filtered promisor blobs'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
delete_object () {
|
||||
local repo="$1" &&
|
||||
local obj="$2" &&
|
||||
local path="$repo/.git/objects/$(test_oid_to_path "$obj")" &&
|
||||
rm "$path"
|
||||
}
|
||||
|
||||
# pack the objects into a promisor pack inside "repo". it is a pack
|
||||
# accompanied by an empty ".promisor" marker file. objects
|
||||
# in such a pack are treated as recoverable from the promisor remote.
|
||||
pack_as_from_promisor () {
|
||||
HASH=$(git -C repo pack-objects .git/objects/pack/pack) &&
|
||||
>repo/.git/objects/pack/pack-$HASH.promisor &&
|
||||
echo $HASH
|
||||
}
|
||||
|
||||
# write a blob of $1 bytes into "repo", record it as coming from the
|
||||
# promisor remote, and remove the loose copy so the object is only
|
||||
# present in the promisor pack
|
||||
promisor_blob () {
|
||||
test-tool genrandom "$1" "$2" >blob_content &&
|
||||
OID=$(git -C repo hash-object -w --stdin <blob_content) &&
|
||||
printf "%s\n" "$OID" | pack_as_from_promisor >/dev/null &&
|
||||
delete_object repo "$OID" &&
|
||||
echo "$OID"
|
||||
}
|
||||
|
||||
# checks for options validations before any promisor walk
|
||||
test_expect_success 'setup plain repo for validation' '
|
||||
git init plain &&
|
||||
@@ -52,4 +79,59 @@ test_expect_success '--drop-filtered fails without a promisor remote' '
|
||||
test_grep "drop-filtered requires a promisor remote" err
|
||||
'
|
||||
|
||||
# enumeration tests using promisor pack
|
||||
test_expect_success 'setup repo with a promisor remote' '
|
||||
rm -rf repo &&
|
||||
test_create_repo repo &&
|
||||
test_commit -C repo base &&
|
||||
|
||||
# mark the repo as a partial clone with a promisor remote so the
|
||||
# promisor walk and the safety guard are satisfied
|
||||
git -C repo config core.repositoryformatversion 1 &&
|
||||
git -C repo config extensions.partialclone origin &&
|
||||
git -C repo config remote.origin.promisor true &&
|
||||
git -C repo config remote.origin.url "." &&
|
||||
|
||||
BIG=$(promisor_blob big 3072) &&
|
||||
SMALL=$(promisor_blob small 512) &&
|
||||
echo "$BIG" >big_oid &&
|
||||
echo "$SMALL" >small_oid
|
||||
'
|
||||
|
||||
test_expect_success 'promisor blob over the threshold is listed' '
|
||||
BIG=$(cat big_oid) &&
|
||||
SMALL=$(cat small_oid) &&
|
||||
|
||||
git -C repo -c repack.writeBitmaps=false \
|
||||
repack --drop-filtered --filter=blob:limit=1k --dry-run -a >out &&
|
||||
|
||||
test_grep "$BIG" out &&
|
||||
test_grep ! "$SMALL" out
|
||||
'
|
||||
|
||||
test_expect_success 'locally created blob is never listed' '
|
||||
BIG=$(cat big_oid) &&
|
||||
|
||||
# large blob that exists only locally must never be a drop candidate.
|
||||
# dropping it would be unrecoverable
|
||||
test-tool genrandom local 4096 >local_content &&
|
||||
LOCAL=$(git -C repo hash-object -w --stdin <local_content) &&
|
||||
|
||||
git -C repo -c repack.writeBitmaps=false \
|
||||
repack --drop-filtered --filter=blob:limit=1k --dry-run -a >out &&
|
||||
|
||||
test_grep "$BIG" out &&
|
||||
test_grep ! "$LOCAL" out
|
||||
'
|
||||
|
||||
test_expect_success '--dry-run does not remove the filtered objects' '
|
||||
BIG=$(cat big_oid) &&
|
||||
|
||||
git -C repo -c repack.writeBitmaps=false \
|
||||
repack --drop-filtered --filter=blob:limit=1k --dry-run -a >out &&
|
||||
|
||||
# candidate blob must still be present after a dry run
|
||||
git -C repo cat-file -e "$BIG"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
||||
Reference in New Issue
Block a user