@@ -82,3 +82,12 @@ uploadpack.allowRefInWant::
is intended for the benefit of load-balanced servers which may
not have the same view of what OIDs their refs point to due to
replication delay.
+
+uploadpack.missingAction::
+ If this option is set, `upload-pack` will call
+ linkgit:git-pack-objects[1] passing it the corresponding
+ `--missing=<missing-action>` command line option. See the
+ documentation for that option, to see the valid values and
+ their meaning. This could be useful if some objects are
+ missing on a server, but a client already has them or could
+ still get them from somewhere else.
@@ -24,3 +24,19 @@ int parse_missing_action_value(const char *value, int print_ok)
return -1;
}
+
+const char *missing_action_to_string(enum missing_action action)
+{
+ switch (action) {
+ case MA_ERROR:
+ return "error";
+ case MA_ALLOW_ANY:
+ return "allow-any";
+ case MA_PRINT:
+ return "print";
+ case MA_ALLOW_PROMISOR:
+ return "allow-promisor";
+ default:
+ BUG("invalid missing action %d", action);
+ }
+}
@@ -15,4 +15,6 @@ enum missing_action {
*/
int parse_missing_action_value(const char *value, int print_ok);
+const char *missing_action_to_string(enum missing_action action);
+
#endif /* MISSING_H */
new file mode 100755
@@ -0,0 +1,125 @@
+#!/bin/sh
+
+test_description='handling of missing objects in upload-pack'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+# Setup the repository with three commits, this way HEAD is always
+# available and we can hide commit 1 or 2.
+test_expect_success 'setup: create "template" repository' '
+ git init template &&
+ test_commit -C template 1 &&
+ test_commit -C template 2 &&
+ test_commit -C template 3 &&
+ test-tool genrandom foo 10240 >template/foo &&
+ git -C template add foo &&
+ git -C template commit -m foo
+'
+
+# A bare repo will act as a server repo with unpacked objects.
+test_expect_success 'setup: create bare "server" repository' '
+ git clone --bare --no-local template server &&
+ mv server/objects/pack/pack-* . &&
+ packfile=$(ls pack-*.pack) &&
+ git -C server unpack-objects --strict <"$packfile"
+'
+
+# Fetching with 'uploadpack.missingAction=allow-any' only works with
+# blobs, as `git pack-objects --missing=allow-any` fails if a missing
+# object is not a blob.
+test_expect_success "fetch with uploadpack.missingAction=allow-any" '
+ oid="$(git -C server rev-parse HEAD:1.t)" &&
+ oid_path="$(test_oid_to_path $oid)" &&
+ path="server/objects/$oid_path" &&
+
+ mv "$path" "$path.hidden" &&
+ test_when_finished "mv $path.hidden $path" &&
+
+ git init client &&
+ test_when_finished "rm -rf client" &&
+
+ # Client needs the missing objects to be available somehow
+ client_path="client/.git/objects/$oid_path" &&
+ mkdir -p $(dirname "$client_path") &&
+ cp "$path.hidden" "$client_path" &&
+
+ test_must_fail git -C client fetch ../server &&
+ git -C server config uploadpack.missingAction error &&
+ test_must_fail git -C client fetch ../server &&
+ git -C server config uploadpack.missingAction allow-any &&
+ git -C client fetch ../server &&
+ git -C server config --unset uploadpack.missingAction
+'
+
+check_missing_objects () {
+ git -C "$1" rev-list --objects --all --missing=print > all.txt &&
+ sed -n "s/^\?\(.*\)/\1/p" <all.txt >missing.txt &&
+ test_line_count = "$2" missing.txt &&
+ test "$3" = "$(cat missing.txt)"
+}
+
+test_expect_success "setup for testing uploadpack.missingAction=allow-promisor" '
+ # Create another bare repo called "server2"
+ git init --bare server2 &&
+
+ # Copy the largest object from server to server2
+ obj="HEAD:foo" &&
+ oid="$(git -C server rev-parse $obj)" &&
+ oid_path="$(test_oid_to_path $oid)" &&
+ path="server/objects/$oid_path" &&
+ path2="server2/objects/$oid_path" &&
+ mkdir -p $(dirname "$path2") &&
+ cp "$path" "$path2" &&
+
+ # Repack everything first
+ git -C server -c repack.writebitmaps=false repack -a -d &&
+
+ # Repack without the largest object and create a promisor pack on server
+ git -C server -c repack.writebitmaps=false repack -a -d \
+ --filter=blob:limit=5k --filter-to="$(pwd)" &&
+ promisor_file=$(ls server/objects/pack/*.pack | sed "s/\.pack/.promisor/") &&
+ > "$promisor_file" &&
+
+ # Check that only one object is missing on the server
+ check_missing_objects server 1 "$oid" &&
+
+ # Configure server2 as promisor remote for server
+ git -C server remote add server2 "file://$(pwd)/server2" &&
+ git -C server config remote.server2.promisor true &&
+
+ git -C server2 config uploadpack.allowFilter true &&
+ git -C server2 config uploadpack.allowAnySHA1InWant true &&
+ git -C server config uploadpack.allowFilter true &&
+ git -C server config uploadpack.allowAnySHA1InWant true
+'
+
+test_expect_success "fetch with uploadpack.missingAction=allow-promisor" '
+ git -C server config uploadpack.missingAction allow-promisor &&
+
+ # Clone from server to create a client
+ git clone -c remote.server2.promisor=true \
+ -c remote.server2.fetch="+refs/heads/*:refs/remotes/server2/*" \
+ -c remote.server2.url="file://$(pwd)/server2" \
+ --no-local --filter="blob:limit=5k" server client &&
+ test_when_finished "rm -rf client" &&
+
+ # Check that the largest object is still missing on the server
+ check_missing_objects server 1 "$oid"
+'
+
+test_expect_success "fetch without uploadpack.missingAction=allow-promisor" '
+ git -C server config --unset uploadpack.missingAction &&
+
+ # Clone from server to create a client
+ git clone -c remote.server2.promisor=true \
+ -c remote.server2.fetch="+refs/heads/*:refs/remotes/server2/*" \
+ -c remote.server2.url="file://$(pwd)/server2" \
+ --no-local --filter="blob:limit=5k" server client &&
+ test_when_finished "rm -rf client" &&
+
+ # Check that the largest object is not missing on the server anymore
+ check_missing_objects server 0 ""
+'
+
+test_done
@@ -29,6 +29,8 @@
#include "write-or-die.h"
#include "json-writer.h"
#include "strmap.h"
+#include "missing.h"
+#include "object-file.h"
/* Remember to update object flag allocation in object.h */
#define THEY_HAVE (1u << 11)
@@ -96,6 +98,8 @@ struct upload_pack_data {
const char *pack_objects_hook;
+ enum missing_action missing_action;
+
unsigned stateless_rpc : 1; /* v0 only */
unsigned no_done : 1; /* v0 only */
unsigned daemon_mode : 1; /* v0 only */
@@ -315,6 +319,9 @@ static void create_pack_file(struct upload_pack_data *pack_data,
strvec_push(&pack_objects.args, "--delta-base-offset");
if (pack_data->use_include_tag)
strvec_push(&pack_objects.args, "--include-tag");
+ if (pack_data->missing_action)
+ strvec_pushf(&pack_objects.args, "--missing=%s",
+ missing_action_to_string(pack_data->missing_action));
if (pack_data->filter_options.choice) {
const char *spec =
expand_list_objects_filter_spec(&pack_data->filter_options);
@@ -1371,6 +1378,18 @@ static int upload_pack_config(const char *var, const char *value,
precomposed_unicode = git_config_bool(var, value);
} else if (!strcmp("transfer.advertisesid", var)) {
data->advertise_sid = git_config_bool(var, value);
+ } else if (!strcmp("uploadpack.missingaction", var)) {
+ int res = parse_missing_action_value(value, 0);
+ if (res < 0)
+ die(_("invalid value for '%s': '%s'"), var, value);
+ /*
+ * parse_missing_action_value() unsets fetch_if_missing
+ * but if we allow promisor we want to still fetch from
+ * the promisor remote
+ */
+ if (res == MA_ALLOW_PROMISOR)
+ fetch_if_missing =1;
+ data->missing_action = res;
}
if (parse_object_filter_config(var, value, ctx->kvi, data) < 0)