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>main
parent
85531bbf29
commit
8bb2a3f454
|
|
@ -15,6 +15,8 @@
|
||||||
#include "repack.h"
|
#include "repack.h"
|
||||||
#include "shallow.h"
|
#include "shallow.h"
|
||||||
#include "list-objects-filter-options.h"
|
#include "list-objects-filter-options.h"
|
||||||
|
#include "oidset.h"
|
||||||
|
#include "hex.h"
|
||||||
|
|
||||||
#define ALL_INTO_ONE 1
|
#define ALL_INTO_ONE 1
|
||||||
#define LOOSEN_UNREACHABLE 2
|
#define LOOSEN_UNREACHABLE 2
|
||||||
|
|
@ -160,6 +162,7 @@ int cmd_repack(int argc,
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
struct string_list names = STRING_LIST_INIT_DUP;
|
struct string_list names = STRING_LIST_INIT_DUP;
|
||||||
struct existing_packs existing = EXISTING_PACKS_INIT;
|
struct existing_packs existing = EXISTING_PACKS_INIT;
|
||||||
|
struct oidset drop_oids = OIDSET_INIT;
|
||||||
struct pack_geometry geometry = { 0 };
|
struct pack_geometry geometry = { 0 };
|
||||||
struct tempfile *refs_snapshot = NULL;
|
struct tempfile *refs_snapshot = NULL;
|
||||||
int i, ret;
|
int i, ret;
|
||||||
|
|
@ -318,6 +321,20 @@ int cmd_repack(int argc,
|
||||||
die(_("--drop-filtered requires a promisor remote"));
|
die(_("--drop-filtered requires a promisor remote"));
|
||||||
|
|
||||||
write_bitmaps = 0;
|
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)
|
if (delete_redundant && repo->repository_format_precious_objects)
|
||||||
|
|
@ -613,7 +630,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 = {
|
struct write_pack_opts opts = {
|
||||||
.po_args = &po_args,
|
.po_args = &po_args,
|
||||||
.destination = filter_to,
|
.destination = filter_to,
|
||||||
|
|
@ -706,6 +723,7 @@ int cmd_repack(int argc,
|
||||||
cleanup:
|
cleanup:
|
||||||
string_list_clear(&keep_pack_list, 0);
|
string_list_clear(&keep_pack_list, 0);
|
||||||
string_list_clear(&names, 1);
|
string_list_clear(&names, 1);
|
||||||
|
oidset_clear(&drop_oids);
|
||||||
existing_packs_release(&existing);
|
existing_packs_release(&existing);
|
||||||
pack_geometry_release(&geometry);
|
pack_geometry_release(&geometry);
|
||||||
pack_objects_args_release(&po_args);
|
pack_objects_args_release(&po_args);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,12 @@
|
||||||
#include "repository.h"
|
#include "repository.h"
|
||||||
#include "run-command.h"
|
#include "run-command.h"
|
||||||
#include "string-list.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,
|
int write_filtered_pack(const struct write_pack_opts *opts,
|
||||||
struct existing_packs *existing,
|
struct existing_packs *existing,
|
||||||
|
|
@ -49,3 +55,79 @@ int write_filtered_pack(const struct write_pack_opts *opts,
|
||||||
return finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,
|
return finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,
|
||||||
names);
|
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.
|
||||||
|
* The caller has to_drop and is responsible for clearing it.
|
||||||
|
*/
|
||||||
|
ret = list_objects_filter__filter_oidset(repo,
|
||||||
|
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 existing_packs *existing,
|
||||||
struct string_list *names);
|
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,
|
int write_cruft_pack(const struct write_pack_opts *opts,
|
||||||
const char *cruft_expiration,
|
const char *cruft_expiration,
|
||||||
unsigned long combine_cruft_below_size,
|
unsigned long combine_cruft_below_size,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,37 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
test_description='git repack --drop-filtered option validation'
|
test_description='git repack --drop-filtered enumerates filtered promisor blobs'
|
||||||
|
|
||||||
. ./test-lib.sh
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
# Delete a loose or packed object from "repo".
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
# Check option validation before any promisor walk
|
# Check option validation before any promisor walk
|
||||||
test_expect_success 'setup plain repo for validation' '
|
test_expect_success 'setup plain repo for validation' '
|
||||||
git init plain &&
|
git init plain &&
|
||||||
|
|
@ -52,4 +80,59 @@ test_expect_success '--drop-filtered fails without a promisor remote' '
|
||||||
test_grep "drop-filtered requires a promisor remote" err
|
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
|
test_done
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue