diff --git a/Documentation/RelNotes/2.56.0.adoc b/Documentation/RelNotes/2.56.0.adoc
index d33dd57996a486..65f0d965633959 100644
--- a/Documentation/RelNotes/2.56.0.adoc
+++ b/Documentation/RelNotes/2.56.0.adoc
@@ -121,6 +121,11 @@ UI, Workflows & Features
code did not check the presence of a value and instead segfaulted
without one, which has been corrected.
+ * 'git repack' has been taught '--drop-filtered' to delete local
+ promisor blobs exceeding a limit (currently 'blob:limit=') in partial
+ clones, reclaiming space. Guards prevent running during other
+ operations or if referenced by the index.
+
Performance, Internal Implementation, Development Support etc.
--------------------------------------------------------------
@@ -419,6 +424,16 @@ Performance, Internal Implementation, Development Support etc.
* The setting of a now-unused member '.pretty_given' in the sequencer
machinery has been removed.
+ * The performance of adding numerous new packfiles has been improved
+ by introducing a fast path for known-new packfiles to skip an
+ unnecessary traversal in packfile_list_append(), avoiding a
+ quadratic complexity regression on load.
+
+ * The unused name parameter in 'struct chdir_notify_entry' has been
+ removed from chdir_notify_register(), chdir_notify_unregister(), and
+ related callback signatures across several subsystems, simplifying the
+ API now that trace output no longer uses it.
+
Fixes since v2.55
-----------------
@@ -669,3 +684,14 @@ Fixes since v2.55
* The help text for the '-l' option of 'git diff' has been updated.
(merge 764243bdf4 en/diff-l-opt-help later to maint).
+
+ * 'git -C
diff fi' did not complete 'file', which has
+ been corrected.
+ (merge 354d1bf3a0 jc/complete-diff-tracked-paths later to maint).
+
+ * 'git -C checkout fi' did not complete 'file', which has
+ been corrected.
+ (merge 05e2ab1f31 jc/complete-checkout later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+ (merge 026636128f ss/submittingpatches-typofix later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index c269e474e3f2bf..c60855f7069492 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -117,7 +117,7 @@ of review.
can still continue to further improve them by adding more patches on
top, but by the time a topic gets merged to 'next', it is expected
that everybody agrees that the scope and the basic direction of the
- topic are appropriate, so such an incremental updates are limited to
+ topic are appropriate, so such incremental updates are limited to
small corrections and polishing. After a topic cooks for some time
(like 7 calendar days) in 'next' without needing further tweaks on
top, it gets merged to the 'master' branch and waits to become part
diff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc
index 72c42015e23f94..a1f9e64f668750 100644
--- a/Documentation/git-repack.adoc
+++ b/Documentation/git-repack.adoc
@@ -12,6 +12,7 @@ SYNOPSIS
'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [-m]
[--window=] [--depth=] [--threads=] [--keep-pack=]
[--write-midx[=]] [--name-hash-version=] [--path-walk]
+ [--filter=] [--drop-filtered [--dry-run]]
DESCRIPTION
-----------
@@ -182,6 +183,42 @@ depth is 4095.
`objects` and `objects/info/alternates` sections of
linkgit:gitrepository-layout[5].
+--drop-filtered::
+ Delete the local objects that match the `--filter` specification
+ instead of keeping them in a separate packfile, reclaiming the
+ disk space they occupy. This is intended for partial clones,
+ where the filtered objects are promisor objects that remain
+ recoverable from the promisor remote and are lazily re-fetched
+ on demand when they are next needed.
++
+Only large blobs are supported for now, so `--filter=blob:limit=`
+is currently the only accepted filter. Because dropped objects must be
+recoverable, this option requires a promisor remote to be configured
+and refuses to run otherwise.
++
+This option requires `-a`, and implies `-d`: the objects are dropped by
+rebuilding the promisor pack without them and then removing the now
+redundant old packs, so the redundant packs must be deleted for the
+space to actually be reclaimed. It is incompatible with `--filter-to`
+and with bitmap writing (`-b`/`--write-bitmap-index`), since filtering
+breaks the single-pack closure that bitmaps require. A bitmap setting
+coming from configuration is silently disabled for the duration of the
+command.
++
+As a convenience, since dropped objects remain recoverable by lazy fetch,
+`--drop-filtered` refuses to run while another operation
+(merge, rebase, am, cherry-pick, revert, or bisect) is in progress, to
+avoid a surprising network fetch mid-operation, and refuses to drop any
+blob that the current index references, since such a blob would only be
+lazily re-fetched by the next command that inspects the working tree.
+These checks are skipped in bare repositories, which have neither a
+working tree nor an index.
+
+--dry-run::
+ Only meaningful with `--drop-filtered`. List the objects that
+ would be dropped, one object ID per line, without rebuilding any
+ pack or deleting anything.
+
-b::
--write-bitmap-index::
Write a reachability bitmap index as part of the repack. This
diff --git a/builtin/repack.c b/builtin/repack.c
index db504d673fcf52..c4360382c1fce2 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -14,6 +14,11 @@
#include "promisor-remote.h"
#include "repack.h"
#include "shallow.h"
+#include "list-objects-filter-options.h"
+#include "oidset.h"
+#include "hex.h"
+#include "wt-status.h"
+#include "read-cache-ll.h"
#define ALL_INTO_ONE 1
#define LOOSEN_UNREACHABLE 2
@@ -28,11 +33,15 @@ static int use_delta_islands;
static int run_update_server_info = 1;
static char *packdir, *packtmp_name, *packtmp;
static int midx_must_contain_cruft = 1;
+static int drop_filtered;
+static int dry_run;
+static int write_bitmaps_given;
static const char *const git_repack_usage[] = {
N_("git repack [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [-m]\n"
"[--window=] [--depth=] [--threads=] [--keep-pack=]\n"
- "[--write-midx[=]] [--name-hash-version=] [--path-walk]"),
+ "[--write-midx[=]] [--name-hash-version=] [--path-walk]\n"
+ "[--filter=] [--drop-filtered [--dry-run]]"),
NULL
};
@@ -111,6 +120,21 @@ static int repack_config(const char *var, const char *value,
return git_default_config(var, value, ctx, cb);
}
+static int option_parse_write_bitmaps(const struct option *opt, const char *arg,
+ int unset)
+{
+ int *value = opt->value;
+
+ BUG_ON_OPT_ARG(arg);
+ if (unset)
+ *value = 0;
+ else
+ *value = 1;
+
+ write_bitmaps_given = 1;
+ return 0;
+}
+
static int option_parse_write_midx(const struct option *opt, const char *arg,
int unset)
{
@@ -140,6 +164,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;
@@ -194,8 +219,9 @@ int cmd_repack(int argc,
OPT__QUIET(&po_args.quiet, N_("be quiet")),
OPT_BOOL('l', "local", &po_args.local,
N_("pass --local to git-pack-objects")),
- OPT_BOOL('b', "write-bitmap-index", &write_bitmaps,
- N_("write bitmap index")),
+ OPT_CALLBACK_F('b', "write-bitmap-index", &write_bitmaps, NULL,
+ N_("write bitmap index"),
+ PARSE_OPT_NOARG, option_parse_write_bitmaps),
OPT_BOOL('i', "delta-islands", &use_delta_islands,
N_("pass --delta-islands to git-pack-objects")),
OPT_STRING(0, "unpack-unreachable", &unpack_unreachable, N_("approxidate"),
@@ -231,6 +257,10 @@ int cmd_repack(int argc,
N_("pack prefix to store a pack containing pruned objects")),
OPT_STRING(0, "filter-to", &filter_to, N_("dir"),
N_("pack prefix to store a pack containing filtered out objects")),
+ OPT_BOOL(0, "drop-filtered", &drop_filtered,
+ N_("delete filtered out objects (requires --filter)")),
+ OPT_BOOL(0, "dry-run", &dry_run,
+ N_("only show which objects would be dropped")),
OPT_END()
};
@@ -252,6 +282,118 @@ int cmd_repack(int argc,
po_args.depth = xstrdup_or_null(opt_depth);
po_args.threads = xstrdup_or_null(opt_threads);
+ die_for_incompatible_opt2(drop_filtered, "--drop-filtered",
+ !!filter_to, "--filter-to");
+
+ if (dry_run && !drop_filtered)
+ die(_("--dry-run only takes effect with --drop-filtered"));
+
+ if (drop_filtered) {
+ if (!po_args.filter_options.choice)
+ die(_("--drop-filtered requires --filter"));
+
+ if (!(pack_everything & ALL_INTO_ONE))
+ die(_("--drop-filtered requires -a"));
+
+ /*
+ * Only blob:limit= is supported for now. Reject other
+ * filter choices early, before walking the object database.
+ */
+ if (po_args.filter_options.choice != LOFC_BLOB_LIMIT)
+ die(_("--drop-filtered only supports --filter=blob:limit= for now"));
+
+ /*
+ * An explicit -b on the command line is a conflict we have to
+ * report; a bitmap setting from config is silently overridden
+ * for the duration of the command.
+ */
+ if (write_bitmaps_given && write_bitmaps > 0)
+ die(_("options '%s' and '%s' cannot be used together"),
+ "--drop-filtered", "--write-bitmap-index");
+
+ /*
+ * Without a promisor remote there is nowhere to re-fetch the
+ * dropped objects from, so dropping them would be permanent
+ * data loss.
+ */
+ if (!repo_has_promisor_remote(repo))
+ die(_("--drop-filtered requires a promisor remote"));
+
+ /*
+ * Refuse to run while another operation is in progress. A
+ * dropped object would just be lazily re-fetched when the
+ * operation resumes, but triggering a network fetch in the
+ * middle of a half-finished
+ * merge/rebase/cherry-pick/revert/bisect is a poor
+ * experience, so this is a UX convenience rather than a
+ * safety measure. Bare repositories have no such state, so
+ * the check is skipped there.
+ */
+ if (!is_bare_repository(repo)) {
+ struct wt_status_state state = { 0 };
+
+ wt_status_get_state(repo, &state, 0);
+ if (state.merge_in_progress || state.revert_in_progress ||
+ state.rebase_in_progress || state.bisect_in_progress ||
+ state.cherry_pick_in_progress || state.am_in_progress ||
+ state.rebase_interactive_in_progress) {
+ wt_status_state_free_buffers(&state);
+ die(_("--drop-filtered cannot be used while "
+ "another operation (merge, rebase, am, "
+ "cherry-pick, revert, or bisect) is in "
+ "progress"));
+ }
+ wt_status_state_free_buffers(&state);
+ }
+
+ write_bitmaps = 0;
+
+ /*
+ * Dropping objects means rebuilding the promisor packs
+ * without them and then removing the old packs, so the
+ * redundant packs must be deleted. Imply -d on a real run.
+ */
+ if (!dry_run)
+ delete_redundant = 1;
+
+ ret = enumerate_promisor_blobs(repo, &po_args.filter_options, &drop_oids);
+
+ if (ret)
+ goto cleanup;
+
+ /*
+ * Refuse to drop blobs that the current index references.
+ * Such a blob would only be lazily re-fetched by the next
+ * command that touches the worktree, so dropping it reclaims
+ * nothing. This guard just avoids that churn. Bare
+ * repositories have no index, so the check is skipped there.
+ */
+ if (!is_bare_repository(repo) && oidset_size(&drop_oids)) {
+ struct index_state *istate = repo->index;
+ unsigned int i;
+
+ if (repo_read_index(repo) < 0)
+ die(_("could not read the index"));
+
+ for (i = 0; i < istate->cache_nr; i++) {
+ const struct cache_entry *ce = istate->cache[i];
+
+ if (oidset_contains(&drop_oids, &ce->oid))
+ die(_("cannot drop '%s' (%s): it is referenced by the current index"),
+ ce->name, oid_to_hex(&ce->oid));
+ }
+ }
+
+ 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)
die(_("cannot delete packs in a precious-objects repo"));
@@ -362,7 +504,8 @@ int cmd_repack(int argc,
strvec_push(&cmd.args, "--delta-islands");
if (pack_everything & ALL_INTO_ONE) {
- repack_promisor_objects(repo, &po_args, &names, packtmp);
+ repack_promisor_objects(repo, &po_args, &names, packtmp,
+ (drop_filtered && !dry_run) ? &drop_oids : NULL);
if (existing_packs_has_non_kept(&existing) &&
delete_redundant &&
@@ -545,7 +688,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,
@@ -638,6 +781,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);
diff --git a/chdir-notify.c b/chdir-notify.c
index 1237a45e2e6492..55773c24c96eda 100644
--- a/chdir-notify.c
+++ b/chdir-notify.c
@@ -7,25 +7,22 @@
#include "trace.h"
struct chdir_notify_entry {
- const char *name;
chdir_notify_callback cb;
void *data;
struct list_head list;
};
static LIST_HEAD(chdir_notify_entries);
-void chdir_notify_register(const char *name,
- chdir_notify_callback cb,
+void chdir_notify_register(chdir_notify_callback cb,
void *data)
{
struct chdir_notify_entry *e = xmalloc(sizeof(*e));
- e->name = name;
e->cb = cb;
e->data = data;
list_add_tail(&e->list, &chdir_notify_entries);
}
-void chdir_notify_unregister(const char *name, chdir_notify_callback cb,
+void chdir_notify_unregister(chdir_notify_callback cb,
void *data)
{
struct list_head *pos, *p;
@@ -34,8 +31,7 @@ void chdir_notify_unregister(const char *name, chdir_notify_callback cb,
struct chdir_notify_entry *e =
list_entry(pos, struct chdir_notify_entry, list);
- if (e->cb != cb || e->data != data || !e->name != !name ||
- (e->name && strcmp(e->name, name)))
+ if (e->cb != cb || e->data != data)
continue;
list_del(pos);
@@ -64,7 +60,7 @@ int chdir_notify(const char *new_cwd)
list_for_each(pos, &chdir_notify_entries) {
struct chdir_notify_entry *e =
list_entry(pos, struct chdir_notify_entry, list);
- e->cb(e->name, old_cwd.buf, new_cwd, e->data);
+ e->cb(old_cwd.buf, new_cwd, e->data);
}
strbuf_release(&old_cwd);
diff --git a/chdir-notify.h b/chdir-notify.h
index 36b4114472e31d..e4ae38e12dd8b9 100644
--- a/chdir-notify.h
+++ b/chdir-notify.h
@@ -33,13 +33,11 @@
* $GIT_TRACE_SETUP. It may be NULL, but if non-NULL should point to
* storage which lasts as long as the registration is active.
*/
-typedef void (*chdir_notify_callback)(const char *name,
- const char *old_cwd,
+typedef void (*chdir_notify_callback)(const char *old_cwd,
const char *new_cwd,
void *data);
-void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data);
-void chdir_notify_unregister(const char *name, chdir_notify_callback cb,
- void *data);
+void chdir_notify_register(chdir_notify_callback cb, void *data);
+void chdir_notify_unregister(chdir_notify_callback cb, void *data);
/*
*
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e8757877104eb9..e6dce62d3c3fb4 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1735,49 +1735,59 @@ __git_checkout_default_dwim_mode ()
_git_checkout ()
{
- __git_has_doubledash && return
+ if ! __git_has_doubledash; then
+ local dwim_opt="$(__git_checkout_default_dwim_mode)"
- local dwim_opt="$(__git_checkout_default_dwim_mode)"
+ case "$prev" in
+ -b|-B|--orphan)
+ # Complete local branches (and DWIM branch
+ # remote branch names) for an option argument
+ # specifying a new branch name. This is for
+ # convenience, assuming new branches are
+ # possibly based on pre-existing branch names.
+ __git_complete_refs $dwim_opt --mode="heads"
+ return
+ ;;
+ *)
+ ;;
+ esac
- case "$prev" in
- -b|-B|--orphan)
- # Complete local branches (and DWIM branch
- # remote branch names) for an option argument
- # specifying a new branch name. This is for
- # convenience, assuming new branches are
- # possibly based on pre-existing branch names.
- __git_complete_refs $dwim_opt --mode="heads"
- return
- ;;
- *)
- ;;
- esac
+ case "$cur" in
+ --conflict=*)
+ __gitcomp "diff3 merge zdiff3" "" "${cur##--conflict=}"
+ return
+ ;;
+ --*)
+ __gitcomp_builtin checkout
+ return
+ ;;
+ *)
+ # At this point, we've already handled special completion for
+ # the arguments to -b/-B, and --orphan. There are 3 main
+ # things left we can possibly complete:
+ # 1) a start-point for -b/-B, -d/--detach, or --orphan
+ # 2) a remote head, for --track
+ # 3) an arbitrary reference, possibly including DWIM names
+ #
+
+ if [ -n "$(__git_find_on_cmdline "-b -B -d --detach --orphan")" ]; then
+ __git_complete_refs --mode="refs"
+ elif [ -n "$(__git_find_on_cmdline "-t --track")" ]; then
+ __git_complete_refs --mode="remote-heads"
+ else
+ __git_complete_refs $dwim_opt --mode="refs"
+ fi
+ ;;
+ esac
+ fi
- case "$cur" in
- --conflict=*)
- __gitcomp "diff3 merge zdiff3" "" "${cur##--conflict=}"
- ;;
- --*)
- __gitcomp_builtin checkout
- ;;
- *)
- # At this point, we've already handled special completion for
- # the arguments to -b/-B, and --orphan. There are 3 main
- # things left we can possibly complete:
- # 1) a start-point for -b/-B, -d/--detach, or --orphan
- # 2) a remote head, for --track
- # 3) an arbitrary reference, possibly including DWIM names
- #
+ if [ ${#COMPREPLY[@]} -eq 0 ]; then
+ __git_complete_index_file ""
+ fi
- if [ -n "$(__git_find_on_cmdline "-b -B -d --detach --orphan")" ]; then
- __git_complete_refs --mode="refs"
- elif [ -n "$(__git_find_on_cmdline "-t --track")" ]; then
- __git_complete_refs --mode="remote-heads"
- else
- __git_complete_refs $dwim_opt --mode="refs"
- fi
- ;;
- esac
+ if [ ${#COMPREPLY[@]} -eq 0 ]; then
+ __git_complete_index_file "--others --directory"
+ fi
}
__git_sequencer_inprogress_options="--continue --quit --abort --skip"
@@ -1947,35 +1957,48 @@ __git_diff_difftool_options="--cached --staged
_git_diff ()
{
- __git_has_doubledash && return
+ if ! __git_has_doubledash; then
+ case "$cur" in
+ --diff-algorithm=*)
+ __gitcomp "$__git_diff_algorithms" \
+ "" "${cur##--diff-algorithm=}"
+ return
+ ;;
+ --submodule=*)
+ __gitcomp "$__git_diff_submodule_formats" \
+ "" "${cur##--submodule=}"
+ return
+ ;;
+ --color-moved=*)
+ __gitcomp "$__git_color_moved_opts" \
+ "" "${cur##--color-moved=}"
+ return
+ ;;
+ --color-moved-ws=*)
+ __gitcomp "$__git_color_moved_ws_opts" \
+ "" "${cur##--color-moved-ws=}"
+ return
+ ;;
+ --ws-error-highlight=*)
+ __gitcomp "$__git_ws_error_highlight_opts" \
+ "" "${cur##--ws-error-highlight=}"
+ return
+ ;;
+ --*)
+ __gitcomp "$__git_diff_difftool_options"
+ return
+ ;;
+ esac
+ __git_complete_revlist_file
+ fi
- case "$cur" in
- --diff-algorithm=*)
- __gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
- return
- ;;
- --submodule=*)
- __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
- return
- ;;
- --color-moved=*)
- __gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}"
- return
- ;;
- --color-moved-ws=*)
- __gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}"
- return
- ;;
- --ws-error-highlight=*)
- __gitcomp "$__git_ws_error_highlight_opts" "" "${cur##--ws-error-highlight=}"
- return
- ;;
- --*)
- __gitcomp "$__git_diff_difftool_options"
- return
- ;;
- esac
- __git_complete_revlist_file
+ if [ ${#COMPREPLY[@]} -eq 0 ]; then
+ __git_complete_index_file ""
+ fi
+
+ if [ ${#COMPREPLY[@]} -eq 0 ]; then
+ __git_complete_index_file "--others --directory"
+ fi
}
__git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/list-objects-filter.c b/list-objects-filter.c
index c912ff3079a7d7..10e44f135780ba 100644
--- a/list-objects-filter.c
+++ b/list-objects-filter.c
@@ -828,3 +828,48 @@ void list_objects_filter__free(struct filter *filter)
filter->free_fn(filter->filter_data);
free(filter);
}
+
+/*
+ * NEEDSWORK: this reimplements the blob:limit size check rather than
+ * reusing the existing filter machinery in
+ * list_objects_filter__filter_object(). That machinery is currently
+ * tied to the object-walk path and cannot easily be driven from a
+ * plain oidset. It would be nice to refactor the filter code so this
+ * helper can reuse it instead of duplicating the size check.
+ */
+int list_objects_filter__filter_oidset(struct repository *r,
+ const struct list_objects_filter_options *opts,
+ const struct oidset *in,
+ struct oidset *omitted)
+{
+ struct oidset_iter iter;
+ const struct object_id *oid;
+
+ if (opts->choice != LOFC_BLOB_LIMIT)
+ return error(_("filter_oidset: only blob:limit filters are supported"));
+
+ oidset_iter_init(in, &iter);
+ while ((oid = oidset_iter_next(&iter))) {
+ struct object_info info = OBJECT_INFO_INIT;
+ enum object_type type;
+ size_t size;
+
+ info.typep = &type;
+ info.sizep = &size;
+
+ /*
+ * Use OBJECT_INFO_SKIP_FETCH_OBJECT to avoid triggering
+ * a lazy fetch while inspecting candidates for removal.
+ */
+ if (odb_read_object_info_extended(r->objects, oid, &info,
+ OBJECT_INFO_SKIP_FETCH_OBJECT) < 0)
+ continue;
+
+ if (type != OBJ_BLOB)
+ continue;
+
+ if (size >= opts->blob_limit_value)
+ oidset_insert(omitted, oid);
+ }
+ return 0;
+}
diff --git a/list-objects-filter.h b/list-objects-filter.h
index 9e98814111cee4..5207ab70a9188e 100644
--- a/list-objects-filter.h
+++ b/list-objects-filter.h
@@ -94,4 +94,20 @@ enum list_objects_filter_result list_objects_filter__filter_object(
*/
void list_objects_filter__free(struct filter *filter);
+/*
+ * Given a set of OIDs in 'in', populate 'omitted' with those that
+ * would be filtered by 'opts'. Currently only blob:limit=N is
+ * supported. Objects that cannot be read are silently skipped.
+ *
+ * NEEDSWORK: this reimplements the blob:limit size check rather than
+ * reusing the existing filter machinery. See the matching comment in
+ * list-objects-filter.c.
+ *
+ * Return 0 on success, -1 if the filter is not supported.
+ */
+int list_objects_filter__filter_oidset(struct repository *r,
+ const struct list_objects_filter_options *opts,
+ const struct oidset *in,
+ struct oidset *omitted);
+
#endif /* LIST_OBJECTS_FILTER_H */
diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb79d7..3945d651b92cd7 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -22,8 +22,7 @@
#include "tree.h"
#include "write-or-die.h"
-static void odb_source_files_reparent(const char *name UNUSED,
- const char *old_cwd,
+static void odb_source_files_reparent(const char *old_cwd,
const char *new_cwd,
void *cb_data)
{
@@ -37,7 +36,7 @@ static void odb_source_files_reparent(const char *name UNUSED,
static void odb_source_files_free(struct odb_source *source)
{
struct odb_source_files *files = odb_source_files_downcast(source);
- chdir_notify_unregister(NULL, odb_source_files_reparent, files);
+ chdir_notify_unregister(odb_source_files_reparent, files);
odb_source_free(&files->loose->base);
odb_source_free(&files->packed->base);
odb_source_release(&files->base);
@@ -780,7 +779,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
* paths in the primary ODB source in some user-facing functionality.
*/
if (!is_absolute_path(path))
- chdir_notify_register(NULL, odb_source_files_reparent, files);
+ chdir_notify_register(odb_source_files_reparent, files);
return files;
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd3334..0921a8748082ba 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -1009,8 +1009,7 @@ static void odb_source_loose_close(struct odb_source *source UNUSED)
/* Nothing to do. */
}
-static void odb_source_loose_reparent(const char *name UNUSED,
- const char *old_cwd,
+static void odb_source_loose_reparent(const char *old_cwd,
const char *new_cwd,
void *cb_data)
{
@@ -1026,7 +1025,7 @@ static void odb_source_loose_free(struct odb_source *source)
struct odb_source_loose *loose = odb_source_loose_downcast(source);
odb_source_loose_clear_cache(loose);
loose_object_map_clear(&loose->map);
- chdir_notify_unregister(NULL, odb_source_loose_reparent, loose);
+ chdir_notify_unregister(odb_source_loose_reparent, loose);
odb_source_release(&loose->base);
free(loose);
}
@@ -1056,7 +1055,7 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
loose->base.write_alternate = odb_source_loose_write_alternate;
if (!is_absolute_path(loose->base.path))
- chdir_notify_register(NULL, odb_source_loose_reparent, loose);
+ chdir_notify_register(odb_source_loose_reparent, loose);
loose_object_map_load(loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d9555856d7c..9b23ea15dc1900 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -785,8 +785,7 @@ static void odb_source_packed_prepare(struct odb_source *source,
packed->initialized = true;
}
-static void odb_source_packed_reparent(const char *name UNUSED,
- const char *old_cwd,
+static void odb_source_packed_reparent(const char *old_cwd,
const char *new_cwd,
void *cb_data)
{
@@ -815,7 +814,7 @@ static void odb_source_packed_free(struct odb_source *source)
{
struct odb_source_packed *packed = odb_source_packed_downcast(source);
- chdir_notify_unregister(NULL, odb_source_packed_reparent, packed);
+ chdir_notify_unregister(odb_source_packed_reparent, packed);
for (struct packfile_list_entry *e = packed->packs.head; e; e = e->next)
free(e->pack);
@@ -852,7 +851,7 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
packed->base.write_alternate = odb_source_packed_write_alternate;
if (!is_absolute_path(path))
- chdir_notify_register(NULL, odb_source_packed_reparent, packed);
+ chdir_notify_register(odb_source_packed_reparent, packed);
return packed;
}
diff --git a/packfile-list.c b/packfile-list.c
index 01fb913abf78fc..d6d411823c34a9 100644
--- a/packfile-list.c
+++ b/packfile-list.c
@@ -57,11 +57,12 @@ void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack)
list->tail = entry;
}
-void packfile_list_append(struct packfile_list *list, struct packed_git *pack)
+void packfile_list_append(struct packfile_list *list, struct packed_git *pack,
+ int skip_dup_check)
{
struct packfile_list_entry *entry;
- entry = packfile_list_remove_internal(list, pack);
+ entry = skip_dup_check ? NULL : packfile_list_remove_internal(list, pack);
if (!entry) {
entry = xmalloc(sizeof(*entry));
entry->pack = pack;
diff --git a/packfile-list.h b/packfile-list.h
index 1b05e2aa36de08..2b4b98b22673a4 100644
--- a/packfile-list.h
+++ b/packfile-list.h
@@ -15,7 +15,8 @@ struct packfile_list_entry {
void packfile_list_clear(struct packfile_list *list);
void packfile_list_remove(struct packfile_list *list, struct packed_git *pack);
void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack);
-void packfile_list_append(struct packfile_list *list, struct packed_git *pack);
+void packfile_list_append(struct packfile_list *list, struct packed_git *pack,
+ int skip_dup_check);
/*
* Find the pack within the "packs" list whose index contains the object
diff --git a/packfile.c b/packfile.c
index 7dc451bade983a..2ec5d221b4c088 100644
--- a/packfile.c
+++ b/packfile.c
@@ -781,7 +781,7 @@ void packfile_store_add_pack(struct odb_source_packed *store,
if (pack->pack_fd != -1)
pack_open_fds++;
- packfile_list_append(&store->packs, pack);
+ packfile_list_append(&store->packs, pack, 1);
strmap_put(&store->packs_by_path, pack->pack_name, pack);
}
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 1cc20aa486c263..71628550f20a57 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -111,8 +111,7 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)
}
}
-static void files_ref_store_reparent(const char *name UNUSED,
- const char *old_cwd,
+static void files_ref_store_reparent(const char *old_cwd,
const char *new_cwd,
void *payload)
{
@@ -182,7 +181,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
packed_ref_store_init(repo, NULL, refs->gitcommondir, opts);
refs->store_flags = opts->access_flags;
- chdir_notify_register(NULL, files_ref_store_reparent, refs);
+ chdir_notify_register(files_ref_store_reparent, refs);
strbuf_release(&refdir);
@@ -234,7 +233,7 @@ static void files_ref_store_release(struct ref_store *ref_store)
free(refs->gitcommondir);
ref_store_release(refs->packed_ref_store);
free(refs->packed_ref_store);
- chdir_notify_unregister(NULL, files_ref_store_reparent, refs);
+ chdir_notify_unregister(files_ref_store_reparent, refs);
}
static void files_reflog_path(struct files_ref_store *refs,
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index b9b04b70105f9a..a73fc6aca78023 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -217,8 +217,7 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)
return snapshot->refs->base.repo->hash_algo->hexsz;
}
-static void packed_ref_store_reparent(const char *name UNUSED,
- const char *old_cwd,
+static void packed_ref_store_reparent(const char *old_cwd,
const char *new_cwd,
void *payload)
{
@@ -248,7 +247,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo,
strbuf_addf(&sb, "%s/packed-refs", gitdir);
refs->path = strbuf_detach(&sb, NULL);
- chdir_notify_register(NULL, packed_ref_store_reparent, refs);
+ chdir_notify_register(packed_ref_store_reparent, refs);
return ref_store;
}
@@ -293,7 +292,7 @@ static void packed_ref_store_release(struct ref_store *ref_store)
clear_snapshot(refs);
rollback_lock_file(&refs->lock);
delete_tempfile(&refs->tempfile);
- chdir_notify_unregister(NULL, packed_ref_store_reparent, refs);
+ chdir_notify_unregister(packed_ref_store_reparent, refs);
free(refs->path);
}
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 028f0211af3d0f..08a75fb3287502 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -391,8 +391,7 @@ static const struct reftable_be_write_options *reftable_be_write_options(struct
return opts;
}
-static void reftable_be_reparent(const char *name UNUSED,
- const char *old_cwd,
+static void reftable_be_reparent(const char *old_cwd,
const char *new_cwd,
void *payload)
{
@@ -465,7 +464,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,
goto done;
}
- chdir_notify_register(NULL, reftable_be_reparent, refs);
+ chdir_notify_register(reftable_be_reparent, refs);
done:
assert(refs->err != REFTABLE_API_ERROR);
@@ -492,7 +491,7 @@ static void reftable_be_release(struct ref_store *ref_store)
free(be);
}
strmap_clear(&refs->worktree_backends, 0);
- chdir_notify_unregister(NULL, reftable_be_reparent, refs);
+ chdir_notify_unregister(reftable_be_reparent, refs);
}
static int reftable_be_create_on_disk(struct ref_store *ref_store,
diff --git a/repack-filtered.c b/repack-filtered.c
index edcf7667c5c378..869b9fc6e3b94c 100644
--- a/repack-filtered.c
+++ b/repack-filtered.c
@@ -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,79 @@ 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.
+ * 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;
+}
diff --git a/repack-promisor.c b/repack-promisor.c
index 90318ce15093f5..fabfdc168a9b86 100644
--- a/repack-promisor.c
+++ b/repack-promisor.c
@@ -6,10 +6,12 @@
#include "path.h"
#include "repository.h"
#include "run-command.h"
+#include "oidset.h"
struct write_oid_context {
struct child_process *cmd;
const struct git_hash_algo *algop;
+ const struct oidset *to_drop;
};
/*
@@ -23,6 +25,15 @@ static int write_oid(const struct object_id *oid,
struct write_oid_context *ctx = data;
struct child_process *cmd = ctx->cmd;
+ /*
+ * Objects in to_drop are being removed from the repository, so
+ * omit them from the rebuilt promisor pack. Each such object is a
+ * promisor object and therefore remains recoverable from the
+ * promisor remote.
+ */
+ if (ctx->to_drop && oidset_contains(ctx->to_drop, oid))
+ return 0;
+
if (cmd->in == -1) {
if (start_command(cmd))
die(_("could not start pack-objects to repack promisor objects"));
@@ -81,7 +92,8 @@ static void finish_repacking_promisor_objects(struct repository *repo,
void repack_promisor_objects(struct repository *repo,
const struct pack_objects_args *args,
- struct string_list *names, const char *packtmp)
+ struct string_list *names, const char *packtmp,
+ const struct oidset *to_drop)
{
struct write_oid_context ctx;
struct child_process cmd = CHILD_PROCESS_INIT;
@@ -98,6 +110,7 @@ void repack_promisor_objects(struct repository *repo,
*/
ctx.cmd = &cmd;
ctx.algop = repo->hash_algo;
+ ctx.to_drop = to_drop;
odb_for_each_object(repo->objects, NULL, write_oid, &ctx,
ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);
diff --git a/repack.h b/repack.h
index f9fbc895f02940..61e554e4ed383d 100644
--- a/repack.h
+++ b/repack.h
@@ -3,6 +3,7 @@
#include "list-objects-filter-options.h"
#include "string-list.h"
+#include "oidset.h"
struct pack_objects_args {
char *window;
@@ -100,7 +101,8 @@ void generated_pack_install(struct generated_pack *pack, const char *name,
void repack_promisor_objects(struct repository *repo,
const struct pack_objects_args *args,
- struct string_list *names, const char *packtmp);
+ struct string_list *names, const char *packtmp,
+ const struct oidset *to_drop);
struct pack_geometry {
struct packed_git **pack;
@@ -165,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,
diff --git a/setup.c b/setup.c
index 20d29f31f428b1..22beb86f7acb1e 100644
--- a/setup.c
+++ b/setup.c
@@ -1057,8 +1057,7 @@ static void apply_gitdir_and_environment(struct repository *repo, const char *pa
strvec_clear(&to_free);
}
-static void update_relative_gitdir(const char *name UNUSED,
- const char *old_cwd,
+static void update_relative_gitdir(const char *old_cwd,
const char *new_cwd,
void *data)
{
@@ -1086,7 +1085,7 @@ static void apply_and_export_relative_gitdir(struct repository *repo, const char
xsetenv(GIT_DIR_ENVIRONMENT, path, 1);
if (!is_absolute_path(path))
- chdir_notify_register(NULL, update_relative_gitdir, repo);
+ chdir_notify_register(update_relative_gitdir, repo);
strbuf_release(&realpath);
}
diff --git a/t/meson.build b/t/meson.build
index 181d61a8a0bd18..7f53cca7d1f891 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -966,6 +966,7 @@ integration_tests = [
't7703-repack-geometric.sh',
't7704-repack-cruft.sh',
't7705-repack-incremental-midx.sh',
+ 't7706-repack-drop-filtered.sh',
't7800-difftool.sh',
't7810-grep.sh',
't7811-grep-open.sh',
diff --git a/t/perf/p5303-many-packs.sh b/t/perf/p5303-many-packs.sh
index af173a7b73e398..4221f9dd706243 100755
--- a/t/perf/p5303-many-packs.sh
+++ b/t/perf/p5303-many-packs.sh
@@ -141,4 +141,8 @@ test_perf "load 10,000 packs" '
git rev-parse --verify "HEAD^{commit}"
'
+test_perf "abbreviate with 10,000 packs" '
+ git rev-parse --short HEAD
+'
+
test_done
diff --git a/t/t7706-repack-drop-filtered.sh b/t/t7706-repack-drop-filtered.sh
new file mode 100755
index 00000000000000..cb361158349c13
--- /dev/null
+++ b/t/t7706-repack-drop-filtered.sh
@@ -0,0 +1,185 @@
+#!/bin/sh
+
+test_description='git repack --drop-filtered enumerates filtered promisor blobs'
+
+. ./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 /dev/null &&
+ delete_object repo "$OID" &&
+ echo "$OID"
+}
+
+# Check option validation before any promisor walk
+test_expect_success 'setup plain repo for validation' '
+ git init plain &&
+ test_commit -C plain initial &&
+ git clone --bare plain plain.git &&
+ git -C plain.git repack -a -d
+'
+
+test_expect_success '--drop-filtered requires --filter' '
+ test_must_fail git -C plain.git repack --drop-filtered --dry-run -a 2>err &&
+ test_grep "drop-filtered requires --filter" err
+'
+
+test_expect_success '--drop-filtered cannot be used with --filter-to' '
+ test_must_fail git -C plain.git repack --drop-filtered \
+ --filter=blob:limit=1k --filter-to=./filter-out 2>err &&
+ test_grep "options .--drop-filtered. and .--filter-to. cannot be used together" err
+'
+
+test_expect_success '--dry-run only takes effect with --drop-filtered' '
+ test_must_fail git -C plain.git repack --dry-run 2>err &&
+ test_grep "dry-run only takes effect with --drop-filtered" err
+'
+
+test_expect_success '--drop-filtered requires -a' '
+ test_must_fail git -C plain.git repack --drop-filtered \
+ --filter=blob:limit=1k --dry-run 2>err &&
+ test_grep "drop-filtered requires -a" err
+'
+
+test_expect_success '--drop-filtered fails with --write-bitmap-index' '
+ test_must_fail git -C plain.git repack --drop-filtered \
+ --filter=blob:limit=1k --dry-run -a -b 2>err &&
+ test_grep "options .--drop-filtered. and .--write-bitmap-index. cannot be used together" err
+'
+
+test_expect_success '--drop-filtered rejects explicit -b even when repack.writeBitmaps=true' '
+ test_must_fail git -C plain.git -c repack.writeBitmaps=true \
+ repack --drop-filtered --filter=blob:limit=1k --dry-run -a -b 2>err &&
+ test_grep "options .--drop-filtered. and .--write-bitmap-index. cannot be used together" err
+'
+
+test_expect_success '--drop-filtered fails without a promisor remote' '
+ test_must_fail git -C plain.git repack --drop-filtered \
+ --filter=blob:limit=1k --dry-run -a 2>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 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_expect_success '--drop-filtered removes the promisor blob locally' '
+ BIG=$(cat big_oid) &&
+ SMALL=$(cat small_oid) &&
+
+ git -C repo -c repack.writeBitmaps=false \
+ repack --drop-filtered --filter=blob:limit=1k -a &&
+
+ git -C repo cat-file --batch-all-objects --batch-check="%(objectname)" >present &&
+ test_grep ! "$BIG" present &&
+ test_grep "$SMALL" present
+'
+
+test_expect_success '--drop-filtered refuses when a merge is in progress' '
+ test_when_finished "git -C repo merge --abort || :" &&
+
+ # Create a conflicting merge so wt_status reports it.
+ git -C repo checkout -B mergebase base &&
+ echo one >repo/conflict.txt &&
+ git -C repo add conflict.txt &&
+ git -C repo commit -m one &&
+
+ git -C repo checkout -B mergeother base &&
+ echo two >repo/conflict.txt &&
+ git -C repo add conflict.txt &&
+ git -C repo commit -m two &&
+
+ test_must_fail git -C repo merge mergebase &&
+
+ test_must_fail git -C repo -c repack.writeBitmaps=false \
+ repack --drop-filtered --filter=blob:limit=1k --dry-run -a 2>err &&
+ test_grep "in progress" err
+'
+
+test_expect_success '--drop-filtered refuses to drop an index-referenced blob' '
+ # Create a large blob, add it to the index and make it a promisor object
+ # so the index references it and enumeration picks it up.
+ test-tool genrandom idx 4096 >repo/tracked-big.bin &&
+ git -C repo add tracked-big.bin &&
+ OID=$(git -C repo rev-parse :tracked-big.bin) &&
+ printf "%s\n" "$OID" | pack_as_from_promisor >/dev/null &&
+ delete_object repo "$OID" &&
+
+ test_must_fail git -C repo -c repack.writeBitmaps=false \
+ repack --drop-filtered --filter=blob:limit=1k --dry-run -a 2>err &&
+ test_grep "referenced by the current index" err
+'
+
+test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 9ae3c48ebdd5e9..628f632ebf13e4 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2663,6 +2663,8 @@ test_expect_success 'setup for integration tests' '
echo content >file1 &&
echo more >file2 &&
git add file1 file2 &&
+ echo untracked >file3 &&
+ echo untracked >ufile &&
git commit -m one &&
git branch mybranch &&
git tag mytag
@@ -2712,6 +2714,118 @@ test_expect_success 'git -C checkout uses the right repo' '
EOF
'
+test_expect_success 'git checkout completes tracked paths when no refs match' '
+ # file1 and file2 are tracked but file3 is not
+ # there is no ref that begins with f
+ test_completion "git checkout f" <<-\EOF &&
+ file1
+ file2
+ EOF
+ test_completion "git checkout -- f" <<-\EOF
+ file1
+ file2
+ EOF
+'
+
+test_expect_success 'git checkout completes untracked paths, too' '
+ # ufile is not tracked and there is no ref that begins with u
+ test_completion "git checkout u" <<-\EOF &&
+ ufile
+ EOF
+ test_completion "git checkout -- u" <<-\EOF
+ ufile
+ EOF
+'
+
+test_expect_success 'git -C checkout completes paths in specified repo' '
+ # otherfile is tracked, oops is not
+ # lostfile is tracked but lost, ufile is untracked.
+ test_when_finished "rm -rf repo-for-checkout" &&
+ git init repo-for-checkout &&
+ echo content >repo-for-checkout/otherfile &&
+ echo content >repo-for-checkout/lostfile &&
+ git -C repo-for-checkout add otherfile &&
+ git -C repo-for-checkout add lostfile &&
+ git -C repo-for-checkout commit -m otherfile &&
+ echo untracked >repo-for-checkout/oops &&
+ echo untracked >repo-for-checkout/ufile &&
+ rm -f repo-for-checkout/lostfile &&
+ test_completion "git -C repo-for-checkout checkout o" <<-\EOF &&
+ otherfile
+ EOF
+ test_completion "git -C repo-for-checkout checkout -- o" <<-\EOF &&
+ otherfile
+ EOF
+ test_completion "git -C repo-for-checkout checkout l" <<-\EOF &&
+ lostfile
+ EOF
+ test_completion "git -C repo-for-checkout checkout -- l" <<-\EOF &&
+ lostfile
+ EOF
+ test_completion "git -C repo-for-checkout checkout u" <<-\EOF &&
+ ufile
+ EOF
+ test_completion "git -C repo-for-checkout checkout -- u" <<-\EOF
+ ufile
+ EOF
+'
+
+test_expect_success 'git diff completes tracked paths when no refs match' '
+ # file1 and file2 are tracked but file3 is not
+ # there is no ref that begins with f
+ test_completion "git diff f" <<-\EOF &&
+ file1
+ file2
+ EOF
+ test_completion "git diff -- f" <<-\EOF
+ file1
+ file2
+ EOF
+'
+
+test_expect_success 'git diff [--] completes untracked paths, too' '
+ # ufile is not tracked and there is no ref that begins with u
+ test_completion "git diff u" <<-\EOF &&
+ ufile
+ EOF
+ test_completion "git diff -- u" <<-\EOF
+ ufile
+ EOF
+'
+
+test_expect_success 'git -C diff completes paths in specified repo' '
+ test_when_finished "rm -rf repo-for-diff" &&
+ git init repo-for-diff &&
+ echo content >repo-for-diff/otherfile &&
+ echo content >repo-for-diff/lostfile &&
+ git -C repo-for-diff add otherfile &&
+ git -C repo-for-diff add lostfile &&
+ git -C repo-for-diff commit -m otherfile &&
+ echo untracked >repo-for-diff/oops &&
+ echo untracked >repo-for-diff/ufile &&
+ rm -f repo-for-diff/lostfile &&
+
+ test_completion "git -C repo-for-diff diff o" <<-\EOF &&
+ otherfile
+ EOF
+ test_completion "git -C repo-for-diff diff l" <<-\EOF &&
+ lostfile
+ EOF
+ test_completion "git -C repo-for-diff diff u" <<-\EOF &&
+ ufile
+ EOF
+
+ test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
+ otherfile
+ EOF
+ test_completion "git -C repo-for-diff diff -- l" <<-\EOF &&
+ lostfile
+ EOF
+ test_completion "git -C repo-for-diff diff -- u" <<-\EOF
+ ufile
+ EOF
+'
+
test_expect_success 'show completes all refs' '
test_completion "git show m" <<-\EOF
main Z
diff --git a/tmp-objdir.c b/tmp-objdir.c
index d199d39e7c9d51..520df2df8c5a6b 100644
--- a/tmp-objdir.c
+++ b/tmp-objdir.c
@@ -37,8 +37,7 @@ static void tmp_objdir_free(struct tmp_objdir *t)
free(t);
}
-static void tmp_objdir_reparent(const char *name UNUSED,
- const char *old_cwd,
+static void tmp_objdir_reparent(const char *old_cwd,
const char *new_cwd,
void *cb_data)
{
@@ -67,7 +66,7 @@ int tmp_objdir_destroy(struct tmp_objdir *t)
err = remove_dir_recursively(&t->path, 0);
- chdir_notify_unregister(NULL, tmp_objdir_reparent, t);
+ chdir_notify_unregister(tmp_objdir_reparent, t);
tmp_objdir_free(t);
return err;
@@ -155,7 +154,7 @@ struct tmp_objdir *tmp_objdir_create(struct repository *r,
repo_get_object_directory(r), prefix);
if (!is_absolute_path(t->path.buf))
- chdir_notify_register(NULL, tmp_objdir_reparent, t);
+ chdir_notify_register(tmp_objdir_reparent, t);
if (!mkdtemp(t->path.buf)) {
/* free, not destroy, as we never touched the filesystem */