From cb1faa1e51aa1e46ed000b99c96b5252934c2786 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Tue, 24 Mar 2026 09:35:27 -0400 Subject: [PATCH 1/2] gvfs-helper: send X-Session-Id headers In order to assist with tracking user experience between the Git client and the GVFS Protocol servers, start sending the SID for the client Git process over the wire as the X-Session-Id header. Insert this header to all curl requests for each protocol. Signed-off-by: Derrick Stolee --- gvfs-helper.c | 34 ++++++++++++++++++++++++++++++ t/helper/test-gvfs-protocol.c | 13 ++++++++++++ t/t5793-gvfs-helper-integration.sh | 22 +++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/gvfs-helper.c b/gvfs-helper.c index d3903108ad946b..25dd8b84f68d37 100644 --- a/gvfs-helper.c +++ b/gvfs-helper.c @@ -251,6 +251,7 @@ #include "abspath.h" #include "progress.h" #include "trace2.h" +#include "trace2/tr2_sid.h" #include "wrapper.h" #include "packfile.h" #include "date.h" @@ -451,6 +452,34 @@ static void reset_cache_server(void) } } +/* + * Build the X-Session-Id header value based on gvfs.sessionkey config. + * + * If gvfs.sessionkey is set, it specifies which config key contains + * a prefix to prepend to the SID. The format is: : + * + * If gvfs.sessionkey is not set or the referenced key doesn't exist, + * the header value is just the SID. + * + * Returns a newly allocated string that must be freed by the caller. + */ +static char *build_session_id_header(void) +{ + struct strbuf header = STRBUF_INIT; + const char *sid = tr2_sid_get(); + + strbuf_addf(&header, "X-Session-Id: %s", sid); + + return strbuf_detach(&header, NULL); +} + +static void append_session_id_header(struct curl_slist **headers) +{ + char *session_id_header = build_session_id_header(); + *headers = curl_slist_append(*headers, session_id_header); + free(session_id_header); +} + static const char *gh__server_type_label[GH__SERVER_TYPE__NR] = { "(main)", "(cs)" @@ -3296,6 +3325,7 @@ static void do__http_get__simple_endpoint(struct gh__response_status *status, "X-TFS-FedAuthRedirect: Suppress"); params.headers = curl_slist_append(params.headers, "Pragma: no-cache"); + append_session_id_header(¶ms.headers); if (gh__cmd_opts.show_progress) { /* @@ -3365,6 +3395,7 @@ static void do__http_get__gvfs_object(struct gh__response_status *status, "X-TFS-FedAuthRedirect: Suppress"); params.headers = curl_slist_append(params.headers, "Pragma: no-cache"); + append_session_id_header(¶ms.headers); oidcpy(¶ms.loose_oid, oid); @@ -3426,6 +3457,8 @@ static void do__http_post__gvfs_objects(struct gh__response_status *status, "Pragma: no-cache"); params.headers = curl_slist_append(params.headers, "Content-Type: application/json"); + append_session_id_header(¶ms.headers); + /* * If our POST contains more than one object, we want the * server to send us a packfile. We DO NOT want the non-standard @@ -3562,6 +3595,7 @@ static void do__http_get__gvfs_prefetch(struct gh__response_status *status, "Pragma: no-cache"); params.headers = curl_slist_append(params.headers, "Accept: application/x-gvfs-timestamped-packfiles-indexes"); + append_session_id_header(¶ms.headers); if (gh__cmd_opts.show_progress) strbuf_addf(¶ms.progress_base_phase3_msg, diff --git a/t/helper/test-gvfs-protocol.c b/t/helper/test-gvfs-protocol.c index 3800bc583bcb35..7351b24a589806 100644 --- a/t/helper/test-gvfs-protocol.c +++ b/t/helper/test-gvfs-protocol.c @@ -1552,6 +1552,19 @@ static enum worker_result req__read(struct req *req, int fd) */ done: + /* + * Log the X-Session-Id header if present (for testing purposes). + */ + { + struct string_list_item *item; + for_each_string_list_item(item, &req->header_list) { + if (starts_with(item->string, "X-Session-Id:")) { + loginfo("Received header: %s", item->string); + break; + } + } + } + #if 0 /* * This is useful for debugging the request, but very noisy. diff --git a/t/t5793-gvfs-helper-integration.sh b/t/t5793-gvfs-helper-integration.sh index c74ab74dd4b410..2f000bbfd5ee40 100755 --- a/t/t5793-gvfs-helper-integration.sh +++ b/t/t5793-gvfs-helper-integration.sh @@ -163,4 +163,26 @@ test_expect_success 'integration: implicit-get: cache_http_503,with-fallback: di # T2 should be considered contaminated at this point. +################################################################# +# Test X-Session-Id header +# +# The X-Session-Id header should contain the SID (session ID). +################################################################# + +test_expect_success 'integration: X-Session-Id header with and without prefix' ' + test_when_finished "per_test_cleanup" && + start_gvfs_protocol_server && + + git -C "$REPO_T1" gvfs-helper \ + --cache-server=disable \ + --remote=origin \ + get \ + <"$OID_ONE_BLOB_FILE" >OUT.output1 && + + # Verify X-Session-Id contains SID (with process ID marker "-P") + test_grep "X-Session-Id:.*-P" "$SERVER_LOG" >OUT.case1 && + # Verify no slash (no prefix) + test_grep ! "X-Session-Id:.*:" OUT.case1 +' + test_done From f78ee34bce50f8947d3f7836b94f160043524c14 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Tue, 24 Mar 2026 09:42:58 -0400 Subject: [PATCH 2/2] gvfs: add gvfs.sessionKey config In different engineering systems, there may already be pseudonymous identifiers stored in the local Git config. For Office and 1JS, this is present in the 'otel.trace2.id' config value. We'd like to be able to collect server-side telemetry based on these pseudonymous identifiers, so prefxing the X-Session-Id header with this value is helpful. We could create a single 'gvfs.sessionPrefix' config key that stores this value, but then we'd need to duplicate the identifier and risk drift in the value. For now, we create this indirection by saying "what config _key_ should Git use to look up the value to add as a prefix?" Signed-off-by: Derrick Stolee --- Documentation/config/gvfs.adoc | 8 ++++++++ gvfs-helper.c | 22 +++++++++++++++++++++- t/t5793-gvfs-helper-integration.sh | 30 +++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Documentation/config/gvfs.adoc b/Documentation/config/gvfs.adoc index 4d988598425cec..a4a060b570f148 100644 --- a/Documentation/config/gvfs.adoc +++ b/Documentation/config/gvfs.adoc @@ -26,3 +26,11 @@ gvfs.fallback:: If set to `false`, then never fallback to the origin server when the cache server fails to connect. This will alert users to failures with the cache server, but avoid causing throttling on the origin server. + +gvfs.sessionKey:: + If set to a string, then the value is a config key name that will be used + to set a prefix in the `X-Session-Id` header sent to the server for all + GVFS Protocol calls. This allows engineering systems to improve support + across client and server behavior, including any End User Pseodynomous + Identifiers (EUPI) that may be configured. The `X-Session-Id` will + include the SID of the current process in either case. diff --git a/gvfs-helper.c b/gvfs-helper.c index 25dd8b84f68d37..77555bf21e54f5 100644 --- a/gvfs-helper.c +++ b/gvfs-helper.c @@ -467,8 +467,28 @@ static char *build_session_id_header(void) { struct strbuf header = STRBUF_INIT; const char *sid = tr2_sid_get(); + char *session_key = NULL; + char *prefix = NULL; + + /* Read gvfs.sessionkey to see if it points to a config key */ + if (!repo_config_get_string(the_repository, "gvfs.sessionkey", &session_key) && + session_key) { + /* Try to read the config key that session_key points to */ + if (!repo_config_get_string(the_repository, session_key, &prefix) && + prefix) { + /* We have a prefix, format as: X-Session-Id: : */ + strbuf_addf(&header, "X-Session-Id: %s:%s", prefix, sid); + free(prefix); + } else { + /* Config key doesn't exist, use just SID */ + strbuf_addf(&header, "X-Session-Id: %s", sid); + } - strbuf_addf(&header, "X-Session-Id: %s", sid); + free(session_key); + } else { + /* No gvfs.sessionkey configured, use just SID */ + strbuf_addf(&header, "X-Session-Id: %s", sid); + } return strbuf_detach(&header, NULL); } diff --git a/t/t5793-gvfs-helper-integration.sh b/t/t5793-gvfs-helper-integration.sh index 2f000bbfd5ee40..dced714f5b7301 100755 --- a/t/t5793-gvfs-helper-integration.sh +++ b/t/t5793-gvfs-helper-integration.sh @@ -173,6 +173,7 @@ test_expect_success 'integration: X-Session-Id header with and without prefix' ' test_when_finished "per_test_cleanup" && start_gvfs_protocol_server && + # Case 1: No gvfs.sessionkey configured - should send just SID git -C "$REPO_T1" gvfs-helper \ --cache-server=disable \ --remote=origin \ @@ -182,7 +183,34 @@ test_expect_success 'integration: X-Session-Id header with and without prefix' ' # Verify X-Session-Id contains SID (with process ID marker "-P") test_grep "X-Session-Id:.*-P" "$SERVER_LOG" >OUT.case1 && # Verify no slash (no prefix) - test_grep ! "X-Session-Id:.*:" OUT.case1 + test_grep ! "X-Session-Id:.*:" OUT.case1 && + + # Case 2: gvfs.sessionkey points to non-existent config - should send just SID + rm -f OUT.output* OUT.case* && + git -C "$REPO_T1" -c gvfs.sessionkey="test.id" gvfs-helper \ + --cache-server=disable \ + --remote=origin \ + get \ + <"$OID_ONE_BLOB_FILE" >OUT.output2 && + + # Verify X-Session-Id still contains just SID (no prefix) + test_grep "X-Session-Id:.*-P" "$SERVER_LOG" >OUT.case2 && + test_grep ! "X-Session-Id:.*:" OUT.case2 && + + # Case 3: gvfs.sessionkey points to existing config - should send prefix/SID + rm -f OUT.output* OUT.case* && + git -C "$REPO_T1" \ + -c gvfs.sessionkey="test.id" \ + -c test.id="my-trace-12345" \ + gvfs-helper \ + --cache-server=disable \ + --remote=origin \ + get \ + <"$OID_ONE_BLOB_FILE" >OUT.output3 && + + # Verify X-Session-Id contains prefix, slash, and SID + test_grep "X-Session-Id:.*my-trace-12345:" "$SERVER_LOG" >OUT.case3 && + test_grep "X-Session-Id:.*my-trace-12345:.*-P" OUT.case3 ' test_done