* [PATCH GSoC v20 09/13] protocol-caps: check object existence regardless of the attributes requested
From: Pablo Sabater @ 2026-07-18 21:49 UTC (permalink / raw)
To: git
Cc: pabloosabaterr, chandrapratap3519, chriscool, eric.peijian,
gitster, jltobler, karthik.188, peff, toon, szeder.dev
In-Reply-To: <20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com>
Currently, send_info() only checks for existence when the attribute
'size' is also requested. Requesting a bare OID, without attributes only
echoes back the OID.
Extract the existence check to be done regardless of the number of
attributes requested.
While at it, introduce a wrapper called get_object_info() similar to
odb_read_object_info() that returns OBJ_BAD on fail and adds
OBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK flags.
OBJECT_INFO_SKIP_FETCH_OBJECT is so a server with a partial clone
doesn't trigger fetching objects when it gets an object-info request
with an OID that is not available locally. A server should only report
what it has locally.
Tighten the condition used to determine whether an object is
recognized. get_object_info() returns OBJ_BAD for unknown objects,
but OBJ_NONE (0) can also mean "not found". Change the check from '< 0'
to '<= OBJ_NONE' to cover both as unrecognized.
With this patch, a bare OID has two possible responses:
1. Recognized OID: the server answers with "<OID>"
2. Unrecognized OID: the server answers with "<OID> SP"
Update the object-info section in 'gitprotocol-v2.adoc':
- Require full obj-oid explicitly.
- Fix parentheses.
- Define obj-size explicitly.
- Make obj-size optional in obj-info and document the behavior
for unrecognized object IDs.
- Describe the attr header as zero or more pkt-lines, one per attribute,
matching what the server implements. A request with no attributes gets
no header.
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
Documentation/gitprotocol-v2.adoc | 21 ++++++++-----
protocol-caps.c | 45 ++++++++++++++++++++++++----
t/t5701-git-serve.sh | 63 +++++++++++++++++++++++++++++++++++++++
3 files changed, 115 insertions(+), 14 deletions(-)
diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc
index 2beb70595f..7bf62014c3 100644
--- a/Documentation/gitprotocol-v2.adoc
+++ b/Documentation/gitprotocol-v2.adoc
@@ -568,21 +568,26 @@ An `object-info` request takes the following arguments:
oid <oid>
Indicates to the server an object which the client wants to obtain
- information for.
+ information for. They must be full OIDs.
-The response of `object-info` is a list of the requested object ids
-and associated requested information, each separated by a single space.
+The response of `object-info` consists of one pkt-line per requested attribute,
+echoing the attributes the server will report, followed by one pkt-line per
+requested object id with its information, each field separated by a single
+space.
output = info flush-pkt
- info = PKT-LINE(attrs) LF)
- *PKT-LINE(obj-info LF)
-
- attrs = attr | attrs SP attrs
+ info = *PKT-LINE(attr LF)
+ *PKT-LINE(obj-info LF)
attr = "size"
- obj-info = obj-id SP obj-size
+ obj-size = 1*DIGIT
+
+ obj-info = obj-id [SP [obj-size]]
+
+If the server does not recognize the OID, the response will be `<oid> SP`
+regardless of the number of attributes requested.
bundle-uri
~~~~~~~~~~
diff --git a/protocol-caps.c b/protocol-caps.c
index 8858ea4489..02261be14d 100644
--- a/protocol-caps.c
+++ b/protocol-caps.c
@@ -30,6 +30,32 @@ static int parse_oid(const char *line, struct string_list *oid_str_list)
return 1;
}
+/*
+ * odb_read_object_info_extended() wrapper. Similar to odb_read_object_info()
+ * but uses the flags:
+ *
+ * - OBJECT_INFO_SKIP_FETCH_OBJECT so a server won't fetch an object when a
+ * object-info request asks for an OID that it doesn't have.
+ *
+ * - OBJECT_INFO_QUICK to avoid re-scanning packs when the object is not found.
+ */
+static enum object_type get_object_info(struct object_database *odb,
+ const struct object_id *oid,
+ size_t *sizep)
+{
+ enum object_type type;
+ struct object_info oi = OBJECT_INFO_INIT;
+
+ oi.typep = &type;
+ oi.sizep = sizep;
+ if (odb_read_object_info_extended(odb, oid, &oi,
+ OBJECT_INFO_LOOKUP_REPLACE |
+ OBJECT_INFO_SKIP_FETCH_OBJECT |
+ OBJECT_INFO_QUICK) < 0)
+ return OBJ_BAD;
+ return type;
+}
+
/*
* Validates and send requested info back to the client. Any errors detected
* are returned as they are detected.
@@ -62,15 +88,22 @@ static void send_info(struct repository *r, struct packet_writer *writer,
strbuf_addstr(&send_buffer, oid_str);
+ /*
+ * Check the existence of the object first.
+ * If an object is not recognized by the server append SP to
+ * the response.
+ */
+ if (get_object_info(r->objects, &oid, &object_size) <= OBJ_NONE) {
+ strbuf_addstr(&send_buffer, " ");
+ goto write;
+ }
+
if (info->size) {
- if (odb_read_object_info(r->objects, &oid, &object_size) < 0) {
- strbuf_addstr(&send_buffer, " ");
- } else {
- strbuf_addf(&send_buffer, " %"PRIuMAX,
- (uintmax_t)object_size);
- }
+ strbuf_addf(&send_buffer, " %"PRIuMAX,
+ (uintmax_t)object_size);
}
+write:
packet_writer_write(writer, "%s", send_buffer.buf);
strbuf_reset(&send_buffer);
}
diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh
index d4c28bae39..cacff4456c 100755
--- a/t/t5701-git-serve.sh
+++ b/t/t5701-git-serve.sh
@@ -7,6 +7,8 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./test-lib.sh
+unknown_oid=$(printf "test" | git hash-object --stdin)
+
test_expect_success 'setup to generate files with expected content' '
printf "agent=git/%s" "$(git version | cut -d" " -f3)" >agent_capability &&
@@ -364,6 +366,67 @@ test_expect_success 'basics of object-info' '
test_cmp expect actual
'
+test_expect_success 'bare OID request' '
+ test_config transfer.advertiseObjectInfo true &&
+
+ test-tool pkt-line pack >in <<-EOF &&
+ command=object-info
+ object-format=$(test_oid algo)
+ 0001
+ oid $(git rev-parse two:two.t)
+ 0000
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse two:two.t)
+ 0000
+ EOF
+
+ test-tool serve-v2 --stateless-rpc <in >out &&
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'object-info with bare unrecognized OID' '
+ test_config transfer.advertiseObjectInfo true &&
+
+ test-tool pkt-line pack >in <<-EOF &&
+ command=object-info
+ object-format=$(test_oid algo)
+ 0001
+ oid $unknown_oid
+ 0000
+ EOF
+
+ printf "%s \n" "$unknown_oid" >expect &&
+ printf "0000\n" >>expect &&
+
+ test-tool serve-v2 --stateless-rpc <in >out &&
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'object-info with size for unrecognized OID' '
+ test_config transfer.advertiseObjectInfo true &&
+
+ test-tool pkt-line pack >in <<-EOF &&
+ command=object-info
+ object-format=$(test_oid algo)
+ 0001
+ size
+ oid $unknown_oid
+ 0000
+ EOF
+
+ printf "size\n" >expect &&
+ printf "%s \n" "$unknown_oid" >>expect &&
+ printf "0000\n" >>expect &&
+
+ test-tool serve-v2 --stateless-rpc <in >out &&
+ test-tool pkt-line unpack <out >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'test capability advertisement with uploadpack.advertiseBundleURIs' '
test_config uploadpack.advertiseBundleURIs true &&
--
2.54.0
^ permalink raw reply related
* [PATCH GSoC v20 10/13] serve: advertise object-info feature
From: Pablo Sabater @ 2026-07-18 21:49 UTC (permalink / raw)
To: git
Cc: pabloosabaterr, chandrapratap3519, chriscool, eric.peijian,
gitster, jltobler, karthik.188, peff, toon, szeder.dev
In-Reply-To: <20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com>
From: Calvin Wan <calvinwan@google.com>
In order for a client to know what object-info components a server can
provide, advertise supported object-info features. This allows a client
to decide whether to query the server for object-info or fetch as a
fallback.
Helped-by: Jonathan Tan <jonathantanmy@google.com>
Helped-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Eric Ju <eric.peijian@gmail.com>
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
serve.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/serve.c b/serve.c
index 49a6e39b1d..2b07d922b3 100644
--- a/serve.c
+++ b/serve.c
@@ -89,7 +89,7 @@ static void session_id_receive(struct repository *r UNUSED,
trace2_data_string("transfer", NULL, "client-sid", client_sid);
}
-static int object_info_advertise(struct repository *r, struct strbuf *value UNUSED)
+static int object_info_advertise(struct repository *r, struct strbuf *value)
{
if (advertise_object_info == -1 &&
repo_config_get_bool(r, "transfer.advertiseobjectinfo",
@@ -97,6 +97,9 @@ static int object_info_advertise(struct repository *r, struct strbuf *value UNUS
/* disabled by default */
advertise_object_info = 0;
}
+ /* Currently only size is supported */
+ if (value && advertise_object_info)
+ strbuf_addstr(value, "size");
return advertise_object_info;
}
--
2.54.0
^ permalink raw reply related
* [PATCH GSoC v20 11/13] transport: add client support for object-info
From: Pablo Sabater @ 2026-07-18 21:50 UTC (permalink / raw)
To: git
Cc: pabloosabaterr, chandrapratap3519, chriscool, eric.peijian,
gitster, jltobler, karthik.188, peff, toon, szeder.dev
In-Reply-To: <20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com>
From: Calvin Wan <calvinwan@google.com>
Sometimes, it is beneficial to retrieve information about an object
without downloading it entirely. The server-side logic for this
functionality was implemented in commit "a2ba162cda (object-info:
support for retrieving object info, 2021-04-20)." And the wire
format is documented at
https://git-scm.com/docs/protocol-v2#_object_info.
Introduce client-side support for the object-info capability.
Add its own function for object-info separate from existing fetch
infrastructure.
Currently, the client supports requesting a list of OIDs with the size
attribute from a v2 server. If the server does not advertise this
feature (i.e., transfer.advertiseobjectinfo is set to false), the client
returns an error and exits.
Note that:
1. The entire request is written into req_buf before being sent to the
remote. This approach follows the pattern used in the
send_fetch_request() logic within 'fetch-pack.c'. Streaming the
request is not addressed in this patch.
2. A new field 'unrecognized' has been added to object_info. This new
field is set at fetch_object_info() when the object is unrecognized
by the server.
Helped-by: Jonathan Tan <jonathantanmy@google.com>
Helped-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Eric Ju <eric.peijian@gmail.com>
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
Makefile | 1 +
fetch-object-info.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++
fetch-object-info.h | 22 ++++++++
meson.build | 1 +
odb.h | 6 +++
transport-helper.c | 10 ++++
transport-internal.h | 8 +++
transport.c | 45 +++++++++++++++++
transport.h | 9 ++++
9 files changed, 240 insertions(+)
diff --git a/Makefile b/Makefile
index 1f3f099f5c..d450e0277e 100644
--- a/Makefile
+++ b/Makefile
@@ -1158,6 +1158,7 @@ LIB_OBJS += ewah/ewah_io.o
LIB_OBJS += ewah/ewah_rlw.o
LIB_OBJS += exec-cmd.o
LIB_OBJS += fetch-negotiator.o
+LIB_OBJS += fetch-object-info.o
LIB_OBJS += fetch-pack.o
LIB_OBJS += fmt-merge-msg.o
LIB_OBJS += fsck.o
diff --git a/fetch-object-info.c b/fetch-object-info.c
new file mode 100644
index 0000000000..30475a1e87
--- /dev/null
+++ b/fetch-object-info.c
@@ -0,0 +1,138 @@
+#include "git-compat-util.h"
+#include "gettext.h"
+#include "hex.h"
+#include "pkt-line.h"
+#include "connect.h"
+#include "oid-array.h"
+#include "odb.h"
+#include "fetch-object-info.h"
+#include "string-list.h"
+
+/* Sends object-info command and its arguments into the request buffer. */
+static void send_object_info_request(const int fd_out, struct object_info_args *args)
+{
+ struct strbuf req_buf = STRBUF_INIT;
+
+ write_command_and_capabilities(&req_buf, "object-info", args->server_options);
+
+ if (unsorted_string_list_has_string(args->object_info_options, "size"))
+ packet_buf_write(&req_buf, "size");
+ else if (args->object_info_options->nr)
+ BUG("only size should be in object_info_options");
+
+ if (args->oids)
+ for (size_t i = 0; i < args->oids->nr; i++)
+ packet_buf_write(&req_buf, "oid %s", oid_to_hex(&args->oids->oid[i]));
+
+ packet_buf_flush(&req_buf);
+ if (write_in_full(fd_out, req_buf.buf, req_buf.len) < 0)
+ die_errno(_("unable to write request to remote"));
+
+ strbuf_release(&req_buf);
+}
+
+static int parse_object_size(const char *s, size_t *res)
+{
+ uintmax_t uim;
+
+ if (!s[0] || s[strspn(s, "0123456789")])
+ return -1;
+ errno = 0;
+ uim = strtoumax(s, NULL, 10);
+ if (errno || uim > SIZE_MAX)
+ return -1;
+ *res = uim;
+ return 0;
+}
+
+int fetch_object_info(const enum protocol_version version, struct object_info_args *args,
+ struct packet_reader *reader, struct object_info *object_info_data,
+ const int stateless_rpc, const int fd_out)
+{
+ int size_index = -1;
+
+ switch (version) {
+ case protocol_v2:
+ if (!server_supports_v2("object-info"))
+ die(_("object-info capability is not enabled on the server"));
+ send_object_info_request(fd_out, args);
+ break;
+ case protocol_v1:
+ case protocol_v0:
+ die(_("object-info requires protocol v2"));
+ case protocol_unknown_version:
+ BUG("unknown protocol version");
+ }
+
+ for (size_t i = 0; i < args->object_info_options->nr; i++) {
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL) {
+ check_stateless_delimiter(stateless_rpc, reader,
+ "stateless delimiter expected");
+ return -1;
+ }
+
+ if (!string_list_has_string(args->object_info_options, reader->line))
+ return -1;
+
+ if (!strcmp(reader->line, "size")) {
+ /*
+ * i is the number of supported options which currently
+ * is only size. No risk of overflow.
+ */
+ size_index = (int)i;
+ for (size_t j = 0; j < args->oids->nr; j++)
+ object_info_data[j].sizep =
+ xcalloc(1, sizeof(*object_info_data[j].sizep));
+ } else {
+ BUG("only size is supported");
+ }
+ }
+
+ for (size_t i = 0;
+ packet_reader_read(reader) == PACKET_READ_NORMAL &&
+ i < args->oids->nr;
+ i++) {
+ struct string_list object_info_values = STRING_LIST_INIT_DUP;
+
+ string_list_split(&object_info_values, reader->line, " ", -1);
+
+ if (strcmp(object_info_values.items[0].string,
+ oid_to_hex(&args->oids->oid[i])))
+ die(_("object-info: expected OID: %s, got %s"),
+ oid_to_hex(&args->oids->oid[i]),
+ object_info_values.items[0].string);
+
+ /*
+ * If the response is two elements but the second one is an
+ * empty string, that means that the OID is unrecognized by the
+ * server.
+ */
+ if (object_info_values.nr >= 2 &&
+ !strcmp(object_info_values.items[1].string, "")) {
+ object_info_data[i].unrecognized = 1;
+ string_list_clear(&object_info_values, 0);
+ continue;
+ }
+
+ /*
+ * Because we filter the options to be only the supported by
+ * the server we expect the server to answer with the same
+ * number of attributes requested.
+ */
+ if (args->object_info_options->nr + 1 != object_info_values.nr)
+ die("object-info: unexpected number of attributes: %s",
+ reader->line);
+
+ if (size_index >= 0 &&
+ parse_object_size(object_info_values.items[size_index + 1].string,
+ object_info_data[i].sizep))
+ die("object-info: ref %s has invalid size %s",
+ object_info_values.items[0].string,
+ object_info_values.items[size_index + 1].string);
+
+ string_list_clear(&object_info_values, 0);
+ }
+ check_stateless_delimiter(stateless_rpc, reader, "stateless delimiter expected");
+
+ return 0;
+}
diff --git a/fetch-object-info.h b/fetch-object-info.h
new file mode 100644
index 0000000000..31aad98408
--- /dev/null
+++ b/fetch-object-info.h
@@ -0,0 +1,22 @@
+#ifndef FETCH_OBJECT_INFO_H
+#define FETCH_OBJECT_INFO_H
+
+#include "pkt-line.h"
+#include "protocol.h"
+
+struct object_info_args {
+ struct string_list *object_info_options;
+ const struct string_list *server_options;
+ struct oid_array *oids;
+};
+
+struct object_info;
+/*
+ * Sends git-cat-file object-info command into the request buf and read the
+ * results from packets.
+ */
+int fetch_object_info(enum protocol_version version, struct object_info_args *args,
+ struct packet_reader *reader, struct object_info *object_info_data,
+ int stateless_rpc, int fd_out);
+
+#endif /* FETCH_OBJECT_INFO_H */
diff --git a/meson.build b/meson.build
index 9434b56960..dfefcd3475 100644
--- a/meson.build
+++ b/meson.build
@@ -359,6 +359,7 @@ libgit_sources = [
'ewah/ewah_rlw.c',
'exec-cmd.c',
'fetch-negotiator.c',
+ 'fetch-object-info.c',
'fetch-pack.c',
'fmt-merge-msg.c',
'fsck.c',
diff --git a/odb.h b/odb.h
index 94754643d2..88a37febbf 100644
--- a/odb.h
+++ b/odb.h
@@ -339,6 +339,12 @@ struct object_info {
* or multiple times in the same source.
*/
struct odb_source_info *source_infop;
+
+ /*
+ * object-info protocol specific. Set by the protocol when the remote
+ * does not recognize the requested object.
+ */
+ unsigned int unrecognized:1;
};
/*
diff --git a/transport-helper.c b/transport-helper.c
index f195070788..623463dcea 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -784,6 +784,15 @@ static int fetch_refs(struct transport *transport,
return -1;
}
+static int fetch_object_info_helper(struct transport *transport)
+{
+ get_helper(transport);
+ if (process_connect(transport, 0))
+ return transport->vtable->fetch_object_info(transport);
+
+ die(_("object-info requires protocol v2"));
+}
+
struct push_update_ref_state {
struct ref *hint;
struct ref_push_report *report;
@@ -1330,6 +1339,7 @@ static struct transport_vtable vtable = {
.get_refs_list = get_refs_list,
.get_bundle_uri = get_bundle_uri,
.fetch_refs = fetch_refs,
+ .fetch_object_info = fetch_object_info_helper,
.push_refs = push_refs,
.connect = connect_helper,
.disconnect = release_helper
diff --git a/transport-internal.h b/transport-internal.h
index 051f3ab0dc..60db0bedcd 100644
--- a/transport-internal.h
+++ b/transport-internal.h
@@ -45,6 +45,14 @@ struct transport_vtable {
**/
int (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);
+ /*
+ * Fetch object info (only size currently) from remote without
+ * downloading the objects.
+ *
+ * Uses object-info capability of v2 protocol.
+ */
+ int (*fetch_object_info)(struct transport *transport);
+
/**
* Push the objects and refs. Send the necessary objects, and
* then, for any refs where peer_ref is set and
diff --git a/transport.c b/transport.c
index fc144f0aed..9342680531 100644
--- a/transport.c
+++ b/transport.c
@@ -9,6 +9,7 @@
#include "hook.h"
#include "pkt-line.h"
#include "fetch-pack.h"
+#include "fetch-object-info.h"
#include "remote.h"
#include "connect.h"
#include "send-pack.h"
@@ -432,6 +433,48 @@ static int get_bundle_uri(struct transport *transport)
transport->bundles, stateless_rpc);
}
+static int fetch_object_info_via_pack(struct transport *transport)
+{
+ int ret = 0;
+ struct git_transport_data *data = transport->data;
+ struct packet_reader reader;
+ struct object_info_args args = { 0 };
+
+ args.server_options = transport->server_options;
+ args.oids = transport->smart_options->object_info_oids;
+ args.object_info_options = transport->smart_options->object_info_options;
+ string_list_sort(args.object_info_options);
+
+ connect_setup(transport, 0);
+ packet_reader_init(&reader, data->fd[0], NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_GENTLE_ON_EOF |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+
+ data->version = discover_version(&reader);
+ transport->hash_algo = reader.hash_algo;
+
+ ret = fetch_object_info(data->version, &args, &reader,
+ data->options.object_info_data,
+ transport->stateless_rpc, data->fd[1]);
+
+ close(data->fd[0]);
+ if (data->fd[1] >= 0)
+ close(data->fd[1]);
+ if (finish_connect(data->conn))
+ ret = -1;
+ data->conn = NULL;
+
+ return ret;
+}
+
+int transport_fetch_object_info(struct transport *transport)
+{
+ if (!transport->vtable->fetch_object_info)
+ die(_("remote does not support object-info"));
+ return transport->vtable->fetch_object_info(transport);
+}
+
static int fetch_refs_via_pack(struct transport *transport,
int nr_heads, struct ref **to_fetch)
{
@@ -1004,6 +1047,7 @@ static struct transport_vtable taken_over_vtable = {
.get_refs_list = get_refs_via_connect,
.get_bundle_uri = get_bundle_uri,
.fetch_refs = fetch_refs_via_pack,
+ .fetch_object_info = fetch_object_info_via_pack,
.push_refs = git_transport_push,
.disconnect = disconnect_git
};
@@ -1169,6 +1213,7 @@ static struct transport_vtable builtin_smart_vtable = {
.get_refs_list = get_refs_via_connect,
.get_bundle_uri = get_bundle_uri,
.fetch_refs = fetch_refs_via_pack,
+ .fetch_object_info = fetch_object_info_via_pack,
.push_refs = git_transport_push,
.connect = connect_git,
.disconnect = disconnect_git
diff --git a/transport.h b/transport.h
index 7e5867cffa..a7869d18e0 100644
--- a/transport.h
+++ b/transport.h
@@ -55,6 +55,10 @@ struct git_transport_options {
* common commits to this oidset instead of fetching any packfiles.
*/
struct oidset *acked_commits;
+
+ struct oid_array *object_info_oids;
+ struct object_info *object_info_data;
+ struct string_list *object_info_options;
};
enum transport_family {
@@ -309,6 +313,11 @@ int transport_get_remote_bundle_uri(struct transport *transport);
const struct git_hash_algo *transport_get_hash_algo(struct transport *transport);
int transport_fetch_refs(struct transport *transport, struct ref *refs);
+/*
+ * Fetch the object info from remote
+ */
+int transport_fetch_object_info(struct transport *transport);
+
/*
* If this flag is set, unlocking will avoid to call non-async-signal-safe
* functions. This will necessarily leave behind some data structures which
--
2.54.0
^ permalink raw reply related
* [PATCH GSoC v20 12/13] cat-file: add remote-object-info to batch-command
From: Pablo Sabater @ 2026-07-18 21:50 UTC (permalink / raw)
To: git
Cc: pabloosabaterr, chandrapratap3519, chriscool, eric.peijian,
gitster, jltobler, karthik.188, peff, toon, szeder.dev
In-Reply-To: <20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com>
From: Eric Ju <eric.peijian@gmail.com>
Since the info command in cat-file --batch-command prints object
info for a given object, it is natural to add another command in
cat-file --batch-command to print object info for a given object
from a remote.
Add remote-object-info command to cat-file --batch-command.
While info takes object ids one at a time, this creates overhead when
making requests to a server. So remote-object-info instead can take
multiple object ids at once.
The cat-file --batch-command command is generally implemented in the
following manner:
- Receive and parse input from user
- Call respective function attached to command
- Get object info, print object info
In --buffer mode, this changes to:
- Receive and parse input from user
- Store respective function attached to command in a queue
- After flush, loop through commands in queue
- Call respective function attached to command
- Get object info, print object info
Notice how the getting and printing of object info is accomplished one
at a time. As described above, this creates a problem for making
requests to a server. Therefore, remote-object-info is implemented in
the following manner:
- Receive and parse input from user
If command is remote-object-info:
- Get object info from remote
- Loop through and print each object info
Else:
- Call respective function attached to command
- Parse input, get object info, print object info
And finally for --buffer mode remote-object-info:
- Receive and parse input from user
- Store respective function attached to command in a queue
- After flush, loop through commands in queue:
If command is remote-object-info:
- Get object info from remote
- Loop through and print each object info
Else:
- Call respective function attached to command
- Get object info, print object info
To summarize, remote-object-info gets object info from the remote and
then loops through the object info passed in, printing the info.
In order for remote-object-info to avoid remote communication
overhead in the non-buffer mode, the objects are passed in as such:
remote-object-info <remote> <oid> <oid> ... <oid>
rather than
remote-object-info <remote> <oid>
remote-object-info <remote> <oid>
...
remote-object-info <remote> <oid>
Placeholders in the format are validated against an allow-list of the
atoms the remote path supports: "objectname" and "objectsize".
Unsupported atoms expand to an empty string, honoring how for-each-ref
handles known but inapplicable atoms.
Without this, atoms like %(objecttype) would mark data->info.typep and
because the server only sends size, type_name() would later crash.
As extra safety, even outside of the remote path, initialize
expand_data's type to OBJ_BAD and handle type_name() returning NULL.
Helped-by: Jonathan Tan <jonathantanmy@google.com>
Helped-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>
Signed-off-by: Calvin Wan <calvinwan@google.com>
Signed-off-by: Eric Ju <eric.peijian@gmail.com>
[pablo: added the atom allow-list validation]
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
Documentation/git-cat-file.adoc | 24 +-
builtin/cat-file.c | 181 ++++++++-
object-file.c | 10 +
odb.h | 3 +
t/meson.build | 1 +
t/t1017-cat-file-remote-object-info.sh | 719 +++++++++++++++++++++++++++++++++
6 files changed, 930 insertions(+), 8 deletions(-)
diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc
index 86b9181599..b9cd958e11 100644
--- a/Documentation/git-cat-file.adoc
+++ b/Documentation/git-cat-file.adoc
@@ -169,6 +169,13 @@ info <object>::
Print object info for object reference `<object>`. This corresponds to the
output of `--batch-check`.
+remote-object-info <remote> <object>...::
+ Print object info for object references `<object>` at specified
+ `<remote>` without downloading objects from the remote.
+ Raise an error when the `object-info` capability is not supported by the remote.
+ Raise an error when no object references are provided.
+ This command may be combined with `--buffer`.
+
flush::
Used with `--buffer` to execute all preceding commands that were issued
since the beginning or since the last flush was issued. When `--buffer`
@@ -301,7 +308,8 @@ one per line, and print information based on the command given. With
`--batch-command`, the `info` command followed by an object will print
information about the object the same way `--batch-check` would, and the
`contents` command followed by an object prints contents in the same way
-`--batch` would.
+`--batch` would. The `remote-object-info` command followed by a remote and
+objects IDs prints object info from the remote without downloading the objects.
You can specify the information shown for each object by using a custom
`<format>`. The `<format>` is copied literally to stdout for each
@@ -330,7 +338,7 @@ newline. The available atoms are:
`deltabase`::
If the object is stored as a delta on-disk, this expands to the
full hex representation of the delta base object name.
- Otherwise, expands to the null OID (all zeroes). See `CAVEATS`
+ Otherwise, expands to the null OID (all zeroes). See `CAVEATS` section
below.
`rest`::
@@ -340,8 +348,14 @@ newline. The available atoms are:
after that first run of whitespace (i.e., the "rest" of the
line) are output in place of the `%(rest)` atom.
+The command `remote-object-info` only supports the `%(objectname)` and
+`%(objectsize)` placeholders. See `CAVEATS` below for more information.
+
If no format is specified, the default format is `%(objectname)
-%(objecttype) %(objectsize)`.
+%(objecttype) %(objectsize)`, except for `remote-object-info` commands which
+use `%(objectname) %(objectsize)` because "%(objecttype)" is not supported yet.
+WARNING: When "%(objecttype)" is supported, the default format WILL be unified,
+so DO NOT RELY on the current default format to stay the same!!!
If `--batch` is specified, or if `--batch-command` is used with the `contents`
command, the object information is followed by the object contents (consisting
@@ -438,6 +452,10 @@ scripting purposes.
CAVEATS
-------
+Note that only `%(objectname)` and `%(objectsize)` are currently
+supported by the `remote-object-info` command. Using any other placeholder in
+the format string will return an empty string in its position.
+
Note that the sizes of objects on disk are reported accurately, but care
should be taken in drawing conclusions about which refs or objects are
responsible for disk usage. The size of a packed non-delta object may be
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 03afc44c5e..8994b04d15 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -29,6 +29,22 @@
#include "promisor-remote.h"
#include "mailmap.h"
#include "write-or-die.h"
+#include "alias.h"
+#include "remote.h"
+#include "transport.h"
+
+/*
+ * Maximum length for a remote URL. While no universal standard exists,
+ * 8K is assumed to be a reasonable limit.
+ */
+#define MAX_REMOTE_URL_LEN (8 * 1024)
+
+/* Maximum number of objects allowed in a single remote-object-info request. */
+#define MAX_ALLOWED_OBJ_LIMIT 10000
+
+/* Maximum input size permitted for the remote-object-info command. */
+#define MAX_REMOTE_OBJ_INFO_LINE \
+ (MAX_REMOTE_URL_LEN + MAX_ALLOWED_OBJ_LIMIT * (GIT_MAX_HEXSZ + 1))
enum batch_mode {
BATCH_MODE_CONTENTS,
@@ -317,8 +333,19 @@ struct expand_data {
* optimized out.
*/
unsigned skip_object_info : 1;
+
+ /*
+ * Flags about when an object info is being fetched from remote.
+ */
+ unsigned is_remote:1;
+};
+
+#define EXPAND_DATA_INIT { .mode = S_IFINVALID, .type = OBJ_BAD }
+
+static const char *remote_object_info_atoms[] = {
+ "objectname",
+ "objectsize",
};
-#define EXPAND_DATA_INIT { .mode = S_IFINVALID }
static int is_atom(const char *atom, const char *s, int slen)
{
@@ -329,14 +356,31 @@ static int is_atom(const char *atom, const char *s, int slen)
static int expand_atom(struct strbuf *sb, const char *atom, int len,
struct expand_data *data)
{
+ if (data->is_remote) {
+ size_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);
+ for (i = 0; i < allowed_nr; i++)
+ if (is_atom(remote_object_info_atoms[i], atom, len))
+ break;
+
+ /*
+ * On remote, skip unsupported atoms returning an empty sb,
+ * honoring how for-each-ref handles known but inapplicable
+ * atoms (e.g. %(tagger)).
+ */
+ if (i == allowed_nr)
+ return 1;
+ }
+
if (is_atom("objectname", atom, len)) {
if (!data->mark_query)
strbuf_add_oid_hex(sb, &data->oid);
} else if (is_atom("objecttype", atom, len)) {
- if (data->mark_query)
+ if (data->mark_query) {
data->info.typep = &data->type;
- else
- strbuf_addstr(sb, type_name(data->type));
+ } else {
+ const char *t = type_name(data->type);
+ strbuf_addstr(sb, t ? t : "");
+ }
} else if (is_atom("objectsize", atom, len)) {
if (data->mark_query)
data->info.sizep = &data->size;
@@ -636,6 +680,65 @@ static void batch_one_object(const char *obj_name,
object_context_release(&ctx);
}
+static int get_remote_info(int argc,
+ const char **argv,
+ struct object_info **remote_object_info,
+ struct oid_array *object_info_oids)
+{
+ int retval = 0;
+ struct remote *remote = NULL;
+ struct object_id oid;
+ struct string_list object_info_options = STRING_LIST_INIT_NODUP;
+ struct transport *gtransport;
+
+ remote = remote_get(argv[0]);
+ if (!remote)
+ die(_("must supply valid remote when using remote-object-info"));
+
+ oid_array_clear(object_info_oids);
+ for (size_t i = 1; i < argc; i++) {
+ if (get_oid_hex(argv[i], &oid)) {
+ size_t len = strlen(argv[i]);
+
+ if (len < the_hash_algo->hexsz && len >= 4) {
+ size_t j;
+ for (j = 0; j < len; j++)
+ if (!isxdigit(argv[i][j]))
+ break;
+ if (j == len)
+ die(_("remote-object-info does not support "
+ "short oids, %d characters required"),
+ (int)the_hash_algo->hexsz);
+ }
+ die(_("not a valid object name '%s'"), argv[i]);
+ }
+ oid_array_append(object_info_oids, &oid);
+ }
+
+ if (!object_info_oids->nr)
+ die(_("remote-object-info requires objects"));
+
+ gtransport = transport_get(remote, NULL);
+
+ if (!gtransport->smart_options) {
+ retval = -1;
+ goto cleanup;
+ }
+
+ CALLOC_ARRAY(*remote_object_info, object_info_oids->nr);
+ gtransport->smart_options->object_info_oids = object_info_oids;
+
+ string_list_append(&object_info_options, "size");
+
+ gtransport->smart_options->object_info_options = &object_info_options;
+ gtransport->smart_options->object_info_data = *remote_object_info;
+ retval = transport_fetch_object_info(gtransport);
+cleanup:
+ string_list_clear(&object_info_options, 0);
+ transport_disconnect(gtransport);
+ return retval;
+}
+
struct object_cb_data {
struct batch_options *opt;
struct expand_data *expand;
@@ -717,6 +820,73 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,
load_mailmap();
}
+static void parse_cmd_remote_object_info(struct batch_options *opt,
+ const char *line, struct strbuf *output,
+ struct expand_data *data)
+{
+ int count;
+ const char **argv;
+ char *line_to_split;
+ struct object_info *remote_object_info = NULL;
+ struct oid_array object_info_oids = OID_ARRAY_INIT;
+ const char *saved_format = opt->format;
+
+ if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)
+ die(_("remote-object-info command too long"));
+ /*
+ * TODO: Use the default format once %(objecttype) is supported.
+ */
+ if (!opt->format)
+ opt->format = "%(objectname) %(objectsize)";
+
+ line_to_split = xstrdup(line);
+ count = split_cmdline(line_to_split, &argv);
+ if (count < 0)
+ die(_("remote-object-info: failed to parse command line: %s"),
+ split_cmdline_strerror(count));
+ if (count - 1 > MAX_ALLOWED_OBJ_LIMIT)
+ die(_("remote-object-info supports at most %d objects"),
+ MAX_ALLOWED_OBJ_LIMIT);
+
+ if (get_remote_info(count, argv, &remote_object_info,
+ &object_info_oids))
+ die(_("failed to get object info from the remote: %s"), argv[0]);
+
+ data->skip_object_info = 1;
+ for (size_t i = 0; i < object_info_oids.nr; i++) {
+ data->oid = object_info_oids.oid[i];
+
+ if (remote_object_info[i].unrecognized) {
+ report_object_status(opt, oid_to_hex(&data->oid),
+ &data->oid, "missing");
+ continue;
+ }
+
+ if (remote_object_info[i].sizep) {
+ /*
+ * When reaching here, it means remote-object-info can retrieve
+ * information from server without downloading them.
+ */
+ data->size = *remote_object_info[i].sizep;
+ opt->batch_mode = BATCH_MODE_INFO;
+ data->is_remote = 1;
+ batch_object_write(argv[i + 1], output, opt, data, NULL, 0);
+ data->is_remote = 0;
+ } else {
+ report_object_status(opt, oid_to_hex(&data->oid), &data->oid, "missing");
+ }
+ }
+ data->skip_object_info = 0;
+ opt->format = saved_format;
+
+ for (size_t i = 0; i < object_info_oids.nr; i++)
+ free_object_info_contents(&remote_object_info[i]);
+ free(line_to_split);
+ free(argv);
+ free(remote_object_info);
+ oid_array_clear(&object_info_oids);
+}
+
static void dispatch_calls(struct batch_options *opt,
struct strbuf *output,
struct expand_data *data,
@@ -747,9 +917,10 @@ static const struct parse_cmd {
unsigned takes_args;
} commands[] = {
{ "contents", parse_cmd_contents, 1 },
- { "info", parse_cmd_info, 1 },
{ "flush", NULL, 0 },
+ { "info", parse_cmd_info, 1 },
{ "mailmap", parse_cmd_mailmap, 1 },
+ { "remote-object-info", parse_cmd_remote_object_info, 1 },
};
static void batch_objects_command(struct batch_options *opt,
diff --git a/object-file.c b/object-file.c
index 6453b1d6fa..07f019a0f6 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1694,3 +1694,13 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
return &transaction->base;
}
+
+void free_object_info_contents(struct object_info *object_info)
+{
+ if (!object_info)
+ return;
+ free(object_info->typep);
+ free(object_info->sizep);
+ free(object_info->disk_sizep);
+ free(object_info->delta_base_oid);
+}
diff --git a/odb.h b/odb.h
index 88a37febbf..92fa414e2c 100644
--- a/odb.h
+++ b/odb.h
@@ -623,4 +623,7 @@ void parse_alternates(const char *string,
const char *relative_base,
struct strvec *out);
+/* Free pointers inside of object_info, but not object_info itself */
+void free_object_info_contents(struct object_info *object_info);
+
#endif /* ODB_H */
diff --git a/t/meson.build b/t/meson.build
index 8ae6ab6c5f..10241e3dcc 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -171,6 +171,7 @@ integration_tests = [
't1014-read-tree-confusing.sh',
't1015-read-index-unmerged.sh',
't1016-compatObjectFormat.sh',
+ 't1017-cat-file-remote-object-info.sh',
't1020-subdirectory.sh',
't1022-read-tree-partial-clone.sh',
't1050-large.sh',
diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh
new file mode 100755
index 0000000000..edc20394d8
--- /dev/null
+++ b/t/t1017-cat-file-remote-object-info.sh
@@ -0,0 +1,719 @@
+#!/bin/sh
+
+test_description='git cat-file --batch-command with remote-object-info command'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-cat-file.sh
+
+hello_content="Hello World"
+hello_size=$(strlen "$hello_content")
+hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin)
+hello_short_oid=$(git rev-parse --short "$hello_oid")
+
+unstored_content="Hello Git"
+unstored_oid=$(echo_without_newline "$unstored_content" | git hash-object --stdin)
+
+# This is how we get 13:
+# 13 = <file mode> + <a_space> + <file name> + <a_null>, where
+# file mode is 100644, which is 6 characters;
+# file name is hello, which is 5 characters
+# a space is 1 character and a null is 1 character
+tree_size=$(($(test_oid rawsz) + 13))
+
+commit_message="Initial commit"
+
+# This is how we get 137:
+# 137 = <tree header> + <a_space> + <a newline> +
+# <Author line> + <a newline> +
+# <Committer line> + <a newline> +
+# <a newline> +
+# <commit message length>
+# An easier way to calculate is: 1. use `git cat-file commit <commit hash> | wc -c`,
+# to get 177, 2. then deduct 40 hex characters to get 137
+commit_size=$(($(test_oid hexsz) + 137))
+
+tag_header_without_oid="type blob
+tag hellotag
+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+tag_header_without_timestamp="object $hello_oid
+$tag_header_without_oid"
+tag_description="This is a tag"
+tag_content="$tag_header_without_timestamp 0 +0000
+
+$tag_description"
+
+tag_oid=$(echo_without_newline "$tag_content" | git hash-object -t tag --stdin -w)
+tag_size=$(strlen "$tag_content")
+
+set_transport_variables () {
+ hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin)
+ tree_oid=$(git -C "$1" write-tree)
+ commit_oid=$(echo_without_newline "$commit_message" | git -C "$1" commit-tree $tree_oid)
+ tag_oid=$(echo_without_newline "$tag_content" | git -C "$1" hash-object -t tag --stdin -w)
+ tag_size=$(strlen "$tag_content")
+}
+
+# This section tests --batch-command with remote-object-info command
+# Since "%(objecttype)" is currently not supported by the command remote-object-info ,
+# the filters are set to "%(objectname) %(objectsize)" in some test cases.
+
+# Test --batch-command remote-object-info with 'git://' transport with
+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+start_git_daemon --export-all
+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent
+
+test_expect_success 'create repo to be served by git-daemon' '
+ git init "$daemon_parent" &&
+ echo_without_newline "$hello_content" > $daemon_parent/hello &&
+ git -C "$daemon_parent" update-index --add hello &&
+ git -C "$daemon_parent" config transfer.advertiseobjectinfo true &&
+ git clone "$GIT_DAEMON_URL/parent" -n "$daemon_parent/daemon_client_empty"
+'
+
+test_expect_success 'batch-command remote-object-info git://' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $tree_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info git:// multiple sha1 per line' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info git:// default filter' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ git cat-file --batch-command >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'remote-object-info does not change the default format of info' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ local_content="local object" &&
+ local_oid=$(echo_without_newline "$local_content" | git hash-object -w --stdin) &&
+ local_size=$(strlen "$local_content") &&
+
+ echo "$local_oid blob $local_size" >expect &&
+ echo "$hello_oid $hello_size" >>expect &&
+ echo "$local_oid blob $local_size" >>expect &&
+
+ git cat-file --batch-command >actual <<-EOF &&
+ info $local_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
+ info $local_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command --buffer remote-object-info git://' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid $tree_oid
+ remote-object-info "$GIT_DAEMON_URL/parent" $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ flush
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command -Z remote-object-info git:// default filter' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ printf "%s\0" "$hello_oid $hello_size" >expect &&
+ printf "%s\0" "$tree_oid $tree_size" >>expect &&
+ printf "%s\0" "$commit_oid $commit_size" >>expect &&
+ printf "%s\0" "$tag_oid $tag_size" >>expect &&
+
+ printf "%s\0" "$hello_oid missing" >>expect &&
+ printf "%s\0" "$tree_oid missing" >>expect &&
+ printf "%s\0" "$commit_oid missing" >>expect &&
+ printf "%s\0" "$tag_oid missing" >>expect &&
+
+ batch_input="remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid
+remote-object-info $GIT_DAEMON_URL/parent $commit_oid $tag_oid
+info $hello_oid
+info $tree_oid
+info $commit_oid
+info $tag_oid
+" &&
+ echo_without_newline_nul "$batch_input" >commands_null_delimited &&
+
+ git cat-file --batch-command -Z < commands_null_delimited >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'remote-object-info does not support short oids' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ test_must_fail git cat-file --batch-command 2>err <<-EOF &&
+ remote-object-info $GIT_DAEMON_URL/parent $hello_short_oid
+ EOF
+ test_grep "does not support short oids" err
+ )
+'
+
+test_expect_success 'remote-object-info does not die on missing oid like info' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ git cat-file --batch-command >local <<-EOF &&
+ info $unstored_oid
+ EOF
+ git cat-file --batch-command >remote <<-EOF &&
+ remote-object-info $GIT_DAEMON_URL/parent $unstored_oid
+ EOF
+ test_cmp local remote
+ )
+'
+
+# This tests depends on %(objecttype) not being supported yet, once supported
+# it needs to be updated.
+test_expect_success 'unsupported placeholder on remote returns empty string' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ echo "" >expect &&
+ git cat-file --batch-command="%(objecttype)" >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+# Test --batch-command remote-object-info with 'git://' and
+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
+test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '
+ (
+ git -C "$daemon_parent" config transfer.advertiseobjectinfo false &&
+ set_transport_variables "$daemon_parent" &&
+
+ test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info $GIT_DAEMON_URL/parent $hello_oid $tree_oid $commit_oid $tag_oid
+ EOF
+ test_grep "object-info capability is not enabled on the server" err &&
+
+ # revert server state back
+ git -C "$daemon_parent" config transfer.advertiseobjectinfo true
+
+ )
+'
+
+stop_git_daemon
+
+# Test --batch-command remote-object-info with 'file://' transport with
+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability
+# shellcheck disable=SC2016
+test_expect_success 'create repo to be served by file:// transport' '
+ git init server &&
+ git -C server config protocol.version 2 &&
+ git -C server config transfer.advertiseobjectinfo true &&
+ echo_without_newline "$hello_content" > server/hello &&
+ git -C server update-index --add hello &&
+ git clone -n "file://$(pwd)/server" file_client_empty
+'
+
+test_expect_success 'batch-command remote-object-info file://' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ cd file_client_empty &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "file://${server_path}" $hello_oid
+ remote-object-info "file://${server_path}" $tree_oid
+ remote-object-info "file://${server_path}" $commit_oid
+ remote-object-info "file://${server_path}" $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info file:// multiple sha1 per line' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ cd file_client_empty &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "file://${server_path}" $hello_oid $tree_oid $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command --buffer remote-object-info file://' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ cd file_client_empty &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF &&
+ remote-object-info "file://${server_path}" $hello_oid $tree_oid
+ remote-object-info "file://${server_path}" $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ flush
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info file:// default filter' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ cd file_client_empty &&
+
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ git cat-file --batch-command >actual <<-EOF &&
+ remote-object-info "file://${server_path}" $hello_oid $tree_oid
+ remote-object-info "file://${server_path}" $commit_oid $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command -Z remote-object-info file:// default filter' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ cd file_client_empty &&
+
+ printf "%s\0" "$hello_oid $hello_size" >expect &&
+ printf "%s\0" "$tree_oid $tree_size" >>expect &&
+ printf "%s\0" "$commit_oid $commit_size" >>expect &&
+ printf "%s\0" "$tag_oid $tag_size" >>expect &&
+
+ printf "%s\0" "$hello_oid missing" >>expect &&
+ printf "%s\0" "$tree_oid missing" >>expect &&
+ printf "%s\0" "$commit_oid missing" >>expect &&
+ printf "%s\0" "$tag_oid missing" >>expect &&
+
+ batch_input="remote-object-info \"file://${server_path}\" $hello_oid $tree_oid
+remote-object-info \"file://${server_path}\" $commit_oid $tag_oid
+info $hello_oid
+info $tree_oid
+info $commit_oid
+info $tag_oid
+" &&
+ echo_without_newline_nul "$batch_input" >commands_null_delimited &&
+
+ git cat-file --batch-command -Z < commands_null_delimited >actual &&
+ test_cmp expect actual
+ )
+'
+
+# Test --batch-command remote-object-info with 'file://' and
+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
+test_expect_success 'batch-command remote-object-info file:// fails when transfer.advertiseobjectinfo=false' '
+ (
+ set_transport_variables "server" &&
+ server_path="$(pwd)/server" &&
+ git -C "${server_path}" config transfer.advertiseobjectinfo false &&
+
+ test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info "file://${server_path}" $hello_oid $tree_oid $commit_oid $tag_oid
+ EOF
+ test_grep "object-info capability is not enabled on the server" err &&
+
+ # revert server state back
+ git -C "${server_path}" config transfer.advertiseobjectinfo true
+ )
+'
+
+# Test --batch-command remote-object-info with 'http://' transport with
+# transfer.advertiseobjectinfo set to true, i.e. server has object-info capability
+
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'create repo to be served by http:// transport' '
+ git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo true &&
+ echo_without_newline "$hello_content" > $HTTPD_DOCUMENT_ROOT_PATH/http_parent/hello &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" update-index --add hello &&
+ git clone "$HTTPD_URL/smart/http_parent" -n "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty"
+'
+
+test_expect_success 'batch-command remote-object-info http://' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
+ remote-object-info "$HTTPD_URL/smart/http_parent" $tree_oid
+ remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid
+ remote-object-info "$HTTPD_URL/smart/http_parent" $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info http:// one line' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command --buffer remote-object-info http://' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
+
+ # These results prove remote-object-info can get object info from the remote
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ # These results prove remote-object-info did not download objects from the remote
+ echo "$hello_oid missing" >>expect &&
+ echo "$tree_oid missing" >>expect &&
+ echo "$commit_oid missing" >>expect &&
+ echo "$tag_oid missing" >>expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize)" --buffer >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid
+ remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid $tag_oid
+ info $hello_oid
+ info $tree_oid
+ info $commit_oid
+ info $tag_oid
+ flush
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command remote-object-info http:// default filter' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
+
+ echo "$hello_oid $hello_size" >expect &&
+ echo "$tree_oid $tree_size" >>expect &&
+ echo "$commit_oid $commit_size" >>expect &&
+ echo "$tag_oid $tag_size" >>expect &&
+
+ git cat-file --batch-command >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid
+ remote-object-info "$HTTPD_URL/smart/http_parent" $commit_oid $tag_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'batch-command -Z remote-object-info http:// default filter' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_client_empty" &&
+
+ printf "%s\0" "$hello_oid $hello_size" >expect &&
+ printf "%s\0" "$tree_oid $tree_size" >>expect &&
+ printf "%s\0" "$commit_oid $commit_size" >>expect &&
+ printf "%s\0" "$tag_oid $tag_size" >>expect &&
+
+ batch_input="remote-object-info $HTTPD_URL/smart/http_parent $hello_oid $tree_oid
+remote-object-info $HTTPD_URL/smart/http_parent $commit_oid $tag_oid
+" &&
+ echo_without_newline_nul "$batch_input" >commands_null_delimited &&
+
+ git cat-file --batch-command -Z < commands_null_delimited >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'remote-object-info fails on unsupported filter option (objectsize:disk)' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+
+ echo "$hello_oid " >expect &&
+
+ git cat-file --batch-command="%(objectname) %(objectsize:disk)" >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'remote-object-info fails on unsupported filter option (deltabase)' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+
+ echo "" >expect &&
+
+ git cat-file --batch-command="%(deltabase)" >actual <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'remote-object-info fails on server with legacy protocol' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+
+ test_must_fail git -c protocol.version=0 cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
+ EOF
+ test_grep "object-info requires protocol v2" err
+ )
+'
+
+test_expect_success 'remote-object-info fails on server with legacy protocol with default filter' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+
+ test_must_fail git -c protocol.version=0 cat-file --batch-command 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid
+ EOF
+ test_grep "object-info requires protocol v2" err
+ )
+'
+
+test_expect_success 'remote-object-info fails on malformed OID' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ malformed_object_id="this_id_is_not_valid" &&
+
+ test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $malformed_object_id
+ EOF
+ test_grep "not a valid object name '$malformed_object_id'" err
+ )
+'
+
+test_expect_success 'remote-object-info fails on malformed OID with default filter' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ malformed_object_id="this_id_is_not_valid" &&
+
+ test_must_fail git cat-file --batch-command 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $malformed_object_id
+ EOF
+ test_grep "not a valid object name '$malformed_object_id'" err
+ )
+'
+
+test_expect_success 'remote-object-info fails on not providing OID' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ cd "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+
+ test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent"
+ EOF
+ test_grep "remote-object-info requires objects" err
+ )
+'
+
+
+# Test --batch-command remote-object-info with 'http://' transport and
+# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
+test_expect_success 'batch-command remote-object-info http:// fails when transfer.advertiseobjectinfo=false ' '
+ (
+ set_transport_variables "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo false &&
+
+ test_must_fail git cat-file --batch-command="%(objectname) %(objectsize)" 2>err <<-EOF &&
+ remote-object-info "$HTTPD_URL/smart/http_parent" $hello_oid $tree_oid $commit_oid $tag_oid
+ EOF
+ test_grep "object-info capability is not enabled on the server" err &&
+
+ # revert server state back
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config transfer.advertiseobjectinfo true
+ )
+'
+
+# DO NOT add non-httpd-specific tests here, because the last part of this
+# test script is only executed when httpd is available and enabled.
+
+test_done
--
2.54.0
^ permalink raw reply related
* [PATCH GSoC v20 13/13] cat-file: make remote-object-info allow-list adapt to the server
From: Pablo Sabater @ 2026-07-18 21:50 UTC (permalink / raw)
To: git
Cc: pabloosabaterr, chandrapratap3519, chriscool, eric.peijian,
gitster, jltobler, karthik.188, peff, toon, szeder.dev
In-Reply-To: <20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com>
The static allow-list in expand_atom() is hardcoded to allow only
"objectname" and "objectsize" for remote queries. This works because,
up to this point, servers will either support object-info with name
and size or they do not support them at all.
As object-info gains new capabilities, we cannot expect different
servers with different Git versions to have the same object-info
capabilities. Therefore, the client needs to adapt its allow-list to
what the server advertises.
The client now:
1. Requests the protocol option that the placeholder refers to (i.e.
"size" for "%(objectsize)").
2. Drops any requested option that the server does not advertise in
fetch_object_info().
3. Maps the remaining advertised options back to their placeholders and
populates remote_allowed_atoms.
4. Uses remote_allowed_atoms in expand_atom(), preserving the previous
behavior for supported placeholders.
For example, if the client requests "%(objectsize) %(objecttype)" and
the server only supports 'size', then the client only requests 'size'.
The server returns the size (i.e "42") "%(objectsize)" is expanded
normally while "%(objecttype)" expands to an empty string:
"42 "
Note that the empty string expansion is only for known but unsupported
placeholders. "%(objectcolor)" which doesn't exist would die().
This honors what for-each-ref does for known but inapplicable atoms
(placeholders).
Move object_info_options out of get_remote_info() so the caller which
has data can select what options will be requested instead of requesting
always size.
Move batch_object_write() out so output is always produced.
If there are no supported attributes, the output is a blank line.
Include "type" in the object_info_options even though the client does
not yet know how to parse the server's "type" capability.
As a result, "type" is always filtered out, allowing the tests to verify
that known but unsupported placeholders expand to an empty string.
Since the filter removes options by swapping with the last element,
the list is no longer kept sorted. Drop the pre-sort in
fetch_object_info_via_pack() and use the unsorted string_list lookup
for the response header. This has no effect in performance as the list
can only be two entries long ('size' and 'type').
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
builtin/cat-file.c | 94 ++++++++++++++++++++++------------
fetch-object-info.c | 20 +++++++-
fetch-object-info.h | 3 ++
t/t1017-cat-file-remote-object-info.sh | 28 ++++++++++
transport.c | 1 -
5 files changed, 111 insertions(+), 35 deletions(-)
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 8994b04d15..2007b0d4bb 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -338,15 +338,18 @@ struct expand_data {
* Flags about when an object info is being fetched from remote.
*/
unsigned is_remote:1;
-};
-
-#define EXPAND_DATA_INIT { .mode = S_IFINVALID, .type = OBJ_BAD }
-static const char *remote_object_info_atoms[] = {
- "objectname",
- "objectsize",
+ /*
+ * List of atoms (i.e. "objectsize") that the server supports. Built
+ * from the server's object-info advertised capabilities.
+ */
+ struct string_list remote_allowed_atoms;
};
+#define EXPAND_DATA_INIT { .mode = S_IFINVALID, \
+ .type = OBJ_BAD, \
+ .remote_allowed_atoms = STRING_LIST_INIT_NODUP }
+
static int is_atom(const char *atom, const char *s, int slen)
{
int alen = strlen(atom);
@@ -357,17 +360,12 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,
struct expand_data *data)
{
if (data->is_remote) {
- size_t i, allowed_nr = ARRAY_SIZE(remote_object_info_atoms);
- for (i = 0; i < allowed_nr; i++)
- if (is_atom(remote_object_info_atoms[i], atom, len))
+ size_t i;
+ for (i = 0; i < data->remote_allowed_atoms.nr; i++)
+ if (is_atom(data->remote_allowed_atoms.items[i].string,
+ atom, len))
break;
-
- /*
- * On remote, skip unsupported atoms returning an empty sb,
- * honoring how for-each-ref handles known but inapplicable
- * atoms (e.g. %(tagger)).
- */
- if (i == allowed_nr)
+ if (i == data->remote_allowed_atoms.nr)
return 1;
}
@@ -683,12 +681,12 @@ static void batch_one_object(const char *obj_name,
static int get_remote_info(int argc,
const char **argv,
struct object_info **remote_object_info,
- struct oid_array *object_info_oids)
+ struct oid_array *object_info_oids,
+ struct string_list *object_info_options)
{
int retval = 0;
struct remote *remote = NULL;
struct object_id oid;
- struct string_list object_info_options = STRING_LIST_INIT_NODUP;
struct transport *gtransport;
remote = remote_get(argv[0]);
@@ -728,13 +726,10 @@ static int get_remote_info(int argc,
CALLOC_ARRAY(*remote_object_info, object_info_oids->nr);
gtransport->smart_options->object_info_oids = object_info_oids;
- string_list_append(&object_info_options, "size");
-
- gtransport->smart_options->object_info_options = &object_info_options;
+ gtransport->smart_options->object_info_options = object_info_options;
gtransport->smart_options->object_info_data = *remote_object_info;
retval = transport_fetch_object_info(gtransport);
cleanup:
- string_list_clear(&object_info_options, 0);
transport_disconnect(gtransport);
return retval;
}
@@ -820,6 +815,21 @@ static void parse_cmd_mailmap(struct batch_options *opt UNUSED,
load_mailmap();
}
+struct protocol_placeholder_entry {
+ const char *option;
+ const char *atom;
+};
+
+static const struct protocol_placeholder_entry remote_atom_map[] = {
+ {"size", "objectsize"},
+ {"type", "objecttype"},
+ /*
+ * Add new protocol options here. Even if the server doesn't support
+ * them the allow_list will drop them if the server doesn't advertise
+ * them.
+ */
+};
+
static void parse_cmd_remote_object_info(struct batch_options *opt,
const char *line, struct strbuf *output,
struct expand_data *data)
@@ -829,6 +839,7 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
char *line_to_split;
struct object_info *remote_object_info = NULL;
struct oid_array object_info_oids = OID_ARRAY_INIT;
+ struct string_list object_info_options = STRING_LIST_INIT_NODUP;
const char *saved_format = opt->format;
if (strlen(line) >= MAX_REMOTE_OBJ_INFO_LINE)
@@ -848,10 +859,22 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
die(_("remote-object-info supports at most %d objects"),
MAX_ALLOWED_OBJ_LIMIT);
+ if (data->info.sizep)
+ string_list_append(&object_info_options, "size");
+ if (data->info.typep)
+ string_list_append(&object_info_options, "type");
+
if (get_remote_info(count, argv, &remote_object_info,
- &object_info_oids))
+ &object_info_oids, &object_info_options))
die(_("failed to get object info from the remote: %s"), argv[0]);
+ string_list_clear(&data->remote_allowed_atoms, 0);
+ string_list_append(&data->remote_allowed_atoms, "objectname");
+ for (size_t i = 0; i < ARRAY_SIZE(remote_atom_map); i++)
+ if (unsorted_string_list_has_string(&object_info_options, remote_atom_map[i].option))
+ string_list_append(&data->remote_allowed_atoms,
+ remote_atom_map[i].atom);
+
data->skip_object_info = 1;
for (size_t i = 0; i < object_info_oids.nr; i++) {
data->oid = object_info_oids.oid[i];
@@ -862,25 +885,29 @@ static void parse_cmd_remote_object_info(struct batch_options *opt,
continue;
}
+ /*
+ * When reaching here, it means remote-object-info can retrieve
+ * information from server without downloading them.
+ */
if (remote_object_info[i].sizep) {
- /*
- * When reaching here, it means remote-object-info can retrieve
- * information from server without downloading them.
- */
data->size = *remote_object_info[i].sizep;
- opt->batch_mode = BATCH_MODE_INFO;
- data->is_remote = 1;
- batch_object_write(argv[i + 1], output, opt, data, NULL, 0);
- data->is_remote = 0;
- } else {
- report_object_status(opt, oid_to_hex(&data->oid), &data->oid, "missing");
}
+
+ if (remote_object_info[i].typep) {
+ data->type = *remote_object_info[i].typep;
+ }
+
+ opt->batch_mode = BATCH_MODE_INFO;
+ data->is_remote = 1;
+ batch_object_write(argv[i + 1], output, opt, data, NULL, 0);
+ data->is_remote = 0;
}
data->skip_object_info = 0;
opt->format = saved_format;
for (size_t i = 0; i < object_info_oids.nr; i++)
free_object_info_contents(&remote_object_info[i]);
+ string_list_clear(&object_info_options, 0);
free(line_to_split);
free(argv);
free(remote_object_info);
@@ -1200,6 +1227,7 @@ static int batch_objects(struct batch_options *opt)
cleanup:
strbuf_release(&input);
strbuf_release(&output);
+ string_list_clear(&data.remote_allowed_atoms, 0);
cfg->warn_on_object_refname_ambiguity = save_warning;
return retval;
}
diff --git a/fetch-object-info.c b/fetch-object-info.c
index 30475a1e87..ba7e179c44 100644
--- a/fetch-object-info.c
+++ b/fetch-object-info.c
@@ -55,6 +55,24 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar
case protocol_v2:
if (!server_supports_v2("object-info"))
die(_("object-info capability is not enabled on the server"));
+ /*
+ * When removing an element from the list it gets swapped by the
+ * last element, iterate backwards to prevent elements skipping
+ * evaluation.
+ *
+ * object_info_options->nr can be safely casted without overflow
+ * because the number of options is a small known number (the
+ * supported placeholders which currently are size and type).
+ */
+ for (int i = (int)args->object_info_options->nr - 1; i >= 0; i--)
+ if (!server_supports_feature("object-info",
+ args->object_info_options->items[i].string, 0))
+ unsorted_string_list_delete_item(args->object_info_options, i, 0);
+
+ /*
+ * Even if no options are left, we still send the oid so we get
+ * at least an existence check.
+ */
send_object_info_request(fd_out, args);
break;
case protocol_v1:
@@ -71,7 +89,7 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar
return -1;
}
- if (!string_list_has_string(args->object_info_options, reader->line))
+ if (!unsorted_string_list_has_string(args->object_info_options, reader->line))
return -1;
if (!strcmp(reader->line, "size")) {
diff --git a/fetch-object-info.h b/fetch-object-info.h
index 31aad98408..269cebb3f7 100644
--- a/fetch-object-info.h
+++ b/fetch-object-info.h
@@ -14,6 +14,9 @@ struct object_info;
/*
* Sends git-cat-file object-info command into the request buf and read the
* results from packets.
+ *
+ * Modifies args->object_info_options, on return it contains only the supported
+ * options by the server.
*/
int fetch_object_info(enum protocol_version version, struct object_info_args *args,
struct packet_reader *reader, struct object_info *object_info_data,
diff --git a/t/t1017-cat-file-remote-object-info.sh b/t/t1017-cat-file-remote-object-info.sh
index edc20394d8..116862f9d0 100755
--- a/t/t1017-cat-file-remote-object-info.sh
+++ b/t/t1017-cat-file-remote-object-info.sh
@@ -271,6 +271,34 @@ test_expect_success 'unsupported placeholder on remote returns empty string' '
)
'
+test_expect_success 'requesting only objectname echoes back' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ echo $hello_oid >expect &&
+ git cat-file --batch-command="%(objectname)" >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $hello_oid
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'objectname goes through existence check' '
+ (
+ set_transport_variables "$daemon_parent" &&
+ cd "$daemon_parent/daemon_client_empty" &&
+
+ echo "$unstored_oid missing" >expect &&
+
+ git cat-file --batch-command="%(objectname)" >actual <<-EOF &&
+ remote-object-info "$GIT_DAEMON_URL/parent" $unstored_oid
+ EOF
+
+ test_cmp expect actual
+ )
+'
+
# Test --batch-command remote-object-info with 'git://' and
# transfer.advertiseobjectinfo set to false, i.e. server does not have object-info capability
test_expect_success 'batch-command remote-object-info git:// fails when transfer.advertiseobjectinfo=false' '
diff --git a/transport.c b/transport.c
index 9342680531..f0a6a45547 100644
--- a/transport.c
+++ b/transport.c
@@ -443,7 +443,6 @@ static int fetch_object_info_via_pack(struct transport *transport)
args.server_options = transport->server_options;
args.oids = transport->smart_options->object_info_oids;
args.object_info_options = transport->smart_options->object_info_options;
- string_list_sort(args.object_info_options);
connect_setup(transport, 0);
packet_reader_init(&reader, data->fd[0], NULL, 0,
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] branch: report kind of checkout when rejecting delete
From: Junio C Hamano @ 2026-07-18 22:09 UTC (permalink / raw)
To: René Scharfe
Cc: Phillip Wood, Toon Claes, Patrick Steinhardt, Git List, stsp
In-Reply-To: <c7357faf-3d2b-46c6-99e7-88d3e2c72a77@web.de>
René Scharfe <l.s.r@web.de> writes:
>>> + int kind = branch_checkout_kind(name);
>>
>> Not "enum branch_checkout_kind" but "int"?
>
> Yes, it doesn't matter for the switch and is easier to print.
I do not understand the "print" part. I was probably in the last
group of people who was forced to switch from CPP macros to enum
and their argument was always "'print kind' in GDB gives symbolic
output". As "enum" is an glorified "int", wouldn't
int i_kind;
enum branch_checkout_kind e_kind;
BUG(_("we did not expect %d %d"), e_kind, i_kind);
do just what we expect?
>>> + switch (kind) {
>>> + case BRANCH_CHECKOUT_KIND_CHECKOUT:
>>> + error(_("cannot delete branch '%s' "
>>> + "used by worktree at '%s'"),
>>> + bname.buf, path);
>>> + break;
>>
>> We may want to be more explicit and say "cannot delete
>> branch 'frotz' checked out in worktree at '/tmp/nitfol'"
>> instead. Unless this is a catch-all entry for states that
>> are neither 'rebase', 'bisect', nor 'rebase-merges' but are
>> somehow otherwise in use, that is.
>>
>>> + case BRANCH_CHECKOUT_KIND_UPDATE_REF:
>>> + error(_("cannot delete branch '%s' "
>>> + "used by worktree at '%s' "
>>> + "for update-ref"),
>>> + bname.buf, path);
>>> + break;
>>
>> I was quite lost when searching for cases where this 'update-ref'
>> state might be encountered, and I still lack confidence. Can
>> we make the diagnostic message a bit friendlier to our users?
>>
>> For instance, something like: 'You are rebasing a history with
>> merges in that other worktree, and the tip of this branch will
>> be updated when that process completes, so you cannot delete
>> it from here.' (Naturally, I may have misidentified the exact
>> nature of the error, but this illustrates the level of detail and
>> user-facing clarity I hope to see.)
>
> That's quite long. Would it make sense to throw that update-ref
> case into the rebase bin, i.e. only distinguish between checkout,
> bisect and rebase?
Shortening a quite long expression down to digestable pieces is left
as an exercise for those with this particular itch to scratch ;-).
I do not personally mind if it ends up indistinguishable from other
"rebase" case (or unified the "kind" enum into one), but others may
have ideas to shorten the message to fit in the pattern we see
above.
Thanks.
^ permalink raw reply
* Re: [PATCH 0/9] object-file: move writing of loose objects into "loose" source
From: Junio C Hamano @ 2026-07-19 1:04 UTC (permalink / raw)
To: SZEDER Gábor; +Cc: Patrick Steinhardt, git, Justin Tobler
In-Reply-To: <alvWfOJb6vAsusai@szeder.dev>
SZEDER Gábor <szeder.dev@gmail.com> writes:
> Hi Junio,
> ...
>> Note that jt/receive-pack-use-odb-transaction requires an evil merge:
>>
>> diff --git a/odb/source-packed.c b/odb/source-packed.c
>> index 06b31dd743..cbb06da038 100644
>> --- a/odb/source-packed.c
>> +++ b/odb/source-packed.c
>> @@ -545,7 +545,8 @@ static int odb_source_packed_write_object_stream(struct odb_source *source UNUSE
>> }
>>
>> static int odb_source_packed_begin_transaction(struct odb_source *source UNUSED,
>> - struct odb_transaction **out UNUSED)
>> + struct odb_transaction **out UNUSED,
>> + enum odb_transaction_flags flags UNUSED)
>> {
>> return error("packed backend cannot begin transactions");
>> }
>
> It seems that you performed this evil merge when merging the topic
> jt/receive-pack-use-odb-transaction into jch as 9727bd8447 (Merge
> branch 'jt/receive-pack-use-odb-transactions' into jch, 2026-07-17),
> but forgot to do so when creating the base for this patch series as
> 1d64e64326 (Merge branch 'jt/receive-pack-use-odb-transactions' into
> ps/odb-move-loose-object-writing, 2026-07-17). Consequently, neither
> 1d64e64326 nor any of the the commits of this patch series can be
> built because of the mismatching function signature:
Thanks for noticing.
Very much appreciated.
Will fix-up.
^ permalink raw reply
* [PATCH 0/2] Rust hash cleanups
From: brian m. carlson @ 2026-07-19 1:08 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Jeff King, Patrick Steinhardt
Peff recently sent out a series to fix several memory leaks with our
hashing code when not using the default block algorithm. This series
follows up with a few fixes to our Rust hash code, which calls the C
code, to fix various memory problems.
brian m. carlson (2):
hash: initialize context before cloning
rust: discard hash context when finished
src/hash.rs | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
^ permalink raw reply
* [PATCH 2/2] rust: discard hash context when finished
From: brian m. carlson @ 2026-07-19 1:08 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Jeff King, Patrick Steinhardt
In-Reply-To: <20260719010842.17991-1-sandals@crustytoothpaste.net>
When we allocate a context but then abandon it, we never discard it,
which means that the underlying crypto library context may leak. This
doesn't happen with our default block code, but it may with OpenSSL.
Note that we do call git_hash_free, which frees the memory we called
from git_hash_alloc, but doesn't discard the underlying context itself.
This can be seen with the following command when compiling with OpenSSL
and running with nightly Rust:
RUSTFLAGS='-Z sanitizer=leak' cargo test
Discard the context in our context handler. Note that it is fine to do
so even after finalizing the context, so our final functions which take
self instead of &mut self will not mishandle memory.
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
---
src/hash.rs | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/hash.rs b/src/hash.rs
index 4d14e4b4fa..e1f2d31fc3 100644
--- a/src/hash.rs
+++ b/src/hash.rs
@@ -194,7 +194,10 @@ impl Clone for CryptoHasher {
impl Drop for CryptoHasher {
fn drop(&mut self) {
- unsafe { c::git_hash_free(self.ctx) };
+ unsafe {
+ c::git_hash_discard(self.ctx);
+ c::git_hash_free(self.ctx);
+ };
}
}
@@ -356,6 +359,7 @@ pub mod c {
pub fn git_hash_clone(dst: *mut c_void, src: *const c_void);
pub fn git_hash_update(ctx: *mut c_void, inp: *const c_void, len: usize);
pub fn git_hash_final(hash: *mut u8, ctx: *mut c_void);
+ pub fn git_hash_discard(ctx: *mut c_void);
pub fn git_hash_final_oid(hash: *mut c_void, ctx: *mut c_void);
}
}
@@ -450,6 +454,7 @@ mod tests {
h.update(&data[2..]);
let h2 = h.clone();
+ let h3 = h2.clone();
let actual_oid = h.into_oid();
assert_eq!(**oid, actual_oid);
@@ -463,6 +468,7 @@ mod tests {
let actual_oid = h.into_oid();
assert_eq!(**oid, actual_oid);
+ std::mem::drop(h3);
}
}
}
^ permalink raw reply related
* [PATCH 1/2] hash: initialize context before cloning
From: brian m. carlson @ 2026-07-19 1:08 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Jeff King, Patrick Steinhardt
In-Reply-To: <20260719010842.17991-1-sandals@crustytoothpaste.net>
Our C-based clone helper requires that the context be initialized, but
we neglect to do that in our Clone implementation for CryptoHasher.
This does not matter when using our default block SHA-256
implementation, but it does cause a crash when using OpenSSL as the
backend. Fix this by properly initializing the context before cloning
into it.
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
---
src/hash.rs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/hash.rs b/src/hash.rs
index dea2998de4..4d14e4b4fa 100644
--- a/src/hash.rs
+++ b/src/hash.rs
@@ -181,7 +181,10 @@ impl CryptoDigest for CryptoHasher {
impl Clone for CryptoHasher {
fn clone(&self) -> Self {
let ctx = unsafe { c::git_hash_alloc() };
- unsafe { c::git_hash_clone(ctx, self.ctx) };
+ unsafe {
+ c::git_hash_init(ctx, self.algo.hash_algo_ptr());
+ c::git_hash_clone(ctx, self.ctx)
+ };
Self {
algo: self.algo,
ctx,
^ permalink raw reply related
* [Feature] linked files — one source, multiple paths, always identical
From: sporteka2 @ 2026-07-19 2:46 UTC (permalink / raw)
To: git
Hi all,
I would like to propose a 'linked files' mechanism for Git: a file can be
declared to mirror another file in the same repository, so the two are
always byte-identical. The tooling would enforce identity and keep the
copies in sync automatically — no manual copy, no symlink, no commit hook.
== Motivation ==
Many projects need the same source file available from multiple paths:
- a library core reused by an example folder
(examples/demo/core.js must equal the root core.js);
- a shared header copied into submodules;
- documentation snippets embedded in several places.
Today the only ways to keep them identical are:
- Copy — drifts whenever one side is edited;
- Symlink — breaks on some archive downloads and cross-filesystem;
- Hardlink — lost by every git checkout / git reset;
- Commit hook — a workaround, not a platform guarantee, and runs only
at commit time.
None of these guarantees that the files are identical at any moment,
which is what a maintainer actually wants.
== Proposed mechanism ==
Add a declarative file (e.g. .gitlinks, similar to .gitignore) mapping
a linked path to its source:
.gitlinks
# linked path source path
examples/demo/core.js core.js
The tooling would then:
- On commit — reject the commit if a linked file differs from its source
(or auto-overwrite it from the source);
- On checkout / clone — materialise the linked file from the source
(hardlink when the filesystem allows, otherwise an identical copy);
- On archive / ZIP download — keep the link so the downloaded tree
stays correct;
- On edit — editing either path updates both, so divergence is
impossible.
== Why this belongs at the tooling level ==
Git already stores content-addressed blobs, so two identical files share
one blob internally. The missing piece is a working-tree guarantee that
the paths stay identical.
== Concrete first step (smaller scope) ==
Even without full 'linked files', a valuable first deliverable would be:
git archive should preserve hardlinks between identical files, the way it
already preserves symlinks. That alone would let a maintainer hardlink a
file locally and have the archive keep the two entries pointing at one
object.
== Alternatives considered ==
- Submodules / subtrees — heavier, separate history or full copy, not
'same file'.
- Symlinks — already preserved by git archive, but break on some download
tools and cross-filesystem.
- Commit hooks — work only at commit time, easy to forget, not enforced.
I would appreciate feedback on this proposal.
Thanks,
sporteka
^ permalink raw reply
* Re: [PATCH v19 5/7] branch: add --delete-merged <branch>
From: Junio C Hamano @ 2026-07-19 3:02 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget
Cc: git, Kristoffer Haugsbakk, Johannes Sixt, Phillip Wood,
Harald Nordgren
In-Reply-To: <a6caa5b397da8ea24eb97e6aa6dc92b437e456ef.1784053493.git.gitgitgadget@gmail.com>
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> +struct spare_data {
> + struct strset *deletable;
> + struct strset *spared;
> +};
> +
> +/*
> + * A surviving branch stacked on a deletion candidate would lose its
> + * upstream, so drop that candidate from the delete set and remember it
> + * in "spared" so its own upstream can be tidied up afterwards.
> + */
> +static int spare_stacked_base(const struct reference *ref, void *cb_data)
> +{
> + struct spare_data *data = cb_data;
> + struct branch *branch;
> + const char *upstream, *up_short;
> +
> + if (strset_contains(data->deletable, ref->name))
> + return 0;
> + branch = branch_get(ref->name);
> + upstream = branch_get_upstream(branch, NULL);
> + if (!upstream || !skip_prefix(upstream, "refs/heads/", &up_short) ||
> + !strset_contains(data->deletable, up_short))
> + return 0;
> +
> + strset_remove(data->deletable, up_short);
> + strset_add(data->spared, up_short);
> + return 0;
> +}
> +
> +/*
> + * Keep any branch that a surviving branch tracks as its upstream, so we
> + * never delete a branch out from under one stacked on top of it. Such a
> + * base is itself merged, so when its own upstream is also going away
> + * (no surviving branch tracks it), clear the base's now-stale upstream.
> + */
> +static void spare_stacked_bases(struct ref_store *refs, struct strset *deletable)
> +{
> + struct strset spared = STRSET_INIT;
> + struct spare_data data = { .deletable = deletable, .spared = &spared };
> + struct strbuf key = STRBUF_INIT;
> + struct hashmap_iter iter;
> + struct strmap_entry *entry;
> +
> + refs_for_each_branch_ref(refs, spare_stacked_base, &data);
Hmph. Wouldn't this implicitly make whether a stacked branch is
spared or has its upstream configuration cleared depends on the
order in which the branches are visited by the callback function of
refs_for_each_branch_ref(), which presumably is alphabetical?
For example, if 'a_tip' (unmerged) tracks 'b_mid' (merged), which in
turn tracks 'c_lower' (merged), visiting them in alphabetical order
('a_tip', 'b_mid', 'c_lower') would spare both 'b_mid' and
'c_lower'. If they, however, were named 'tip' (unmerged), which
tracks 'mid' (merged), which in turn tracks 'lower' (merged), they
would be visited in the order 'lower', 'mid', 'tip'. This would
result in 'lower' being deleted and 'mid' being spared with its
upstream configuration cleared, even though the relationship among
these three branches is exactly the same.
Since the branches are visited in a fixed alphabetical order, it
might not be an unpredictable order that yields unrepeatable
results. Nonetheless, the behavior should be consistent and
independent of branch names, as long as the inter-relationship
among the branches involved is identical, no?
Or am I grossly misreading the code?
Thanks.
^ permalink raw reply
* Thought your community might enjoy this - CodeRaider
From: CodeRaider Team @ 2026-07-19 3:03 UTC (permalink / raw)
To: git
[-- Attachment #1: Type: text/plain, Size: 520 bytes --]
Hey! I'm Eli, founder of CodeRaider (https://coderaider.net), a code-raid calculator and strategy tool for Rust. Enter what you know about a base's locks and it gives you the optimal sequences to try. I came across Website and think your members would enjoy it. Would you be open to us sharing CodeRaider in your server? Happy to set up a channel or bot integration if you're into that. Cheers, Eli Young Founder, CodeRaider https://coderaider.net --- To opt out of future outreach, please reply 'unsubscribe' or 'stop'.
^ permalink raw reply
* Re: [PATCH 0/9] object-file: move writing of loose objects into "loose" source
From: Junio C Hamano @ 2026-07-19 5:48 UTC (permalink / raw)
To: SZEDER Gábor; +Cc: Patrick Steinhardt, git, Justin Tobler
In-Reply-To: <xmqq5x2brdqj.fsf@gitster.g>
Junio C Hamano <gitster@pobox.com> writes:
> SZEDER Gábor <szeder.dev@gmail.com> writes:
>
>> It seems that you performed this evil merge when merging the topic
>> jt/receive-pack-use-odb-transaction into jch as 9727bd8447 (Merge
>> branch 'jt/receive-pack-use-odb-transactions' into jch, 2026-07-17),
>> but forgot to do so when creating the base for this patch series as
>> 1d64e64326 (Merge branch 'jt/receive-pack-use-odb-transactions' into
>> ps/odb-move-loose-object-writing, 2026-07-17). Consequently, neither
>> 1d64e64326 nor any of the the commits of this patch series can be
>> built because of the mismatching function signature:
>
> Thanks for noticing.
> Very much appreciated.
>
> Will fix-up.
I've rebuilt the topic in question and pushed the results out. I
also added some more automation to catch this kind of mistakes early
to make it less likely to happen again.
Thanks.
^ permalink raw reply
* Re: [PATCH] branch: report kind of checkout when rejecting delete
From: René Scharfe @ 2026-07-19 5:55 UTC (permalink / raw)
To: Junio C Hamano
Cc: Phillip Wood, Toon Claes, Patrick Steinhardt, Git List, stsp
In-Reply-To: <xmqqa4roq7a8.fsf@gitster.g>
On 7/19/26 12:09 AM, Junio C Hamano wrote:
> René Scharfe <l.s.r@web.de> writes:
>
>>>> + int kind = branch_checkout_kind(name);
>>>
>>> Not "enum branch_checkout_kind" but "int"?
>>
>> Yes, it doesn't matter for the switch and is easier to print.
>
> I do not understand the "print" part. I was probably in the last
> group of people who was forced to switch from CPP macros to enum
> and their argument was always "'print kind' in GDB gives symbolic
> output". As "enum" is an glorified "int", wouldn't
>
> int i_kind;
> enum branch_checkout_kind e_kind;
> BUG(_("we did not expect %d %d"), e_kind, i_kind);
>
> do just what we expect?
True, integer promotion makes this work regardless of the underlying
type of the enum. It was easier for me to make that conversion
explicit than to remember the conversion rule. Which isn't that
complicated, though, admittedly.
René
^ permalink raw reply
* What's cooking in git.git (Jul 2026, #08)
From: Junio C Hamano @ 2026-07-19 7:49 UTC (permalink / raw)
To: git
Here are the topics that have been cooking in my tree. Commits
prefixed with '+' are in 'next' (being in 'next' is a sign that a
topic is stable enough to be used and is a candidate to be in a
future release). Commits prefixed with '-' are only in 'seen', and
aren't considered "accepted" at all. They may be annotated with a URL
to a message that raises issues but they are by no means exhaustive.
A topic without enough support may be discarded after a long period
of no activity (of course, it can be resubmitted when new interest
arises).
The fourth batch of topics have now graduated to the 'master'
branch.
Copies of the source code to Git live in many repositories, and the
following is a list of the ones I push into or their mirrors. Some
repositories have only a subset of branches.
With maint, master, next, seen, todo:
git://git.kernel.org/pub/scm/git/git.git/
git://repo.or.cz/alt-git.git/
https://kernel.googlesource.com/pub/scm/git/git/
https://github.com/git/git/
https://gitlab.com/git-scm/git/
With all the integration branches and topics broken out:
https://github.com/gitster/git/
Even though the preformatted documentation in HTML and man format
are not sources, they are published in these repositories for
convenience (replace "htmldocs" with "manpages" for the manual
pages):
git://git.kernel.org/pub/scm/git/git-htmldocs.git/
https://github.com/gitster/git-htmldocs.git/
Release tarballs are available at:
https://www.kernel.org/pub/software/scm/git/
--------------------------------------------------
[New Topics]
* tl/gitweb-shorten-hashes-with-modes (2026-07-17) 1 commit
- gitweb: shorten index hashes with trailing file modes
The object ID shortening and linking in the 'commitdiff' view of
'gitweb' has been corrected to work even when the index line carries
a trailing file mode.
Needs review.
source: <SA1PR10MB9977150C823C0751E53B150D5AF1C62@SA1PR10MB997715.namprd10.prod.outlook.com>
* kj/repo-info-more-path-keys (2026-07-17) 7 commits
- repo: add path.git-prefix path key
- repo: add path.grafts with absolute and relative suffix formatting
- repo: add path.index with absolute and relative suffix formatting
- repo: add path.hooks with absolute and relative suffix formatting
- repo: add path.objects with absolute and relative suffix formatting
- repo: add path.superproject-working-tree with absolute and relative suffixes
- repo: add path.toplevel with absolute and relative suffix formatting
The 'git repo info' command has been taught more keys to output
paths of various repository components (such as the working tree
root, superproject working tree, object database, etc.), supporting
both absolute and relative path formats.
Needs review.
source: <20260717133015.32040-1-jayatheerthkulkarni2005@gmail.com>
* sk/userdiff-swift (2026-07-17) 1 commit
- userdiff: add support for Swift
Userdiff patterns for Swift have been added, with support for
Swift-specific constructs such as attributes, modifiers, failable
initializers, and generics.
Waiting for response.
cf. <2a3a73c5-5e90-44a3-bf6a-6e98ce5e5a59@kdbg.org>
source: <20260717140232.6722-1-diy2903@gmail.com>
* ps/odb-move-loose-object-writing (2026-07-17) 10 commits
- object-file: move logic to write loose objects
- object-file: move `force_object_loose()`
- object-file: force objects loose via generic interface
- object-file: fix memory leak in `force_object_loose()`
- odb: support setting mtime when writing objects
- odb: lift object existence check out of the "loose" backend
- odb: compute object hash in `odb_write_object_ext()`
- t/u-odb-inmemory: implement wrapper for writing objects
- odb: compute compat object ID in `odb_write_object_ext()`
- Merge branch 'jt/receive-pack-use-odb-transactions' into HEAD
(this branch uses jt/receive-pack-use-odb-transactions.)
The logic to write loose objects has been refactored and moved from
'object-file.c' to the loose backend source file 'odb/source-loose.c',
making the loose backend more self-contained. This is achieved by
first refactoring 'force_object_loose()' to use generic ODB write
interfaces instead of loose-backend internals.
Needs review.
source: <20260717-pks-odb-move-loose-object-writing-v1-0-46446a3cb5b7@pks.im>
* pw/rebase-fixup-fixes (2026-07-17) 2 commits
- rebase: remember fixup -c after skipping fixup/squash
- rebase -i: fix counting of fixups after rebase --skip
Two bugs in how 'git rebase' handles skipped 'fixup' and 'squash'
commands have been fixed. One bug caused an incorrect commit count
to be shown in the template message when multiple commands were
skipped, and another caused the editor not to be opened when the
final command in a chain containing 'fixup -c' was skipped.
Needs review.
source: <cover.1784304378.git.phillip.wood@dunelm.org.uk>
* tc/last-modified-bloom (2026-07-17) 4 commits
- last-modified: keep per-path Bloom filters for wildcard pathspecs
- last-modified: check pathspec against Bloom filter first
- revision: expose check for paths maybe changed in Bloom filter
- revision: move bloom keyvec precondition into function
The 'git last-modified' command has been optimized by using Bloom
filters. It now reuses revision walk filtering logic from 'git log'
to pre-filter commits, and maintains per-path Bloom filters even
when wildcard pathspecs are used.
Expecting a reroll.
cf. <20260718083757.GD22588@coredump.intra.peff.net>
cf. <20260718081407.GC22588@coredump.intra.peff.net>
cf. <20260718075700.GB22588@coredump.intra.peff.net>
cf. <87cxwl1lb4.fsf@emacs.iotcl.com>
source: <20260717-toon-speed-up-last-modified-v1-0-410418f18614@iotcl.com>
* hn/bisect-auto-reset (2026-07-17) 3 commits
- bisect: add --auto-reset to leave when done
- bisect: let bisect_reset() optionally check out quietly
- bisect: read run output from the open descriptor
The 'git bisect' command has been taught a '--auto-reset[=<where>]'
option that tells the command to automatically run 'git bisect reset'
to jump back to the original state or to the found culprit.
Waiting for response.
cf. <1139ae20-f08b-4cf2-b779-42328831e13e@kdbg.org>
cf. <b79a479b-d279-4ac9-a368-6eb8edfed937@kdbg.org>
source: <pull.2335.v2.git.git.1784312854.gitgitgadget@gmail.com>
--------------------------------------------------
[Graduated to 'master']
* ih/precompose-flex-array (2026-07-04) 1 commit
(merged to 'next' on 2026-07-09 at 737a87f65e)
+ precompose_utf8: use a flex array for d_name
The UTF-8 precomposition wrapper on macOS has been updated to use a
flexible array member to represent the name of a directory entry,
preventing fortified libc checks from failing when the name is
reallocated to be larger than 'NAME_MAX' bytes.
Graduated to 'master'.
cf. <20260703050800.GA29216@tb-raspi4>
source: <20260704233724.16928-1-ihar.hrachyshka@gmail.com>
* jk/git-hash-cleanups (2026-07-07) 8 commits
(merged to 'next' on 2026-07-09 at 12a4856545)
+ hash: check ctx->active flag in all wrapper functions
+ http: use idempotent git_hash_discard()
+ csum-file: use idempotent git_hash_discard()
+ hash: make git_hash_discard() idempotent
+ hash: document function pointers and wrappers
+ hash: convert remaining direct function calls
+ hash: use git_hash_init() consistently
+ Merge branch 'jk/hash-algo-leak-fixes' into jk/git-hash-cleanups
(this branch uses jk/hash-algo-leak-fixes.)
The 'git_hash_*()' wrappers have been updated to be used consistently
across the codebase instead of direct calls to members of 'struct
git_hash_algo', and 'git_hash_discard()' has been made idempotent to
simplify cleanups.
Graduated to 'master'.
cf. <ak4E4-jmgYFSI75O@pks.im>
source: <20260708035235.GA41491@coredump.intra.peff.net>
* jk/hash-algo-leak-fixes (2026-07-02) 9 commits
(merged to 'next' on 2026-07-09 at 7db7b74972)
+ hash: add platform-specific discard functions
+ hash: fix memory leak copying sha256 gcrypt handles
+ http: discard hash in dumb-http http_object_request
+ check_stream_oid(): discard hash on read error
+ patch-id: discard hash when done
+ csum-file: provide a function to release checkpoints
+ csum-file: always finalize or discard hash
+ hash: add discard primitive
+ csum-file: drop discard_hashfile()
(this branch is used by jk/git-hash-cleanups.)
Various code paths that initialize a cryptographic hash context but
bail out or finish without calling 'git_hash_final()' have been taught
to call 'git_hash_discard()' to release allocated resources, fixing
memory leaks when Git is built with non-default backends like
'OpenSSL' or 'libgcrypt'.
Graduated to 'master'.
cf. <aktIIKuReMxJmDsi@pks.im>
source: <20260702075234.GA1548258@coredump.intra.peff.net>
* js/ci-dockerized-pid-limit (2026-07-04) 1 commit
(merged to 'next' on 2026-07-09 at cd80e673a5)
+ ci(dockerized): raise the PID limit for private repositories
Dockerized CI jobs running in private GitHub repositories have been
adjusted to use explicit process and file limits, preventing resource
exhaustion errors on private runners.
Graduated to 'master'.
cf. <xmqqh5medmzh.fsf@gitster.g>
source: <pull.2164.v2.git.1783155124926.gitgitgadget@gmail.com>
* js/coverity-fixes (2026-07-05) 12 commits
(merged to 'next' on 2026-07-09 at 1823fe297c)
+ mingw: make `exit_process()` own the process handle on all paths
+ fsmonitor: plug token-data leak on early daemon-startup failures
+ reftable/table: release filter on error path
+ imap-send: avoid leaking the IMAP upload buffer
+ worktree: fix resource leaks when branch creation fails
+ submodule: fix cwd leak in `get_superproject_working_tree()`
+ dir: free allocations on parse-error paths in `read_one_dir()`
+ line-log: avoid redundant copy that leaks in process_ranges
+ run-command: avoid `close(-1)` in `start_command()` error paths
+ download_https_uri_to_file(): do not leak fd upon failure
+ loose: avoid closing invalid fd on error path
+ load_one_loose_object_map(): fix resource leak
Various resource leaks, invalid file descriptor closures, and process
handle ownership issues flagged by Coverity have been fixed.
Graduated to 'master'.
cf. <xmqqa4s238lg.fsf@gitster.g>
source: <pull.2163.v2.git.1783239870.gitgitgadget@gmail.com>
* js/wincred-fixes (2026-07-16) 2 commits
(merged to 'next' on 2026-07-16 at 8c5927f06f)
+ wincred: prevent silent credential loss when storing OAuth tokens
+ wincred: avoid memory corruption when erasing a credential
The 'wincred' credential helper has been updated to avoid memory
corruption when erasing credentials and to prevent silent credential
loss when storing OAuth tokens, by correcting buffer allocations and
arguments passed to safe-CRT APIs.
Graduated to 'master'.
source: <pull.2182.git.1784212072.gitgitgadget@gmail.com>
* mm/sideband-ansi-sgr-colon-fix (2026-05-13) 1 commit
(merged to 'next' on 2026-07-09 at fd2b979b73)
+ sideband: allow ANSI SGR with colon-separated subfields
The sideband demultiplexer has been updated to recognize ANSI SGR
escape sequences that use colon-separated subfields (e.g., for
256-color or true-color codes).
Graduated to 'master'.
cf. <8addf7c0-ae39-f1c0-20ab-52114702aaf6@gmx.de>
source: <20260513070803.163546-1-grawity@nullroute.lt>
* ps/t-fixes-for-git-test-long (2026-07-05) 9 commits
(merged to 'next' on 2026-07-09 at c5b13248c8)
+ gitlab-ci: enable "GIT_TEST_LONG"
+ gitlab-ci: disable RAM disk on macOS jobs
+ t: use `test_bool_env` to parse GIT_TEST_LONG
+ t7900: clean up large EXPENSIVE repository
+ t7508: skip EXPENSIVE test that is broken without SIZE_T_IS_64BIT
+ t5608: reduce maximum disk usage
+ t4141: fix inefficient use of dd(1)
+ t0021: skip EXPENSIVE test that is broken without SIZE_T_IS_64BIT
+ README: add GitLab CI badge to make it more discoverable
Various test scripts have been updated to clean up large temporary
files and repositories, reducing peak disk usage during testing.
Also, expensive tests have been disabled on platforms that lack
sufficient resources (like 32-bit platforms and Windows CI runners),
and the long test suite has been enabled in GitLab CI.
Graduated to 'master'.
cf. <20260707043026.GB677056@coredump.intra.peff.net>
source: <20260706-b4-pks-t-fixes-for-GIT-TEST-LONG-v3-0-4f6c5a37fd1f@pks.im>
--------------------------------------------------
[Stalled]
* kh/doc-trailers (2026-06-10) 10 commits
- doc: interpret-trailers: document comment line treatment
- doc: interpret-trailers: commit to “trailer block” term
- doc: interpret-trailers: join new-trailers again
- doc: interpret-trailers: add key format example
- doc: interpret-trailers: explain key format
- doc: interpret-trailers: explain the format after the intro
- doc: interpret-trailers: not just for commit messages
- doc: interpret-trailers: use “metadata” in Name as well
- doc: interpret-trailers: replace “lines” with “metadata”
- doc: interpret-trailers: stop fixating on RFC 822
Documentation for 'git interpret-trailers' has been updated to explain
the format of trailer keys (alphanumeric characters and hyphens),
replace outdated terminology, define key terms upfront, and document
how comment lines in the input are treated.
Expecting a reroll for too long, stalled.
cf. <729baf6b-53ea-4e8d-95ab-5935667e66c2@app.fastmail.com>
source: <V3_CV_doc_int-tr_key_format.8a3@msgid.xyz>
* sn/rebase-update-refs-symrefs (2026-06-03) 1 commit
- rebase: skip branch symref aliases
'git rebase --update-refs' has been taught to resolve local branch
symrefs to their referents before queuing updates, ensuring aliases of
the current branch are skipped and duplicate updates are avoided to
prevent failures when branch aliases are present.
Waiting for response for too long, stalled.
cf. <f982c386-e329-4ab0-b695-e540bcb9de3d@gmail.com>
source: <pull.2126.v2.git.1780482436865.gitgitgadget@gmail.com>
* jt/config-lock-timeout (2026-05-17) 1 commit
- config: retry acquiring config.lock, configurable via core.configLockTimeout
Configuration file locking has been updated to retry for a short
period, avoiding failures when multiple processes attempt to update
the configuration simultaneously.
Waiting for response for too long, stalled.
cf. <agrIrGwSMFlKTx9x@pks.im>
source: <20260517132111.1014901-1-joerg@thalheim.io>
--------------------------------------------------
[Cooking]
* js/coverity-unchecked-returns-fix (2026-07-14) 11 commits
- bisect: handle dup() failure when redirecting stdout
- bisect: check get_terms return at all call sites
- bisect: check strbuf_getline_lf return when reading terms
- transport-helper: warn when export-marks file cannot be finalized
- transport-helper: check dup() return in get_exporter
- compat/pread: check initial lseek for errors
- last-modified: handle repo_parse_commit() failures
- reftable tests: check reftable_table_init_ref_iterator() return
- reftable/block: check deflateInit() return value
- config: propagate launch_editor() failure in show_editor()
- http: die on curl_easy_duphandle failure in get_active_slot
A handful of code paths have been corrected to check return values
from functions like 'curl_easy_duphandle()', 'deflateInit()',
'lseek()', 'dup()', and 'strbuf_getline_lf()', resolving several
Coverity warnings about unchecked returns.
Waiting for response.
cf. <xmqqldbdqciy.fsf@gitster.g>
cf. <xmqqh5m1qcfh.fsf@gitster.g>
cf. <alcvmX3b6y92KE4y@pks.im>
cf. <alcvnm0xiOv5W0w_@pks.im>
source: <pull.2179.git.1784069325.gitgitgadget@gmail.com>
* jk/diff-relative-cached-unmerged (2026-07-14) 1 commit
- diff: ignore unmerged paths outside prefix with --relative --cached
'git diff --relative' running with '--cached' has been corrected to
avoid a segfault when encountering unmerged paths outside the
prefix.
Needs review.
source: <20260715060523.GA517940@coredump.intra.peff.net>
* jc/submodule-helper-avoid-zu (2026-07-15) 1 commit
- submodule--helper: avoid use of %zu for now
An accidental use of '%zu' format flag in 'git submodule--helper'
has been corrected to use 'PRIuMAX' and cast the value to
'uintmax_t', to avoid portability issues.
Will merge to 'next'.
source: <xmqq4ii0ko9t.fsf@gitster.g>
* sk/t7614-do-not-hide-git-exit-status (2026-07-15) 1 commit
(merged to 'next' on 2026-07-16 at 0d143986e7)
+ t7614: avoid hiding git's exit code in a pipe
The test script 't/t7614-merge-signoff.sh' has been updated to avoid
suppressing the exit code of 'git' commands in a pipe.
Will merge to 'master'.
cf. <xmqq1pd4m4ea.fsf@gitster.g>
source: <20260715113344.3490-1-diy2903@gmail.com>
* ds/trace2-tolerate-failed-timestamp (2026-07-15) 1 commit
- trace2: tolerate failed timestamp formatting
The 'trace2' telemetry library has been updated to tolerate failures
from system calls like 'gettimeofday()' and datetime formatting
functions, replacing potential program crashes with blank placeholder
timestamps in the traces.
Waiting for response.
cf. <alpXW5U6sndZtgqV@com-79390>
source: <pull.2178.git.1784131932489.gitgitgadget@gmail.com>
* mm/revision-pure-get-commit-action (2026-07-15) 1 commit
- revision: make get_commit_action() a pure predicate
The 'get_commit_action()' function has been refactored to be a pure
predicate by moving the side-effecting line-level log range folding to
'simplify_commit()'. This ensures that evaluating a commit's action
before the walk reaches it does not prematurely mutate its tracked
line ranges, making it safer for potential lookahead evaluations.
Needs review.
source: <pull.2169.git.1784143793613.gitgitgadget@gmail.com>
* rs/remote-curl-simplify-push-specs (2026-07-14) 1 commit
- remote-curl: simplify passing of push specs
The passing of push destination specifications in the 'remote-curl'
helper has been simplified by removing the explicit 'count' parameter
and relying on the NULL-termination of the array.
Will merge to 'next'.
source: <935883f3-3be4-4c51-9711-5208b9ef9ca1@web.de>
* kk/no-walk-pathspec-fix (2026-07-16) 2 commits
(merged to 'next' on 2026-07-16 at 4dd6fb0e7e)
+ revision: fix --no-walk path filtering regression
+ Merge branch 'kk/streaming-walk-pqueue' into kk/no-walk-pathspec-fix
The 'git rev-list --no-walk' command lost pathspec filtering when the
streaming walk was refactored, which has been corrected.
Will merge to 'master'.
source: <pull.2181.git.1784198879711.gitgitgadget@gmail.com>
* cc/fast-import-usage (2026-07-16) 7 commits
- fast-import: use struct option for usage string
- fast-import: move command state globals into 'struct fast_import_state'
- fast-import: introduce 'struct fast_import_state'
- fast-import: localize 'i' into the 'for' loops using it
- api-parse-options.adoc: document hidden and OPT_*_F option macros
- api-parse-options.adoc: document per-option flags
- parse-options: introduce OPT_HIDDEN_GROUP
The usage string of 'git fast-import' has been updated to use the
'parse_options' API for displaying help, and its SYNOPSIS in the
documentation has been standardized to match.
Waiting for response.
cf. <xmqq4ihyehyb.fsf@gitster.g>
cf. <xmqqcxwmeiwq.fsf@gitster.g>
source: <20260716165517.433849-1-christian.couder@gmail.com>
* ps/copy-wo-the-repository (2026-07-16) 1 commit
- copy: drop dependency on `the_repository`
The 'copy_file()' and 'copy_file_with_time()' functions have been
refactored to take a repository parameter, allowing the removal of the
implicit dependency on the global 'the_repository' variable in
'copy.c'.
Will merge to 'next'?
cf. <b0df688a-3b26-48f6-8b1c-98530483885e@gmail.com>
cf. <xmqqo6g54k7m.fsf@gitster.g>
source: <20260716-pks-copy-wo-the-repository-v2-1-8f5e32942929@pks.im>
* ps/refspec-wo-the-repository (2026-07-16) 3 commits
- refspec: stop depending on `the_repository`
- refspec: let callers pass in hash algorithm when parsing items
- refspec: group related structures and functions
The dependency on the global 'the_repository' variable in the
'refspec.c' API has been removed by passing the hash algorithm
explicitly to refspec-parsing functions and storing it in 'struct
refspec'.
Will merge to 'next'.
source: <20260716-pks-refspec-wo-the-repository-v1-0-aa40844d067f@pks.im>
* ps/writev (2026-07-16) 5 commits
- fast-import: use writev(3p) to send cat-blob responses
- sideband: use writev(3p) to send pktlines
- wrapper: properly handle MAX_IO_SIZE in writev(3p)
- wrapper: introduce writev(3p) wrappers
- compat/posix: introduce writev(3p) wrapper
A compatibility wrapper for 'writev(3p)' has been reintroduced,
including fixes for CMake build and 'MAX_IO_SIZE' limits on NonStop.
Calls to 'write(3p)' in 'send_sideband()' and 'cat_blob()' have been
refactored to use 'writev(3p)' wrappers to reduce syscall overhead.
Waiting for response.
cf. <f8050598-392f-44c9-8d66-0454740a7a12@kdbg.org>
cf. <a2676ec6-39d5-4220-8549-10a17daec668@hogyros.de>
cf. <xmqqfr1ig0hv.fsf@gitster.g>
source: <20260716-pks-reintroduce-writev-v1-0-ea9038c884bc@pks.im>
* sc/wt-status-avoid-quadratic-insertion (2026-07-17) 1 commit
- wt-status: avoid repeated insertion for untracked paths
The enumeration of untracked and ignored files in 'git status' has
been optimized by avoiding quadratic complexity insertion into string
lists, reducing the construction cost from O(n^2) to O(n log n).
Will merge to 'next'.
(a newer iteration v3 exists as <20260718081449.26747-1-sahityajb@gmail.com>)
cf. <20260718083828.GE22588@coredump.intra.peff.net>
source: <20260717144620.259031-1-sahityajb@gmail.com>
* tb/send-pack-no-ref-delta (2026-07-12) 4 commits
- send-pack: honor `no-ref-delta` capability
- pack-objects: support reuse with `--no-ref-delta`
- pack-objects: introduce `--no-ref-delta`
- t/helper: teach pack-deltas to list delta entries
'git send-pack' has been taught to refrain from sending 'REF_DELTA'
encoded packfiles when the other side asks it to.
Needs review.
source: <alQ7WKITYDXfiVn9@com-79390>
* cc/doc-fast-export-synopsis-fix (2026-07-13) 1 commit
(merged to 'next' on 2026-07-16 at b1dbc0cb3f)
+ fast-export: standardize usage string and SYNOPSIS
The usage string and SYNOPSIS for 'git fast-export' have been
standardized to make them consistent with each other and with other
commands.
Will merge to 'master'.
cf. <alX5Nl8uX4ctVqo3@pks.im>
cf. <xmqq4ii228dd.fsf@gitster.g>
source: <20260713124153.245268-1-christian.couder@gmail.com>
* sk/t1100-modernize (2026-07-14) 2 commits
(merged to 'next' on 2026-07-16 at 621ca4ca5f)
+ t1100: move creation of expected output into setup test
+ t1100: modernize test style
The test script 't/t1100-commit-tree-options.sh' has been modernized
by converting test cases to the modern style (using single quotes and
tab indentation) and moving the creation of the expected file inside
the setup test so it runs under the protection of the test harness.
Will merge to 'master'.
cf. <xmqq4ii1v7x0.fsf@gitster.g>
source: <20260714122033.61947-1-diy2903@gmail.com>
* tn/packfile-uri-concurrency (2026-07-13) 2 commits
- fetch-pack: accept "pack" output for packfile URIs
- http: use unique tempfiles for packfile URI downloads
Concurrent downloads of packfiles via packfile URIs have been
supported by using unique temporary files, preventing corruption when
multiple processes fetch the same pack. The 'fetch-pack' command has
also been updated to tolerate pre-existing '.keep' files.
Expecting a reroll.
cf. <alaAi4vNwi-KabYV@com-76773>
source: <alVn-QmK3K91_tkH@com-76773>
* rs/strbuf-avoid-redundant-reset (2026-07-14) 1 commit
(merged to 'next' on 2026-07-16 at f258ce38ba)
+ strbuf: avoid redundant reset in strbuf_getwholeline()
A redundant 'strbuf_reset()' call in the 'HAVE_GETDELIM' path of
'strbuf_getwholeline()' has been removed, as 'getdelim()' overwrites
the buffer and the length is updated afterward.
Will merge to 'master'.
cf. <xmqq8q7dv82b.fsf@gitster.g>
cf. <20260714214941.GB4095533@coredump.intra.peff.net>
source: <d4ffe7fb-f782-4f06-9e3b-f72729d1e225@web.de>
* rs/tempfile-wo-the-repository (2026-07-14) 5 commits
- use repo_hold_lock_file_for_update{,_mode,_timeout}() with custom repos
- tempfile: stop using the_repository
- lockfile: add repo_hold_lock_file_for_update{,_timeout}{,_mode}()
- refs/packed: use repo_create_tempfile()
- tempfile: add repo_create_tempfile{,_mode}()
The tempfile and lockfile APIs have been refactored to stop depending
on the 'the_repository' global variable, and their callers have been
updated to use the repository-aware variants.
Waiting for response.
cf. <aldYVPyMl40-Myp0@pks.im>
cf. <aldYTuMvN-8EMvYK@pks.im>
cf. <3c0a8031-7082-422a-b474-938418682b60@web.de>
source: <20260714175956.54601-1-l.s.r@web.de>
* js/pack-objects-delta-size-t (2026-07-09) 12 commits
- git-zlib: widen `git_deflate_bound()` to `size_t`
- t/helper/test-pack-deltas: widen `do_compress()`'s maxsize local to `size_t`
- http-push: widen `start_put()`'s size local from `ssize_t` to `size_t`
- diff: widen `deflate_it()`'s bound local from int to `size_t`
- archive-zip: widen `zlib_deflate_raw()`'s maxsize local to `size_t`
- packfile, git-zlib: widen `use_pack()` and zstream avail fields to `size_t`
- delta: widen `create_delta()` and `diff_delta()` to `size_t`
- pack-objects: widen `mem_usage` and `try_delta()`'s out-param to `size_t`
- pack-objects: widen `free_unpacked()` return to `size_t`
- pack-objects: widen delta-cache accounting to `size_t`
- delta: widen `create_delta_index()` parameter to `size_t`
- diff-delta: widen `struct delta_index`' size fields to `size_t`
The 'pack-objects' and delta-encoding code paths have been updated to
use 'size_t' instead of 'unsigned long' for object sizes and offset
limits, avoiding potential truncation issues on 64-bit Windows.
Needs review.
source: <pull.2175.git.1783615780.gitgitgadget@gmail.com>
* cl/b4-cover-change-id (2026-07-10) 1 commit
(merged to 'next' on 2026-07-13 at 15c7ad9a3f)
+ b4: include change-id in cover template
The in-tree 'b4' cover letter template has been updated to include the
'change-id' trailer, ensuring that sent tags generated by 'b4' contain
the required tracking information for subsequent runs.
Will merge to 'master'.
source: <20260710-add-change-id-to-b4-template-v1-1-1bd37a25064e@black-desk.cn>
* ps/odb-stream-double-close-fix (2026-07-10) 1 commit
(merged to 'next' on 2026-07-13 at dd2c5795b7)
+ object-file: fix closing object stream twice
The stream-based object signature verification path has been
corrected to avoid double-closing the stream on read errors.
Will merge to 'master'.
source: <20260710-pks-odb-stream-double-close-v1-1-d5fa233a37c7@pks.im>
* pz/fetch-submodule-errors-config (2026-07-16) 2 commits
- fetch: add fetch.submoduleErrors to make submodule fetch errors non-fatal
- submodule: fix premature failure in recursive submodule fetch
The 'git fetch' command has been updated to allow configuring how
submodule fetch errors are handled. A new configuration variable
'fetch.submoduleErrors' and a corresponding '--submodule-errors'
command-line option have been introduced, allowing users to make
submodule fetch errors non-fatal (warn instead of fail).
Additionally, a premature failure during recursive submodule fetches
has been fixed by deferring the error until the OID-based retry phase
also fails.
Needs review.
source: <20260716140956.1023740-1-paulius.zaleckas@gmail.com>
* gr/add-e-use-apply-api (2026-07-10) 1 commit
- builtin/add.c: replace run_command() with direct apply_all_patches() call
The application of the edited patch in 'git add -e' has been
refactored to use the internal apply API directly, avoiding the need
to spawn a 'git apply' subprocess.
Needs review.
source: <20260711061246.58079-1-gatlavishweshwarreddy26@gmail.com>
* fz/rebase-autosquash-empty (2026-07-11) 1 commit
. sequencer: honor --empty when a fixup!/squash! empties its target
A commit that is emptied by melding a 'fixup!' or 'squash!' commit
during 'git rebase --autosquash' is now handled according to the
'--empty' option, allowing it to be dropped, kept, or to halt the
rebase.
Waiting for response.
cf. <690b965e-5f07-4aa4-a64c-96e60a86d73b@gmail.com>
source: <20260711-fz-autosquash-empty-v3-1-d227b63eb511@gmail.com>
* dm/submodule-update-i-shorthand (2026-07-07) 1 commit
(merged to 'next' on 2026-07-15 at 55ef0fb748)
+ submodule--helper: accept '-i' shorthand for update --init
The '-i' shorthand for the '--init' option, which was accepted by the
'git submodule update' command until it was broken in a modernization
of the option-parsing code, has been restored.
Will merge to 'master'.
cf. <xmqq8q7ltf51.fsf@gitster.g>
source: <20260708-submodule-init-v1-1-719456077262@atmark-techno.com>
* hf/unpack-trees-quadratic-scan (2026-07-08) 1 commit
(merged to 'next' on 2026-07-12 at 744f1aede4)
+ unpack-trees: avoid quadratic index scan in next_cache_entry()
The cache-scanning loop in 'next_cache_entry()' has been optimized
to avoid rescanning already-unpacked index entries, preventing a
quadratic performance slow-down when diffing the working tree
against a commit with a pathspec matching early index entries.
Will merge to 'master'.
cf. <xmqqpl0xqh3n.fsf@gitster.g>
source: <pull.2353.v2.git.git.1783546933992.gitgitgadget@gmail.com>
* jc/relnotes-2.55-rust-fix (2026-07-07) 1 commit
(merged to 'next' on 2026-07-10 at 444d202a75)
+ Rust: fix description in Release Notes to 2.55
A description in the release notes for Git 2.55.0 has been
retroactively updated to clarify that Rust support is enabled by
default, but still optional, and will become mandatory in Git 3.0.
Will merge to 'master'.
source: <xmqqpl0y4rpg.fsf@gitster.g>
* jc/submitting-patches-abandoning (2026-07-08) 1 commit
(merged to 'next' on 2026-07-10 at 41b9b65b23)
+ SubmittingPatches: document how to retract a topic
The 'SubmittingPatches' document has been updated to explicitly
describe the expectation for contributors to retract or abandon their
patch series when they are no longer pursuing it.
Will merge to 'master'.
cf. <ak6U07K1dQPlXxIp@nixos>
source: <xmqqpl0xv25e.fsf@gitster.g>
* mm/lib-httpd-cgi-safe (2026-07-10) 3 commits
- t/README: document writing concurrency-safe helpers
- t/lib-httpd: make http-429 first-request check atomic
- t/lib-httpd: fix apply-one-time-script race under concurrent requests
CGI helper scripts used by HTTP-related test scripts have been updated
to use atomic filesystem operations, preventing race conditions when
Apache handles concurrent requests.
Needs review.
source: <pull.2171.v2.git.1783704657.gitgitgadget@gmail.com>
* ps/odb-pluggable-housekeeping (2026-07-12) 12 commits
- odb: make optimizations pluggable
- builtin/gc: fix signedness issues in ODB-related functionality
- builtin/gc: refactor ODB optimizations to operate on "files" source
- builtin/gc: introduce `odb_optimize_required()`
- builtin/gc: move geometric repacking into `odb_optimize()`
- builtin/gc: introduce object database optimization options
- builtin/gc: inline config values specific to the "files" backend
- builtin/gc: make repack arguments self-contained
- builtin/gc: extract object database optimizations into separate function
- builtin/gc: move worktree and rerere tasks before object optimizations
- odb: run "pre-auto-gc" hook for all maintenance tasks
- t7900: simplify how we check for maintenance tasks
Object database housekeeping in 'git gc' and 'git maintenance' has
been refactored to be pluggable. The files-backend specific logic,
including incremental and geometric repacking as well as object
pruning, has been moved out of the command implementation and into the
files object database source, enabling future alternative object
database backends to implement their own housekeeping services.
Waiting for response.
cf. <xmqqwluyyhv1.fsf@gitster.g>
source: <20260713-b4-pks-odb-optimize-v2-0-9c2c3ee94b38@pks.im>
* tc/bundle-uri-empty-fix (2026-07-08) 2 commits
(merged to 'next' on 2026-07-12 at 9da32fdaf7)
+ bundle-uri: stop sending invalid bundle configuration
+ bundle-uri: drain remaining response on invalid bundle-uri lines
The client-side parser of the server-advertised bundle-URI list has
been updated to drain the remaining response in order to avoid
protocol desynchronization when the server sends a misconfigured list.
Also, the server-side has been taught to omit empty configuration
values instead of sending invalid key-value lines.
Will merge to 'master'.
cf. <xmqqtsq9qj5k.fsf@gitster.g>
source: <20260708-toon-bundle-uri-no-uri-v2-0-09a03d8db556@iotcl.com>
* gr/t1410-reflog-exit-code (2026-07-08) 1 commit
(merged to 'next' on 2026-07-10 at d0cf55ea54)
+ t1410-reflog.sh: avoid suppressing git's exit code in pipelines
The pipelines in 't1410-reflog.sh' have been replaced with the
'test_stdout_line_count' helper to avoid suppressing the exit code of
'git' commands, ensuring failures are not hidden from the test suite.
Will merge to 'master'.
cf. <xmqqtsq8p18x.fsf@gitster.g>
source: <20260709051229.40363-1-gatlavishweshwarreddy26@gmail.com>
* js/coverity-fixes-null-safety (2026-07-10) 12 commits
(merged to 'next' on 2026-07-12 at 8d093f411d)
+ shallow: give write_one_shallow() its own hex buffer
+ shallow: fix NULL dereference
+ bisect: ensure non-NULL `head` before using it
+ pack-bitmap: handle missing bitmap for base MIDX
+ revision: avoid dereferencing NULL in `add_parents_only()`
+ replay: die when --onto does not peel to a commit
+ bisect: handle NULL commit in `bisect_successful()`
+ mailsplit: move NULL check before first use of file handle
+ reftable/stack: guard against NULL list_file in stack_destroy
+ remote: guard `remote_tracking()` against NULL remote
+ diff: handle NULL return from repo_get_commit_tree()
+ diffcore-break: guard against NULLed queue entries in merge loop
Various code paths have been hardened against potential NULL-pointer
dereferences and invalid file descriptor accesses flagged by
Coverity.
Will merge to 'master'.
cf. <xmqqa4ryg84e.fsf@gitster.g>
source: <pull.2174.v2.git.1783683577.gitgitgadget@gmail.com>
* ps/odb-for-each-object-filter (2026-07-14) 10 commits
(merged to 'next' on 2026-07-16 at 8f30e80d33)
+ builtin/cat-file: filter objects via object database
+ odb: introduce object filters to `odb_for_each_object()`
+ pack-bitmap: introduce function to open bitmap for a single source
+ pack-bitmap: drop `_1` suffix from functions that open bitmaps
+ pack-bitmap: iterate object sources when opening bitmaps
+ pack-bitmap: allow aborting iteration of bitmapped objects
+ pack-objects: drop unused return value from add_object_entry()
+ pack-bitmap: mark object filter as `const`
+ odb/source-packed: improve lookup when enumerating objects
+ Merge branch 'ps/odb-drop-whence' into ps/odb-for-each-object-filter
The object database enumeration interface 'odb_for_each_object()'
has been taught to accept object filters, allowing the underlying
backends to optimize the traversal by using reachability bitmaps
when available. 'git cat-file --batch-all-objects' has been updated
to use this generic interface, simplifying its code and avoiding
direct access to ODB backend internals.
Will merge to 'master'.
cf. <874ii0h2uf.fsf@emacs.iotcl.com>
source: <20260715-pks-odb-for-each-object-filter-v4-0-616d7adf7fb7@pks.im>
* ps/refs-wo-the-repository (2026-07-15) 7 commits
- refs: remove remaining uses of `the_repository`
- worktree: pass repository to public functions
- worktree: pass repository to file-local functions
- worktree: refactor code to use available repositories
- refs/files: drop `USE_THE_REPOSITORY_VARIABLE`
- refs/packed: de-globalize handling of "core.packedRefsTimeout"
- Merge branch 'ps/refs-writing-subcommands' into ps/refs-wo-the-repository
The ref subsystem and the worktree API have been refactored to pass a
repository pointer down the call chain, allowing them to drop
references to the global 'the_repository' variable. As part of this,
the handling of the 'core.packedRefsTimeout' configuration has been
moved into the per-repository ref store structure.
Will merge to 'next'.
source: <20260716-pks-refs-wo-the-repository-v3-0-db0a804e0224@pks.im>
* kk/commit-graph-topo-levels-fix (2026-07-09) 2 commits
(merged to 'next' on 2026-07-12 at 295a5f9b34)
+ commit-graph: propagate topo_levels slab to all chain layers
+ commit-graph: add trace2 instrumentation for generation DFS
The 'topo_levels' slab was propagated only to the topmost layer of a
split commit-graph chain, causing topological levels for commits in
base layers to be recomputed during incremental writes. This has been
corrected.
Will merge to 'master'.
cf. <alFu8gZURKhYr1VE@com-79390>
source: <pull.2170.v2.git.1783609382.gitgitgadget@gmail.com>
* ds/sparse-index-ita-crash (2026-07-06) 1 commit
- sparse-index: avoid crash on intent-to-add entry outside the cone
A crash in the 'sparse-index' collapse code when encountering an
invalidated cache-tree node (due to an intent-to-add path) has been
fixed by avoiding collapsing such subtrees.
Needs review.
source: <pull.2167.git.1783345853272.gitgitgadget@gmail.com>
* ij/subtree-reject-v2-config (2026-07-06) 2 commits
- git-subtree: Bail out if we find output from Rust rewrite (test)
- git-subtree: Bail out if we find output from Rust rewrite
The shell script implementation of 'git subtree' has been updated to
check for the presence of the configuration file of the new Rust
implementation, preventing users from accidentally running the old
script on repositories already managed by the new tool.
Expecting a reroll.
cf. <27219.20156.438730.881821@chiark.greenend.org.uk>
source: <20260706115816.20267-1-ijackson@chiark.greenend.org.uk>
* kk/reftable-tombstone-quadratic-fix (2026-07-10) 2 commits
(merged to 'next' on 2026-07-12 at 4e60bb0027)
+ reftable: fix quadratic behavior in the presence of tombstones
+ t/perf: add perf test for ref tombstone scenarios
The performance of ref updates and reads using the 'reftable' backend
in the presence of many deletion tombstone records has been optimized
by removing the tombstone suppression flag from the merged iterator
and instead skipping tombstones at higher-level call sites where
iteration bounds are known.
Will merge to 'master'.
cf. <alECc90WZ9RPqMaA@pks.im>
source: <pull.2166.v3.git.1783679767.gitgitgadget@gmail.com>
* jm/t0213-skip-emulated-ancestry-tests (2026-07-06) 1 commit
- t0213: skip ancestry tests under user-mode emulation
The 'TRACE2_ANCESTRY' prerequisite in the 't0213' test script has been
refined to avoid failures under user-mode emulation, by verifying that
the ancestry collector reports the expected process names rather than
the emulator binary name.
Needs review.
source: <pull.2168.git.1783359242130.gitgitgadget@gmail.com>
* bc/parse-options-exit-0-on-help (2026-07-07) 4 commits
(merged to 'next' on 2026-07-10 at 775654e447)
+ parse-options: exit 0 on -h
+ rev-parse: have --parseopt callers exit 0 on --help
+ parse-options: add a separate case for help output on error
+ t1517: skip svn tests if svn is not installed
Option parsing with 'git rev-parse --parseopt' and in most 'git'
subcommands has been updated to exit with 0 (instead of 129) when the
help option ('-h' or '--help') is requested directly by the user,
aligning with standard Unix convention.
Will merge to 'master'.
cf. <20260708035930.GB41684@coredump.intra.peff.net>
source: <20260708001557.3581080-1-sandals@crustytoothpaste.net>
* zy/apply-abandoned-header-fix (2026-07-01) 1 commit
- apply: avoid leaking abandoned git-header state
A candidate 'git diff' header parsed by 'git apply' has been isolated
in a temporary structure, preventing any partially parsed state from
polluting the main patch structure and causing assertions to trip if
the header is ultimately rejected.
Needs review.
source: <20260702041759.51572-1-zhihao.yao@njit.edu>
* ml/t9811-replace-test-f (2026-07-11) 2 commits
(merged to 'next' on 2026-07-15 at ffb7fcad15)
+ t9811: replace 'test -f' and '! test -f' with 'test_path_*'
+ t9811: break long && chains into multiple lines
The test script 't/t9811-git-p4-label-import.sh' has been
modernized to use 'test_path_is_file' and 'test_path_is_missing'
instead of raw 'test -f' and '! test -f' calls.
Will merge to 'master'.
cf. <alTHrUEh4_O5ROeu@pks.im>
source: <20260711160447.99708-1-marcelomlage@usp.br>
* sn/osxkeychain-rust-universal (2026-07-07) 3 commits
(merged to 'next' on 2026-07-10 at fe82b5d188)
+ contrib: wire up osxkeychain in contrib/Makefile on macOS
+ Makefile: support universal macOS builds via RUST_TARGETS
+ Makefile: add $(RUST_LIB) prerequisite to osxkeychain
The build system has been updated to support building universal macOS
binaries when 'Rust' is enabled, by compiling separate static archives
for each target triple listed in 'RUST_TARGETS' and combining them
using the macOS 'lipo' tool. The 'git-credential-osxkeychain' helper
has been updated to link against '$(RUST_LIB)' when 'Rust' is enabled.
Will merge to 'master'.
cf. <xmqq4ii9teym.fsf@gitster.g>
source: <pull.2288.v8.git.git.1783480879.gitgitgadget@gmail.com>
* cl/conditional-config-on-worktree-path (2026-07-09) 2 commits
(merged to 'next' on 2026-07-15 at 86ca33c437)
+ config: add "worktree" and "worktree/i" includeIf conditions
+ config: refactor include_by_gitdir() into include_by_path()
The '[includeIf "condition"]' conditional inclusion facility for
configuration files has been taught to use the location of the
worktree in its condition.
Will merge to 'master'.
cf. <alTJCTKR9jOWfgbk@pks.im>
source: <20260710-includeif-worktree-v8-0-04686d8a616c@black-desk.cn>
* kk/commit-reach-find-all-fix (2026-06-29) 2 commits
(merged to 'next' on 2026-07-10 at 0444c74d81)
+ commit-reach: guard !FIND_ALL early exit with generation ordering check
+ t6600: add test for merge-base early exit with clock skew
(this branch is used by kk/merge-base-exhaustion.)
The early-exit optimization in 'paint_down_to_common()' has been
gated on the queue being generation-ordered, fixing a bug where
'git merge-base' (without '--all') could return incorrect results
on repositories with v1 commit graphs and clock skew.
Will merge to 'master'.
cf. <xmqqjyr5v1gu.fsf@gitster.g>
source: <pull.2162.git.1782739162.gitgitgadget@gmail.com>
* bl/t7412-use-test-path-helpers (2026-06-29) 1 commit
- submodule absorbgitdirs tests: use test_* helper functions
The test script 't7412' that tests 'git submodule absorbgitdirs' has
been modernized to use 'test_path_is_file', 'test_path_is_dir', and
'test_path_is_missing' helper functions instead of raw 'test -[fde]'
commands.
Waiting for response.
cf. <akTKHfKPsP3-Rn31@pks.im>
source: <20260630020220.1559190-1-bblima@usp.br>
* ps/setup-split-discovery-and-setup (2026-07-07) 16 commits
(merged to 'next' on 2026-07-10 at 1691a942ab)
+ setup: mark `set_git_work_tree()` as file-local
+ setup: pass worktree to `init_db()`
+ setup: drop redundant configuration of `startup_info->have_repository`
+ setup: make repository discovery self-contained
+ setup: propagate prefix via repository discovery
+ setup: drop static `cwd` variable
+ setup: move prefix into repository
+ setup: embed repository format in discovery
+ setup: introduce explicit repository discovery
+ setup: split up concerns of `setup_git_env_internal()`
+ setup: unify setup of shallow file
+ setup: mark bogus worktree in `apply_repository_format()`
+ setup: rename `check_repository_format_gently()`
+ Merge branch 'jk/repo-info-path-keys' into ps/setup-split-discovery-and-setup
+ Merge branch 'ps/setup-drop-global-state' into ps/setup-split-discovery-and-setup
+ Merge branch 'ps/refs-onbranch-fixes' into ps/setup-split-discovery-and-setup
The repository discovery and repository configuration phases, which
were previously intertwined in 'setup.c', have been split. Repository
discovery has been updated to populate a 'struct repo_discovery'
without modifying the repository state, which is then taken by
repository configuration to initialize the repository, paving the way
for clean unification of repository configuration.
Will merge to 'master'.
cf. <87h5m9om0j.fsf@emacs.iotcl.com>
source: <20260707-pks-setup-split-discovery-and-setup-v2-0-aab372cd227c@pks.im>
* pw/rebase-drop-notes-with-commit (2026-07-15) 9 commits
- sequencer: do not record dropped commits as rewritten
- sequencer: use an enum to represent result of picking a commit
- sequencer: simplify pick_one_commit()
- sequencer: remove unnecessary condition in pick_one_commit()
- sequencer: simplify handling of fixup with conflicts
- sequencer: remove unnecessary "or" in pick_one_commit()
- sequencer: never reschedule on failed commit
- sequencer: be more careful with external merge
- t3400: restore coverage for note copying with apply backend
The rebase post-rewrite notes-copying logic has been corrected. When
a commit is dropped during rebase (e.g., because its changes are
already upstream), it is no longer recorded as rewritten, preventing
its notes from being copied to an unrelated commit.
Needs review.
source: <cover.1784128921.git.phillip.wood@dunelm.org.uk>
* tb/repack-geometric-cruft (2026-06-28) 11 commits
- SQUASH??? bare grep !???
- repack: support combining '--geometric' with '--cruft'
- pack-objects: support '--refs-snapshot' with 'follow-reachable'
- pack-objects: introduce '--stdin-packs=follow-reachable'
- pack-objects: extract `stdin_packs_add_all_pack_entries()`
- repack-geometry: drop unused redundant-pack removal
- repack: delete geometric packs via existing_packs
- repack: teach MIDX retention about geometric rollups
- repack: mark geometric progression of packs as retained
- repack: extract `locate_existing_pack()` helper
- repack: unconditionally exclude non-kept packs
'git repack' has been taught to accept '--geometric' and '--cruft'
together. When both are given, non-cruft packs are rolled up by the
geometric repack as usual, while a separate cruft pack is written to
collect unreachable objects.
Waiting for response.
cf. <xmqq8q8068f7.fsf@gitster.g>
cf. <xmqqpl1d56dd.fsf@gitster.g>
source: <cover.1782500507.git.me@ttaylorr.com>
* jt/receive-pack-use-odb-transactions (2026-07-10) 11 commits
(merged to 'next' on 2026-07-15 at aba57e3365)
+ builtin/receive-pack: stage incoming objects via ODB transactions
+ builtin/receive-pack: drop redundant tmpdir env
+ odb/transaction: introduce ODB transaction flags
+ odb/transaction: add transaction env interface
+ odb/transaction: propagate commit errors
+ odb/transaction: propagate begin errors
+ object-file: propagate files transaction errors
+ object-file: drop check for inflight transactions
+ object-file: embed transaction flush logic in commit function
+ object-file: rename files transaction fsync function
+ object-file: rename files transaction prepare function
(this branch is used by ps/odb-move-loose-object-writing.)
'git receive-pack' has been refactored to use ODB transaction
interfaces instead of directly managing 'tmp_objdir' for staging
incoming objects, bringing it closer to being ODB backend agnostic.
Will merge to 'master'.
cf. <alR1P-RGZNmjyiUE@pks.im>
source: <20260710163722.2962278-1-jltobler@gmail.com>
* ps/reftable-hardening (2026-07-03) 12 commits
(merged to 'next' on 2026-07-10 at b8f4dd0ab9)
+ reftable/table: fix OOB read on truncated table
+ reftable/table: fix NULL pointer access when seeking to bogus offsets
+ reftable/block: fix OOB read with bogus restart offset
+ reftable/block: fix use of uninitialized memory when binsearch fails
+ reftable/block: fix OOB read with bogus restart count
+ reftable/block: fix OOB read with bogus block size
+ reftable/block: fix OOB write with bogus inflated log size
+ t/unit-tests: introduce test helper to write reftable blocks
+ reftable/record: don't abort when decoding invalid ref value type
+ reftable/basics: fix OOB read on binary search of empty range
+ oss-fuzz: add fuzzer for parsing reftables
+ meson: support building fuzzers with libFuzzer
The 'reftable' code has been hardened against corrupted tables by
fixing out-of-bounds writes, out-of-bounds reads, and abort calls
during parsing.
Will merge to 'master'.
cf. <877bn5obz9.fsf@emacs.iotcl.com>
source: <20260703-pks-reftable-hardening-v3-0-b87c555b9920@pks.im>
* ty/migrate-excludes-file (2026-07-13) 10 commits
- repository: adjust the comment of config_values_private_
- environment: move object_creation_mode into repo_config_values
- environment: move autorebase into repo_config_values
- environment: move push_default into repo_config_values
- environment: migrate apply_default_whitespace and apply_default_ignorewhitespace
- environment: move askpass_program into repo_config_values
- environment: move pager_program into repo_config_values
- environment: move editor_program into repo_config_values
- environment: move excludes_file into repo_config_values
- repository: introduce repo_config_values_clear()
The 'excludes_file' and various other global configuration variables
(including 'editor_program', 'pager_program', 'askpass_program', and
'push_default') have been migrated into the per-repository structure.
Needs review.
cf. <xmqq8q7961xe.fsf@gitster.g>
source: <20260714032525.1611141-1-cat@malon.dev>
* ps/libgit-in-subdir (2026-07-12) 3 commits
. Move libgit.a sources into separate "lib/" directory
. t/helper: prepare "test-example-tap.c" for introduction of "lib/"
. Merge branch 'ps/odb-source-packed' into ps/libgit-in-subdir
The source files for 'libgit.a' have been moved into a new 'lib/'
directory to clean up the top-level directory and clearly separate
library code.
Ejected for now, as it causes too many evil merges with other topics.
Waiting for response.
cf. <alR9GDNTbdjWB4dq@szeder.dev>
source: <20260713-pks-libgit-in-subdir-v4-0-696240876eb1@pks.im>
* ty/migrate-ignorecase (2026-06-19) 2 commits
(merged to 'next' on 2026-07-12 at 39e9fdb93f)
+ config: use repo_ignore_case() to access core.ignorecase
+ environment: move ignore_case into repo_config_values
The global configuration variable 'ignore_case' (representing the
'core.ignorecase' configuration) has been migrated into 'struct
repo_config_values' to tie it to a specific repository instance.
Will merge to 'master'.
cf. <xmqqechaga7p.fsf@gitster.g>
source: <20260619155152.642760-1-cat@malon.dev>
* mm/line-log-limited-ops (2026-06-27) 7 commits
- diffcore-pickaxe: scope -G to the -L tracked range
- diff: support --check with -L line ranges
- line-log: support diff stat formats with -L
- diff: extract a line-range diff helper for reuse
- diff: emit -L hunk headers via xdiff's formatter
- diff: simplify the line-range filter by classifying removals immediately
- diff: rename and group the line-range filter for clarity
The 'git log -L<range>:<path>' command has been taught to limit
various 'diff' operations, such as '--stat', '--check', and '-G', to
the specified range and path.
Needs review.
source: <pull.2152.v2.git.1782581342.gitgitgadget@gmail.com>
* hn/history-squash (2026-07-15) 5 commits
- history: re-edit a squash with every message
- sequencer: share the squash message marker helpers and flags
- history: add squash subcommand to fold a range
- history: give commit_tree_ext a message template
- history: extract helper for a commit's parent tree
The experimental 'git history' command has been taught a new 'squash'
subcommand to fold a range of commits into a single commit, with any
descendants replayed on top.
Needs review.
source: <pull.2337.v9.git.git.1784128573.gitgitgadget@gmail.com>
* wy/doc-myfirstcontribution-trim-quotes (2026-06-11) 1 commit
(merged to 'next' on 2026-07-12 at adeaa999b6)
+ MyFirstContribution: mention trimming quoted text in replies
The contributor guide has been updated to advise new contributors to
trim irrelevant quoted text when replying to review comments, matching
the existing advice given to reviewers.
Will merge to 'master'.
cf. <xmqqcxxwljue.fsf@gitster.g>
source: <080402ff0ac8127b654dccea59a1bf643df62a5c.1781186476.git.wy@wyuan.org>
* tb/midx-incremental-custom-base (2026-06-12) 3 commits
- midx-write: include packs above custom incremental base
- midx: pass custom '--base' through incremental writes
- t5334: expose shared `nth_line()` helper
The 'git multi-pack-index write --incremental' command has been
corrected to properly honor the '--base' option. Previously, the
custom base was ignored by the normal write path; packs from layers
above the selected base were incorrectly skipped by the pack exclusion
logic, and reachability closure for bitmaps was broken.
Needs review.
source: <cover.1781294771.git.me@ttaylorr.com>
* mm/test-grep-lint (2026-07-05) 6 commits
(merged to 'next' on 2026-07-10 at 1916c07bf5)
+ t: add greplint to detect bare grep assertions
+ t: convert grep assertions to test_grep
+ t: fix Lexer line count for $() inside double-quoted strings
+ t: extract chainlint's parser into shared module
+ t: fix grep assertions missing file arguments
+ t/README: document test_grep helper
The test suite has been updated to use the 'test_grep' helper instead
of bare 'grep' for test assertions, allowing file contents to be
printed on failure for easier debugging. A new 'greplint' linter has
been introduced to detect and prevent new bare 'grep' assertions from
being added to the test suite.
Will merge to 'master'.
cf. <xmqqtsqedxmt.fsf@gitster.g>
source: <pull.2135.v4.git.1783314119.gitgitgadget@gmail.com>
* td/ref-filter-memoize-contains (2026-06-12) 3 commits
- commit-reach: die on contains walk errors
- ref-filter: memoize --contains with generations
- commit-reach: reject cycles in contains walk
'git branch --contains' and 'git for-each-ref --contains' have been
optimized to use the memoized commit traversal previously used only by
'git tag --contains', significantly speeding up connectivity checks
across many candidate refs with shared history.
Will merge to 'next'.
cf. <20260716091924.GB1212956@coredump.intra.peff.net>
source: <20260612-ref-filter-memoized-contains-v4-0-5ed39fd001dd@gmail.com>
* tc/replay-linearize (2026-07-07) 3 commits
(merged to 'next' on 2026-07-09 at 371c2e9c3b)
+ replay: offer an option to linearize the commit topology
+ replay: resolve the replay base outside pick_regular_commit()
+ replay: add helper to put entry into replayed_commits
The 'git replay' command has been taught the '--linearize' option to
drop merge commits and linearize the replayed history, mimicking 'git
rebase --no-rebase-merges'.
On hold, waiting for response from the author.
cf. <xmqq5x2qz42z.fsf@gitster.g>
cf. <CABPp-BGzU9KHGF1nipi2HZaa1AiikMKGGaapQzHVH06wO4V1ww@mail.gmail.com>
source: <20260707-toon-git-replay-drop-merges-v7-0-808ab9b4afa6@iotcl.com>
* ps/cat-file-remote-object-info (2026-07-18) 13 commits
- cat-file: make remote-object-info allow-list adapt to the server
- cat-file: add remote-object-info to batch-command
- transport: add client support for object-info
- serve: advertise object-info feature
- protocol-caps: check object existence regardless of the attributes requested
- fetch-pack: move fetch initialization
- connect: make write_fetch_command_and_capabilities() more generic
- fetch-pack: move write_fetch_command_and_capabilities() to connect.c
- fetch-pack: use unsigned int for hash_algo variable
- fetch-pack: drop the static advertise_sid variable
- t1006: extract helper functions into new 'lib-cat-file.sh'
- cat-file: declare loop counter inside for()
- transport-helper: fix memory leak of helper on disconnect
The 'remote-object-info' command has been added to 'git cat-file
--batch-command', allowing clients to request object metadata
(currently size) from a remote server via protocol v2 without
downloading the entire object. Format placeholders are dynamically
filtered on the client based on server-advertised capabilities,
returning empty strings for inapplicable or unsupported fields.
Needs review.
source: <20260718-ps-eric-work-rebase-v20-0-0c13962ac532@gmail.com>
* mm/diff-process-hunks (2026-07-15) 9 commits
. line-log: consult diff process for range tracking
. diff: consult diff process for --stat counts
. blame: consult diff process for no-hunk detection
. diff: bypass diff process with --no-ext-diff and in format-patch
. diff: add long-running diff process via diff.<driver>.process
. sub-process: separate process lifecycle from hashmap management
. userdiff: add diff.<driver>.process config
. xdiff: support external hunks via xpparam_t
. gitattributes: document how external diff drivers relate to diff features
A new 'diff.<driver>.process' configuration has been introduced to
allow a long-running external process to act as a hunk provider,
enabling external tools to control which lines Git considers changed
while leaving all output formatting (word diff, color, blame, etc.) to
Git's standard pipeline.
Ejected for now, as it conflicts badly with 'mm/line-log-limited-ops'.
Expecting a reroll.
cf. <xmqq8q7aj3b0.fsf@gitster.g>
cf. <CAC2QwmKRp90hmBAckug9PPvvD53Pi53q5csZhi15LRhzdQasQg@mail.gmail.com>
source: <pull.2120.v5.git.1784149323.gitgitgadget@gmail.com>
* ty/migrate-trust-executable-bit (2026-07-16) 4 commits
- environment: move has_symlinks into repo_config_values
- environment: move trust_executable_bit into repo_config_values
- read-cache: pass 'repo' to 'ce_mode_from_stat()'
- read-cache: remove redundant extern declarations
The 'trust_executable_bit' (coming from the 'core.filemode'
configuration) has been migrated into 'struct repo_config_values' to
tie it to a specific repository instance.
Waiting for response.
cf. <xmqq8q7961xe.fsf@gitster.g>
source: <20260717063559.1633567-1-cat@malon.dev>
* za/completion-hide-dotfiles (2026-06-20) 2 commits
- completion: hide dotfiles by default for path completion
- completion: hide dotfiles for selected path completion
Path completion for commands like 'git rm' and 'git mv' has been
updated to hide dotfiles by default unless the user explicitly starts
the path with a dot, matching standard shell-completion behavior.
Waiting for response, stalled.
cf. <xmqqik71t3nr.fsf@gitster.g>
source: <pull.2311.v3.git.git.1781978156.gitgitgadget@gmail.com>
* ec/commit-fixup-options (2026-05-26) 2 commits
- commit: allow -c/-C for all kinds of --fixup
- commit: allow -m/-F for all kinds of --fixup
Support for '-m', '-F', '-c', or '-C' options to supply a commit log
message from outside the editor has been added for all 'git commit
--fixup' variations.
Needs review.
source: <cover.1779792311.git.erik@cervined.in>
* kh/doc-replay-config (2026-06-05) 4 commits
- doc: replay: move “default” to the right-hand side
- doc: replay: use a nested description list
- doc: replay: improve config description
- doc: link to config for git-replay(1)
Documentation for 'git replay' has been updated to refer to its
configuration variables.
Waiting for response for too long, stalled.
cf. <87cxwxofgv.fsf@emacs.iotcl.com>
source: <V3_CV_doc_replay_config.780@msgid.xyz>
* hn/branch-delete-merged (2026-07-14) 7 commits
- branch: add --dry-run for --delete-merged
- branch: add branch.<name>.deleteMerged opt-out
- branch: add --delete-merged <branch>
- branch: prepare delete_branches for a bulk caller
- branch: let delete_branches skip unmerged branches on bulk refusal
- branch: convert delete_branches() to a flags argument
- branch: add --forked filter for --list mode
The 'git branch' command has been taught the '--delete-merged' option
to remove local branches that are already merged into their tracked
remote-tracking branches.
Needs review.
cf. <xmqqtspvptqc.fsf@gitster.g>
source: <pull.2285.v19.git.git.1784053493.gitgitgadget@gmail.com>
* hn/checkout-track-fetch (2026-06-24) 2 commits
- checkout: extend --track with a "fetch" mode to refresh start-point
- branch: expose helpers for finding the remote owning a tracking ref
The 'git checkout --track=...' command has been taught to optionally
fetch the branch from the remote that the new branch will work with.
Waiting for response for too long, stalled.
cf. <xmqq5x37h6fj.fsf@gitster.g>
cf. <CAL71e4MiijEiM26TKJcOYT7L4pfQeMM_F2oT3U3igP-wOZm2Ag@mail.gmail.com>
source: <pull.2281.v15.git.git.1782338098.gitgitgadget@gmail.com>
* ps/shift-root-in-graph (2026-07-14) 7 commits
- graph: add --[no-]graph-indent and log.graphIndent
- graph: move config reading into graph_read_config()
- graph: wrap cascading commits after 4 columns
- graph: indent visual root in graph
- graph: add a 2 commit buffer for lookahead
- revision: add next_commit_to_show()
- lib-log-graph: move check_graph function
'git log --graph' has been modified to visually distinguish parentless
'root' commits (and commits that become roots due to history
simplification) by indenting them, preventing them from appearing
falsely related to unrelated commits rendered immediately above them.
Will merge to 'next'.
cf. <CA+J6zkQNzEAhhY74qDrOwfFVrshEF7YFxWRRkwE3ttJo15ZbAg@mail.gmail.com>
source: <20260714-ps-pre-commit-indent-v12-0-d50938e006df@gmail.com>
* kk/merge-base-exhaustion (2026-07-11) 11 commits
- commit-reach: remove commit-date ordering fallback
- commit-reach: move min_generation check into paint_queue_get()
- commit-reach: terminate merge-base walk when one paint side is exhausted
- commit-reach: introduce struct paint_state with per-side counters
- t6600: add clock-skew topologies and step counts for edge cases
- commit-reach: add trace2 instrumentation to paint_down_to_common()
- t6099, t6600: add side-exhaustion regression tests
- t6600: add test cases for side-exhaustion edge cases
- test-lib-functions: improve diagnostic output for trace2 data assertions
- Documentation/technical: add paint-down-to-common doc
- Merge branch 'kk/commit-reach-find-all-fix' into kk/merge-base-exhaustion
(this branch uses kk/commit-reach-find-all-fix.)
The merge-base computation has been optimized by stopping the walk
early when one side's exclusive commits in the queue are exhausted,
yielding significant speedups for queries with one-sided histories.
Needs review.
source: <pull.2149.v6.git.1783776466.gitgitgadget@gmail.com>
--------------------------------------------------
[Discarded]
* kk/prio-queue-cascade-sift (2026-07-08) 3 commits
. prio-queue: use cascade for unfused gets
. prio-queue: extract sift_up() from prio_queue_put()
. Merge branch 'kk/prio-queue-get-put-fusion' into kk/prio-queue-cascade-sift
'prio_queue_get()' has been optimized by using a cascade-down approach
(promoting the smaller child at each level and sifting up the last
element from the leaf vacancy), whereby the number of comparisons per
extract-min operation is halved in the common case.
Retracted.
cf. <CAL71e4PRVYfUWc-c+6XHTwtADqrbub9ykbo+rPyramDhJw=Rfg@mail.gmail.com>
source: <pull.2132.v3.git.1783532989.gitgitgadget@gmail.com>
* ap/http-redirect-wwwauth-fix (2026-06-02) 1 commit
. http: preserve wwwauth_headers across redirects
When 'cURL' follows a redirect, the 'WWW-Authenticate' headers from
the redirect target were lost because 'credential_from_url()' cleared
the credential state. This has been fixed by preserving the collected
headers across the redirect update.
Discarded.
cf. <xmqqmrw2zavx.fsf@gitster.g>
source: <20260602161150.1527493-1-aplattner@nvidia.com>
* dk/meson-enable-use-nsec-build (2026-06-20) 1 commit
. meson: wire up USE_NSEC build knob
The 'USE_NSEC' build knob, which enables support for sub-second file
timestamp resolution, has been wired up to the Meson build system.
Discarded.
cf. <xmqqa4rx9mb5.fsf@gitster.g>
cf. <45F2C180-1DE1-4371-869B-BF605B64E01A@gmail.com>
source: <c4c5ade901ff95b0f95939ea818870e4f3d59da1.1781971201.git.ben.knoble+github@gmail.com>
^ permalink raw reply
* Re: [PATCH 0/2] Rust hash cleanups
From: Jeff King @ 2026-07-19 8:07 UTC (permalink / raw)
To: brian m. carlson; +Cc: git, Junio C Hamano, Patrick Steinhardt
In-Reply-To: <20260719010842.17991-1-sandals@crustytoothpaste.net>
On Sun, Jul 19, 2026 at 01:08:40AM +0000, brian m. carlson wrote:
> Peff recently sent out a series to fix several memory leaks with our
> hashing code when not using the default block algorithm. This series
> follows up with a few fixes to our Rust hash code, which calls the C
> code, to fix various memory problems.
Both of these look good to me (modulo my almost-zero knowledge of the
Rust bits).
I was worried at first that I had introduced new problems with my fixes,
but I think these are both pre-existing issues (really just variants of
the cleanups I did in the C code).
For patch 1, an alternative is to switch git_hash_clone() to _not_
require initialization. But it introduces the leak problem in the
opposite direction. E.g., hashfile_truncate() wants to overwrite
existing state, so it would now need to discard() before cloning. I
doubt it's worth the effort or risk of regression to save the tiny bit
of effort spent on a few init-then-overwrite cases.
So the approach taken here makes sense (and obviously this is just
following the C code's lead anyway).
-Peff
^ permalink raw reply
* Re: [PATCH] branch: report kind of checkout when rejecting delete
From: Phillip Wood @ 2026-07-19 9:50 UTC (permalink / raw)
To: Junio C Hamano, René Scharfe
Cc: Toon Claes, Patrick Steinhardt, Git List, stsp
In-Reply-To: <xmqqa4roq7a8.fsf@gitster.g>
On 18/07/2026 23:09, Junio C Hamano wrote:
> René Scharfe <l.s.r@web.de> writes:
>
>>>> + switch (kind) {
>>>> + case BRANCH_CHECKOUT_KIND_CHECKOUT:
>>>> + error(_("cannot delete branch '%s' "
>>>> + "used by worktree at '%s'"),
>>>> + bname.buf, path);
>>>> + break;
>>>
>>> We may want to be more explicit and say "cannot delete
>>> branch 'frotz' checked out in worktree at '/tmp/nitfol'"
>>> instead. Unless this is a catch-all entry for states that
>>> are neither 'rebase', 'bisect', nor 'rebase-merges' but are
>>> somehow otherwise in use, that is.
That's a great suggestion, I don't think there are any other cases so it
should be fine to say "checked out".
>>>> + case BRANCH_CHECKOUT_KIND_UPDATE_REF:
>>>> + error(_("cannot delete branch '%s' "
>>>> + "used by worktree at '%s' "
>>>> + "for update-ref"),
>>>> + bname.buf, path);
>>>> + break;
>>>
>>> I was quite lost when searching for cases where this 'update-ref'
>>> state might be encountered, and I still lack confidence. Can
>>> we make the diagnostic message a bit friendlier to our users?
>>>
>>> For instance, something like: 'You are rebasing a history with
>>> merges in that other worktree, and the tip of this branch will
>>> be updated when that process completes, so you cannot delete
>>> it from here.' (Naturally, I may have misidentified the exact
>>> nature of the error, but this illustrates the level of detail and
>>> user-facing clarity I hope to see.)
>>
>> That's quite long. Would it make sense to throw that update-ref
>> case into the rebase bin, i.e. only distinguish between checkout,
>> bisect and rebase?
I also wondered whether we should fold this into the rebase case. My
concern is that if the user sees
cannot delete branch 'feature' because it is being rebased in the
worktree '../feature'
and then they do
cd ../feature
git status
they'll see a different branch name in the status output which is
confusing. So I think we either need to improve the status output to
show all the branches that are being rewritten (which to my mind is the
better option, it is more work but shouldn't be too difficult as it
already parses "rebase-merge/git-rebase-todo" and "rebase-merge/done"),
or say something like
cannot delete branch 'feature' because it is being updated by a
rebase running in '../feature' which is updating multiple branches.
for the update-refs case.
Thanks for working on this, it is a nice usability improvement.
Phillip
> Shortening a quite long expression down to digestable pieces is left
> as an exercise for those with this particular itch to scratch ;-).
> I do not personally mind if it ends up indistinguishable from other
> "rebase" case (or unified the "kind" enum into one), but others may
> have ideas to shorten the message to fit in the pattern we see
> above.
>
> Thanks.
^ permalink raw reply
* [PATCH] completion: complete paths for git send-email
From: Yury Norov (NVIDIA) @ 2026-07-19 13:44 UTC (permalink / raw)
To: git, Thiago Perrotta, Philippe Blain, Junio C Hamano,
Rubén Justo
Cc: Yury Norov, linux-kernel, Yury Norov, Codex
From: Yury Norov <ynorov@nvidia.com>
git send-email accepts either revisions or paths to patch files, but its
Bash completion only offers revisions. This prevents patch files from
being completed. It can also make a prefix such as "0" expand to an
unrelated hexadecimal ref even when matching 0001-*.patch files exist.
In my Linux tree, an attempt to autocomplete the standard-named patch
brings a random hashtag:
$ ls 0*
0001-bitmap-drop-bitmap_next_set_region.patch
$ git send-email 0<Tab>
$ git send-email 05c69d298c96703741cac9a5cbbf6c53bd55a6e2
Introduce an append variant of __gitcomp_file() and use it to add
filesystem candidates after the existing revision candidates. Keep the
latter because revisions remain valid send-email arguments.
Add a regression test covering patch files alongside a 40-hex ref.
Assisted-by: Codex <codex@openai.com>
Signed-off-by: Yury Norov <ynorov@nvidia.com>
---
contrib/completion/git-completion.bash | 29 +++++++++++++++++++-------
t/t9902-completion.sh | 12 ++++++++++-
2 files changed, 33 insertions(+), 8 deletions(-)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e87578771..b7017488d 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -579,21 +579,18 @@ __gitcomp_file_direct ()
}
# Generates completion reply with compgen from newline-separated possible
-# completion filenames.
+# completion filenames by appending them to the existing list of completion
+# candidates, COMPREPLY.
# It accepts 1 to 3 arguments:
# 1: List of possible completion filenames, separated by a single newline.
# 2: A directory prefix to be added to each possible completion filename
# (optional).
# 3: Generate possible completion matches for this word (optional).
-__gitcomp_file ()
+__gitcomp_file_append ()
{
local IFS=$'\n'
- # XXX does not work when the directory prefix contains a tilde,
- # since tilde expansion is not applied.
- # This means that COMPREPLY will be empty and Bash default
- # completion will be used.
- __gitcompadd "$1" "${2-}" "${3-$cur}" ""
+ __gitcompappend "$1" "${2-}" "${3-$cur}" ""
# use a hack to enable file mode in bash < 4
compopt -o filenames +o nospace 2>/dev/null ||
@@ -601,6 +598,23 @@ __gitcomp_file ()
true
}
+# Generates completion reply with compgen from newline-separated possible
+# completion filenames.
+# It accepts 1 to 3 arguments:
+# 1: List of possible completion filenames, separated by a single newline.
+# 2: A directory prefix to be added to each possible completion filename
+# (optional).
+# 3: Generate possible completion matches for this word (optional).
+__gitcomp_file ()
+{
+ # XXX does not work when the directory prefix contains a tilde,
+ # since tilde expansion is not applied.
+ # This means that COMPREPLY will be empty and Bash default
+ # completion will be used.
+ COMPREPLY=()
+ __gitcomp_file_append "$@"
+}
+
# Find the current subcommand for commands that follow the syntax:
#
# git <command> <subcommand>
@@ -2634,6 +2648,7 @@ _git_send_email ()
;;
esac
__git_complete_revlist
+ __gitcomp_file_append "$(compgen -f -- "$cur")"
}
_git_stage ()
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 55dc9eabf..e87827f21 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2777,7 +2777,17 @@ test_expect_success PERL 'send-email' '
test_completion "git send-email --val" <<-\EOF &&
--validate Z
EOF
- test_completion "git send-email ma" "main "
+ test_completion "git send-email ma" "main " &&
+
+ git tag 05c69d298c96703741cac9a5cbbf6c53bd55a6e2 &&
+ test_when_finished "git tag -d 05c69d298c96703741cac9a5cbbf6c53bd55a6e2 &&
+ rm -f 0001-example.patch 0002-example.patch" &&
+ touch 0001-example.patch 0002-example.patch &&
+ test_completion "git send-email 0" <<-\EOF
+ 0001-example.patch
+ 0002-example.patch
+ 05c69d298c96703741cac9a5cbbf6c53bd55a6e2 Z
+ EOF
'
test_expect_success 'complete files' '
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v19 5/7] branch: add --delete-merged <branch>
From: Harald Nordgren @ 2026-07-19 15:30 UTC (permalink / raw)
To: Junio C Hamano
Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
Johannes Sixt, Phillip Wood
In-Reply-To: <xmqqtspvptqc.fsf@gitster.g>
I think I can fix this with
```
-+ if (strset_contains(data->deletable, ref->name))
++ if (strset_contains(data->deletable, ref->name) ||
++ strset_contains(data->spared, ref->name))
```
I used your example to write a test about it as well.
Harald
^ permalink raw reply
* Re: [PATCH] completion: complete paths for git send-email
From: Junio C Hamano @ 2026-07-19 17:04 UTC (permalink / raw)
To: Yury Norov (NVIDIA)
Cc: git, Thiago Perrotta, Philippe Blain, Rubén Justo,
Yury Norov, linux-kernel, Codex
In-Reply-To: <20260719134447.381835-1-yury.norov@gmail.com>
"Yury Norov (NVIDIA)" <yury.norov@gmail.com> writes:
> From: Yury Norov <ynorov@nvidia.com>
>
> git send-email accepts either revisions or paths to patch files, but its
> Bash completion only offers revisions. This prevents patch files from
> being completed. It can also make a prefix such as "0" expand to an
> unrelated hexadecimal ref even when matching 0001-*.patch files exist.
>
> In my Linux tree, an attempt to autocomplete the standard-named patch
> brings a random hashtag:
>
> $ ls 0*
> 0001-bitmap-drop-bitmap_next_set_region.patch
> $ git send-email 0<Tab>
> $ git send-email 05c69d298c96703741cac9a5cbbf6c53bd55a6e2
Wow. Even though I use nothing but 'git send-email' when sending my
own patches, I have never noticed this behavior. I guess that is
primarily because I only use the command via my own wrapper script,
so the usual bash completion kicks in only for filenames in my
workflow. Since I store my patches two levels deep in my working
tree (for example, '+outgo/topic/0000-cover-letter.txt'), I suspect
that even if I got rid of my wrapper, I would not suffer from this
issue. An attempt to run 'git send-email +outgo/contrib-doc/0<TAB>'
expanding the trailing '0' into a hexadecimal object name would
indeed be quite annoying.
Good find.
> Introduce an append variant of __gitcomp_file() and use it to add
> filesystem candidates after the existing revision candidates. Keep the
> latter because revisions remain valid send-email arguments.
OK. I will need help from those who are more familiar with our
completion code than I am to properly assess this change. Any
assistance in reviewing this would be appreciated.
> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
> index 55dc9eabf..e87827f21 100755
> --- a/t/t9902-completion.sh
> +++ b/t/t9902-completion.sh
> @@ -2777,7 +2777,17 @@ test_expect_success PERL 'send-email' '
> test_completion "git send-email --val" <<-\EOF &&
> --validate Z
> EOF
> - test_completion "git send-email ma" "main "
> + test_completion "git send-email ma" "main " &&
> +
> + git tag 05c69d298c96703741cac9a5cbbf6c53bd55a6e2 &&
> + test_when_finished "git tag -d 05c69d298c96703741cac9a5cbbf6c53bd55a6e2 &&
> + rm -f 0001-example.patch 0002-example.patch" &&
If the initial 'git tag' fails, 'test_when_finished' is never
registered, and we end up failing to remove the '000?-example.patch'
files. The usual way to write this is:
- set up 'test_when_finished' with a body that is written to
succeed even if the clean-up target is not present (your '-f' in
'rm -f' is good, as it prevents 'rm' from failing even if
'0001-example.patch' does not get created); then
- write the test code that dirties the state (requiring clean-up)
after registering the 'test_when_finished' handler.
That is, "Prepare the clean-up first, and then you do not have to
worry about making a mess."
By the way, the use of a purely hexadecimal string as a tag or
branch name is highly misleading. What happens if an object exists
whose name is identical to that tag? Git offers ways to
disambiguate if you really want to, but I do not see any reason for
a sensible person or workflow to deliberately place oneself in a
situation where such disambiguation becomes necessary.
Of course, that is no excuse for the bug. Our completion script
should not misbehave, even when confronted with a workflow that uses
funny-looking tags.
^ permalink raw reply
* Re: [PATCH v6 00/10] commit-reach: terminate merge-base walk when one side is exhausted
From: Junio C Hamano @ 2026-07-19 18:14 UTC (permalink / raw)
To: Kristofer Karlsson
Cc: Kristofer Karlsson via GitGitGadget, git, Derrick Stolee,
Elijah Newren, René Scharfe, SZEDER Gábor
In-Reply-To: <CAL71e4O5=ZJoPD4dnPmh8mjsTKtugx05-8d83VeQdBNOjp=bFw@mail.gmail.com>
Kristofer Karlsson <krka@spotify.com> writes:
> ...
> After that, all ten patches apply cleanly with git am -3.
>
> I should have stated this more clearly in the cover letter
> instead of mentioning next at all.
Well that is how I wiggled the series in my tree after all ;-)
In any case, we really need to get somebody take a look at these
patches to move them forward. Any takers?
Thanks.
^ permalink raw reply
* Re: [PATCH 5/5] use repo_hold_lock_file_for_update{,_mode,_timeout}() with custom repos
From: Junio C Hamano @ 2026-07-19 19:11 UTC (permalink / raw)
To: René Scharfe; +Cc: Patrick Steinhardt, git
In-Reply-To: <3c0a8031-7082-422a-b474-938418682b60@web.de>
René Scharfe <l.s.r@web.de> writes:
> On 7/15/26 11:52 AM, Patrick Steinhardt wrote:
>> On Tue, Jul 14, 2026 at 07:59:56PM +0200, René Scharfe wrote:
>>> Apply the config setting core.sharedRepository from the repository at
>>> hand instead of from the_repository.
>>
>> We only do this for a subset of callsites, apparently. How did you
>> select which subsystems to convert and which not to? To make this
>> explicit: I don't mind a partial migration, but I think the commit
>> message should briefly explain the reasoning behind it.
>
> All those that have a repository reference other than the_repository.
>
>> Also, as you don't get rid of the old functions that still implicitly
>> depend on `the_repository`, I think we should have an additional commit
>> on top that guards all functions that have this implicit dependency with
>> `USE_THE_REPOSITORY_VARIABLE`. This ensures that we cannot accidentally
>> call such functions from other subsystems that already got rid of the
>> global dependency.
>
> Probably, but the lockfile conversions deserve their own patch series.
> Patch 5 is only included here because it was easy to write. We can drop
> it and leave the low-hanging fruit on the tree if that's preferable.
I am personally indifferent as to what we do immediately in this
series, as long as we all agree on the longer-term direction. It
seems we are in agreement on providing additional safety in the
medium term?
Thanks.
^ permalink raw reply
* Re: [PATCH v19 5/7] branch: add --delete-merged <branch>
From: Junio C Hamano @ 2026-07-19 19:22 UTC (permalink / raw)
To: Harald Nordgren
Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
Johannes Sixt, Phillip Wood
In-Reply-To: <CAHwyqnXdaPeO12+p=_+_ttrknV0-VqTMnH-suS66yZ4stsBKnQ@mail.gmail.com>
Harald Nordgren <haraldnordgren@gmail.com> writes:
> I think I can fix this with
>
> ```
> -+ if (strset_contains(data->deletable, ref->name))
> ++ if (strset_contains(data->deletable, ref->name) ||
> ++ strset_contains(data->spared, ref->name))
> ```
>
> I used your example to write a test about it as well.
>
>
> Harald
I do not claim that the single example I gave covers all the issues
that arise from failing to analyze the dependency graph, or from
attempting to solve the problem sequentially, which makes the
solution depend on the order in which branches are visited.
I have a suspicion that it may be unavoidable to employ a multi-pass
approach that iteratively identifies all branches transitively
needed by any surviving branch, though that is merely a hunch,
unsupported by any proof.
Thanks.
^ permalink raw reply
* Re: [PATCH v3 0/9] sequencer: do not record dropped commits as rewritten
From: Junio C Hamano @ 2026-07-19 19:29 UTC (permalink / raw)
To: Phillip Wood
Cc: git, Uwe Kleine-König, Oswald Buddenhagen, Farid Zakaria,
Andrei Rybak
In-Reply-To: <cover.1784128921.git.phillip.wood@dunelm.org.uk>
Phillip Wood <phillip.wood123@gmail.com> writes:
> Thanks to everyone who commented on v2. I've dropped patch 2 which
> Andrei pointed out was pointless and tried to make the remaining
> commit messages clearer as requested by Oswald.
>
> If a commit gets dropped because its changes are already upstream
> then we should not record it as rewritten. As well as confusing any
> post-rewrite hooks this means we end up copying the notes from the
> dropped commit to the commit that was picked immediately before the
> one that was dropped.
>
> This series is structured as follows:
>
> Patch 1 restores some test coverage that was lost when the default
> rebase backend was changed.
>
> Patches 2 & 3 fix the return value of do_pick_commit() when an external
> command fails (this is in preparation for patch 8).
>
> Patches 4-7 try and simplify the control flow in pick_one_commit()
> in preparation for patch 8.
>
> Patch 8 changes the return type of do_pick_commit() to an enum.
>
> Patch 9 adds a new member to the enum from patch 8 for commits that
> are dropped when they become empty and uses that to stop them from
> being recorded as rewritten.
I see Phillip Cc'ed everybody who participated in the review for the
previous iterations, which is very much appreciated.
It looks like this is now ready to go? Any further comments?
Thanks.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox