{"thread":{"id":"62747","subject":"[PATCH 0/4][Outreachy] Introduce os-version Capability with Configurable Options","startedAt":"2025-01-06T10:37:20Z","lastAt":"2025-02-18T17:14:03Z","messageCount":107,"participants":["Usman Akinyemi","Junio C Hamano","Eric Sunshine","rsbecker@nexbridge.com","Christian Couder"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"509966","messageId":"20250106103713.1452035-1-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":null,"subject":"[PATCH 0/4][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-06T10:30:48Z","receivedAt":"2025-01-06T10:37:20Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"For debugging, statistical analysis, and security purposes, it can\nbe valuable for Git servers to know the operating system the clients\nare using.\n\nFor example:\n- A server noticing that a client is using an old Git version with\nsecurity issues on one platform, like macOS, could verify if the\nuser is indeed running macOS before sending a message to upgrade.\"\n- Similarly, a server identifying a client that could benefit from\nan upgrade (e.g., for performance reasons) could better customize the\nmessage it sends to nudge the client to upgrade.\n\nSo let's add a new 'os-version' capability to the v2 protocol, in the\nsame way as the existing 'agent' capability that lets clients and servers\nexchange the Git version they are running.\n\nBy default this sends similar info as `git bugreport` is already sending,\nwhich uses uname(2). The difference is that it is sanitized in the same\nway as the Git version sent by the 'agent' capability is sanitized\n(by replacing characters having an ascii code less than 32 or more\nthan 127 with '.'). Also, it only sends the result of `uname -s` i.e\njust only the operating system name (e.g \"Linux\").\n\nDue to privacy issues and concerns, let's add the `transfer.advertiseOSVersion`\nconfig option. This boolean option is enabled by default, but allows users to\ndisable this feature completely by setting it to \"false\".\n\nTo provide flexibility and customization, let also add the `osversion.command`\nconfig option. This allows users to specify a custom command whose output will\nbe used as the string exchanged via the \"os-version\" capability. If this option\nis not set, the default behavior exchanges only the operating system name,\nsuch as \"Linux\" or \"Windows\".\n\nPlanned Feature: osversion.format\nWhile the above configurations are already implemented, we will be introducing\nan additional config option, `osversion.format`. This option would allow users\nto fully customize the string sent to the other side using placeholders,\nsimilar to how git for-each-ref uses %() syntax.\n\nFor example:\nFormat: \"OS: %(os_name), Distro: %(distro), Arch: %(arch)\"\nResult: \"OS: Linux, Distro: Fedora, Arch: x86_64\"\n\nWe are wondering if it's worth it for placeholders to use the %()\nsyntax or if they could use another simpler syntax like $OS_NAME or\njust OS_NAME instead of %(os_name).\n\nNote that, due to differences between `uname(1)` (command-line\nutility) and `uname(2)` (system call) outputs on Windows,\n`transfer.advertiseOSVersion` is set to false on Windows during\ntesting. See the message part of patch 3/4 for more details.\n\nMy mentor, Christian Couder, sent a previous patch series about this\nbefore. You can find it here \nhttps://lore.kernel.org/git/20240619125708.3719150-1-christian.couder@gmail.com/\n\nUsman Akinyemi (4):\n  version: refactor redact_non_printables()\n  version: refactor get_uname_info()\n  connect: advertise OS version\n  version: introduce osversion.command config for os-version output\n\n Documentation/config/transfer.txt |  16 ++++\n Documentation/gitprotocol-v2.txt  |  21 +++++\n builtin/bugreport.c               |  13 +--\n connect.c                         |   3 +\n serve.c                           |  14 +++\n t/t5555-http-smart-common.sh      |  41 ++++++++-\n t/t5701-git-serve.sh              |  45 +++++++++-\n t/test-lib-functions.sh           |   8 ++\n version.c                         | 136 ++++++++++++++++++++++++++++--\n version.h                         |  13 +++\n 10 files changed, 291 insertions(+), 19 deletions(-)\n\n-- \n2.47.1\n\n"},{"id":"509967","messageId":"20250106103713.1452035-2-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250106103713.1452035-1-usmanakinyemi202@gmail.com","subject":"[PATCH 1/4] version: refactor redact_non_printables()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-06T10:30:49Z","receivedAt":"2025-01-06T10:37:24Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"The git_user_agent_sanitized() function performs some sanitizing to\navoid special characters being sent over the line and possibly messing\nup with the protocol or with the parsing on the other side.\n\nLet's extract this sanitizing into a new redact_non_printables() function,\nas we will want to reuse it in a following patch.\n\nFor now the new redact_non_printables() function is still static as\nit's only needed locally.\n\nWhile at it, let's also make a few small improvements:\n  - use 'size_t' for 'i' instead of 'int',\n  - move the declaration of 'i' inside the 'for ( ... )',\n  - use strbuf_detach() to explicitly detach the string contained by\n    the 'buf' strbuf.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n version.c | 22 ++++++++++++++++------\n 1 file changed, 16 insertions(+), 6 deletions(-)\n\ndiff --git a/version.c b/version.c\nindex 4d763ab48d..78f025c808 100644\n--- a/version.c\n+++ b/version.c\n@@ -6,6 +6,20 @@\n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n \n+/*\n+ * Trim and replace each character with ascii code below 32 or above\n+ * 127 (included) using a dot '.' character.\n+ * TODO: ensure consecutive non-printable characters are only replaced once\n+*/\n+static void redact_non_printables(struct strbuf *buf)\n+{\n+\tstrbuf_trim(buf);\n+\tfor (size_t i = 0; i < buf->len; i++) {\n+\t\tif (buf->buf[i] <= 32 || buf->buf[i] >= 127)\n+\t\t\tbuf->buf[i] = '.';\n+\t}\n+}\n+\n const char *git_user_agent(void)\n {\n \tstatic const char *agent = NULL;\n@@ -27,12 +41,8 @@ const char *git_user_agent_sanitized(void)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&buf, git_user_agent());\n-\t\tstrbuf_trim(&buf);\n-\t\tfor (size_t i = 0; i < buf.len; i++) {\n-\t\t\tif (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n-\t\t\t\tbuf.buf[i] = '.';\n-\t\t}\n-\t\tagent = buf.buf;\n+\t\tredact_non_printables(&buf);\n+\t\tagent = strbuf_detach(&buf, NULL);\n \t}\n \n \treturn agent;\n-- \n2.47.1\n\n"},{"id":"509968","messageId":"20250106103713.1452035-3-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250106103713.1452035-1-usmanakinyemi202@gmail.com","subject":"[PATCH 2/4] version: refactor get_uname_info()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-06T10:30:50Z","receivedAt":"2025-01-06T10:37:27Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Some code from \"builtin/bugreport.c\" uses uname(2) to get system\ninformation.\n\nLet's refactor this code into a new get_uname_info() function, so\nthat we can reuse it in a following commit.\n\nWe may need to refactor this function in the future if an\n`osVersion.format` config option is added, but for now we only\nneed it to accept a \"full\" flag that makes it switch between providing\nfull OS information and providing only the OS name. The mode\nproviding only the OS name is needed in a following commit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c | 13 ++-----------\n version.c           | 23 +++++++++++++++++++++++\n version.h           |  7 +++++++\n 3 files changed, 32 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 7c2df035c9..e3288a86c8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -12,10 +12,10 @@\n #include \"diagnose.h\"\n #include \"object-file.h\"\n #include \"setup.h\"\n+#include \"version.h\"\n \n static void get_system_info(struct strbuf *sys_info)\n {\n-\tstruct utsname uname_info;\n \tchar *shell = NULL;\n \n \t/* get git version from native cmd */\n@@ -24,16 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tif (uname(&uname_info))\n-\t\tstrbuf_addf(sys_info, _(\"uname() failed with error '%s' (%d)\\n\"),\n-\t\t\t    strerror(errno),\n-\t\t\t    errno);\n-\telse\n-\t\tstrbuf_addf(sys_info, \"%s %s %s %s\\n\",\n-\t\t\t    uname_info.sysname,\n-\t\t\t    uname_info.release,\n-\t\t\t    uname_info.version,\n-\t\t\t    uname_info.machine);\n+\tget_uname_info(sys_info, 1);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 78f025c808..44ffc4dd57 100644\n--- a/version.c\n+++ b/version.c\n@@ -2,6 +2,7 @@\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n+#include \"gettext.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -47,3 +48,25 @@ const char *git_user_agent_sanitized(void)\n \n \treturn agent;\n }\n+\n+int get_uname_info(struct strbuf *buf, unsigned int full)\n+{\n+\tstruct utsname uname_info;\n+\n+\tif (uname(&uname_info)) {\n+\t\tstrbuf_addf(buf, _(\"uname() failed with error '%s' (%d)\\n\"),\n+\t\t\t    strerror(errno),\n+\t\t\t    errno);\n+\t\treturn -1;\n+\t}\n+\n+\tif (full)\n+\t\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t\t    uname_info.sysname,\n+\t\t\t    uname_info.release,\n+\t\t\t    uname_info.version,\n+\t\t\t    uname_info.machine);\n+\telse\n+\t\tstrbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n+\treturn 0;\n+}\ndiff --git a/version.h b/version.h\nindex 7c62e80577..5eb586c0bd 100644\n--- a/version.h\n+++ b/version.h\n@@ -7,4 +7,11 @@ extern const char git_built_from_commit_string[];\n const char *git_user_agent(void);\n const char *git_user_agent_sanitized(void);\n \n+/*\n+  Try to get information about the system using uname(2).\n+  Return -1 and put an error message into 'buf' in case of uname()\n+  error. Return 0 and put uname info into 'buf' otherwise.\n+*/\n+int get_uname_info(struct strbuf *buf, unsigned int full);\n+\n #endif /* VERSION_H */\n-- \n2.47.1\n\n"},{"id":"509969","messageId":"20250106103713.1452035-4-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250106103713.1452035-1-usmanakinyemi202@gmail.com","subject":"[PATCH 3/4] connect: advertise OS version","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-06T10:30:51Z","receivedAt":"2025-01-06T10:37:31Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"As some issues that can happen with a Git client can be operating system\nspecific, it can be useful for a server to know which OS a client is\nusing. In the same way it can be useful for a client to know which OS\na server is using.\n\nLet's introduce a new protocol (`os-version`) allowing Git clients and\nservers to exchange operating system information. The protocol is\ncontrolled by the new `transfer.advertiseOSVersion` config option.\n\nAdd the `transfer.advertiseOSVersion` config option to address\nprivacy concerns issue. It defaults to `true` and can be changed to\n`false`. When enabled, this option makes clients and servers send each\nother the OS name (e.g., \"Linux\" or \"Windows\"). The information is\nretrieved using the 'sysname' field of the `uname(2)` system call.\n\nHowever, there are differences between `uname(1)` (command-line utility)\nand `uname(2)` (system call) outputs on Windows. These discrepancies\ncomplicate testing on Windows platforms. For example:\n  - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n  .2024-02-14.20:17.UTC.x86_64\n  - `uname(2)` output: Windows.10.0.20348\n\nUntil a good way to test the feature on Windows is found, the\ntransfer.advertiseOSVersion is set to false on Windows during testing.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n Documentation/config/transfer.txt |  7 ++++++\n Documentation/gitprotocol-v2.txt  | 20 +++++++++++++++\n connect.c                         |  3 +++\n serve.c                           | 14 +++++++++++\n t/t5555-http-smart-common.sh      | 12 ++++++++-\n t/t5701-git-serve.sh              | 12 ++++++++-\n t/test-lib-functions.sh           |  8 ++++++\n version.c                         | 42 +++++++++++++++++++++++++++++++\n version.h                         |  6 +++++\n 9 files changed, 122 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt\nindex f1ce50f4a6..e2d95d1ccd 100644\n--- a/Documentation/config/transfer.txt\n+++ b/Documentation/config/transfer.txt\n@@ -125,3 +125,10 @@ transfer.bundleURI::\n transfer.advertiseObjectInfo::\n \tWhen `true`, the `object-info` capability is advertised by\n \tservers. Defaults to false.\n+\n+transfer.advertiseOSVersion::\n+\tWhen `true`, the `os-version` capability is advertised by clients and\n+\tservers. It makes clients and servers send to each other a string\n+\trepresenting the operating system name, like \"Linux\" or \"Windows\".\n+\tThis string is retrieved from the 'sysname' field of the struct returned\n+\tby the uname(2) system call. Defaults to true.\ndiff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\nindex 1652fef3ae..c28262c60b 100644\n--- a/Documentation/gitprotocol-v2.txt\n+++ b/Documentation/gitprotocol-v2.txt\n@@ -190,6 +190,26 @@ printable ASCII characters except space (i.e., the byte range 32 < x <\n and debugging purposes, and MUST NOT be used to programmatically assume\n the presence or absence of particular features.\n \n+os-version\n+~~~~~~~~~~\n+\n+In the same way as the `agent` capability above, the server can\n+advertise the `os-version` capability with a value `X` (in the form\n+`os-version=X`) to notify the client that the server is running an\n+operating system that can be identified by `X`. The client may\n+optionally send its own `os-version` string by including the\n+`os-version` capability with a value `Y` (in the form `os-version=Y`)\n+in its request to the server (but it MUST NOT do so if the server did\n+not advertise the os-version capability). The `X` and `Y` strings may\n+contain any printable ASCII characters except space (i.e., the byte\n+range 32 < x < 127), and are typically made from the result of\n+`uname -s`(OS name e.g Linux). The os-version capability can be disabled\n+entirely by setting the `transfer.advertiseOSVersion` config option\n+to `false`. The `os-version` strings are purely informative for\n+statistics and debugging purposes, and MUST NOT be used to\n+programmatically assume the presence or absence of particular\n+features.\n+\n ls-refs\n ~~~~~~~\n \ndiff --git a/connect.c b/connect.c\nindex 10fad43e98..6d5792b63c 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -492,6 +492,9 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_write_fmt(fd_out, \"agent=%s\", git_user_agent_sanitized());\n \n+\tif (server_supports_v2(\"os-version\") && advertise_os_version(the_repository))\n+\t\tpacket_write_fmt(fd_out, \"os-version=%s\", os_version_sanitized());\n+\n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n \t\tint hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo == GIT_HASH_UNKNOWN)\ndiff --git a/serve.c b/serve.c\nindex c8694e3751..5b0d54ae9a 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -31,6 +31,16 @@ static int agent_advertise(struct repository *r UNUSED,\n \treturn 1;\n }\n \n+static int os_version_advertise(struct repository *r,\n+\t\t\t   struct strbuf *value)\n+{\n+\tif (!advertise_os_version(r))\n+\t\treturn 0;\n+\tif (value)\n+\t\tstrbuf_addstr(value, os_version_sanitized());\n+\treturn 1;\n+}\n+\n static int object_format_advertise(struct repository *r,\n \t\t\t\t   struct strbuf *value)\n {\n@@ -123,6 +133,10 @@ static struct protocol_capability capabilities[] = {\n \t\t.name = \"agent\",\n \t\t.advertise = agent_advertise,\n \t},\n+\t{\n+\t\t.name = \"os-version\",\n+\t\t.advertise = os_version_advertise,\n+\t},\n \t{\n \t\t.name = \"ls-refs\",\n \t\t.advertise = ls_refs_advertise,\ndiff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh\nindex e47ea1ad10..f9e2a66cba 100755\n--- a/t/t5555-http-smart-common.sh\n+++ b/t/t5555-http-smart-common.sh\n@@ -123,9 +123,19 @@ test_expect_success 'git receive-pack --advertise-refs: v1' '\n '\n \n test_expect_success 'git upload-pack --advertise-refs: v2' '\n+\tprintf \"agent=FAKE\" >agent_and_os_name &&\n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\t# We do not use test_config here so that any tests below can reuse\n+\t\t# the \"expect\" file from this test\n+\t\tgit config transfer.advertiseOSVersion false\n+\telse\n+\t\tprintf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_os_name\n+\tfi &&\n+\n \tcat >expect <<-EOF &&\n \tversion 2\n-\tagent=FAKE\n+\t$(cat agent_and_os_name)\n \tls-refs=unborn\n \tfetch=shallow wait-for-done\n \tserver-option\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex de904c1655..f4668b7acd 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -8,13 +8,23 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n . ./test-lib.sh\n \n test_expect_success 'test capability advertisement' '\n+\tprintf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_os_name &&\n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\t# We do not use test_config here so that tests below will be able to reuse\n+\t\t# the expect.base and expect.trailer files\n+\t\tgit config transfer.advertiseOSVersion false\n+\telse\n+\t\tprintf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_os_name\n+\tfi &&\n+\n \ttest_oid_cache <<-EOF &&\n \twrong_algo sha1:sha256\n \twrong_algo sha256:sha1\n \tEOF\n \tcat >expect.base <<-EOF &&\n \tversion 2\n-\tagent=git/$(git version | cut -d\" \" -f3)\n+\t$(cat agent_and_os_name)\n \tls-refs=unborn\n \tfetch=shallow wait-for-done\n \tserver-option\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 78e054ab50..447c698d74 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -2007,3 +2007,11 @@ test_trailing_hash () {\n \t\ttest-tool hexdump |\n \t\tsed \"s/ //g\"\n }\n+\n+# Trim and replace each character with ascii code below 32 or above\n+# 127 (included) using a dot '.' character.\n+# Octal intervals \\001-\\040 and \\177-\\377\n+# corresponds to decimal intervals 1-32 and 127-255\n+test_redact_non_printables () {\n+    tr -d \"\\n\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n+}\ndiff --git a/version.c b/version.c\nindex 44ffc4dd57..8242baf41c 100644\n--- a/version.c\n+++ b/version.c\n@@ -3,6 +3,7 @@\n #include \"version-def.h\"\n #include \"strbuf.h\"\n #include \"gettext.h\"\n+#include \"config.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -70,3 +71,44 @@ int get_uname_info(struct strbuf *buf, unsigned int full)\n \t\tstrbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n \treturn 0;\n }\n+\n+const char *os_version(void)\n+{\n+\tstatic const char *os = NULL;\n+\n+\tif (!os) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tget_uname_info(&buf, 0);\n+\t\tos = strbuf_detach(&buf, NULL);\n+\t}\n+\n+\treturn os;\n+}\n+\n+const char *os_version_sanitized(void)\n+{\n+\tstatic const char *os_sanitized = NULL;\n+\n+\tif (!os_sanitized) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tstrbuf_addstr(&buf, os_version());\n+\t\tredact_non_printables(&buf);\n+\t\tos_sanitized = strbuf_detach(&buf, NULL);\n+\t}\n+\n+\treturn os_sanitized;\n+}\n+\n+int advertise_os_version(struct repository *r)\n+{\n+\tstatic int transfer_advertise_os_version = -1;\n+\n+\tif (transfer_advertise_os_version == -1) {\n+\t\trepo_config_get_bool(r, \"transfer.advertiseosversion\", &transfer_advertise_os_version);\n+\t\t/* enabled by default */\n+\t\ttransfer_advertise_os_version = !!transfer_advertise_os_version;\n+\t}\n+\treturn transfer_advertise_os_version;\n+}\ndiff --git a/version.h b/version.h\nindex 5eb586c0bd..8167ce956a 100644\n--- a/version.h\n+++ b/version.h\n@@ -1,6 +1,8 @@\n #ifndef VERSION_H\n #define VERSION_H\n \n+struct repository;\n+\n extern const char git_version_string[];\n extern const char git_built_from_commit_string[];\n \n@@ -14,4 +16,8 @@ const char *git_user_agent_sanitized(void);\n */\n int get_uname_info(struct strbuf *buf, unsigned int full);\n \n+const char *os_version(void);\n+const char *os_version_sanitized(void);\n+int advertise_os_version(struct repository *r);\n+\n #endif /* VERSION_H */\n-- \n2.47.1\n\n"},{"id":"509970","messageId":"20250106103713.1452035-5-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250106103713.1452035-1-usmanakinyemi202@gmail.com","subject":"[PATCH 4/4] version: introduce osversion.command config for os-version output","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-06T10:30:52Z","receivedAt":"2025-01-06T10:37:35Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently by default, the new `os-version` capability only exchange the\noperating system name between servers and clients i.e \"Linux\" or\n\"Windows\".\n\nLet's introduce a new configuration option, `osversion.command`, to handle\nthe string exchange between servers and clients. This option allows\ncustomization of the exchanged string by leveraging the output of the\nspecified command. If this is not set, the `os-version` capability\nexchange just the operating system name.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n Documentation/config/transfer.txt | 11 ++++++-\n Documentation/gitprotocol-v2.txt  | 13 ++++----\n t/t5555-http-smart-common.sh      | 29 ++++++++++++++++++\n t/t5701-git-serve.sh              | 33 ++++++++++++++++++++\n version.c                         | 51 ++++++++++++++++++++++++++++++-\n 5 files changed, 129 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt\nindex e2d95d1ccd..28a08f21fc 100644\n--- a/Documentation/config/transfer.txt\n+++ b/Documentation/config/transfer.txt\n@@ -131,4 +131,13 @@ transfer.advertiseOSVersion::\n \tservers. It makes clients and servers send to each other a string\n \trepresenting the operating system name, like \"Linux\" or \"Windows\".\n \tThis string is retrieved from the 'sysname' field of the struct returned\n-\tby the uname(2) system call. Defaults to true.\n+\tby the uname(2) system call. If the `osVersion.command` is set, the\n+\toutput of the command specified will be the string exchanged by the clients\n+\tand the servers. Defaults to true.\n+\n+osVersion.command::\n+\tIf this variable is set, the specified command will be run and the output\n+\twill be used as the value `X` for `os-version` capability (in the form\n+\t`os-version=X`). `osVersion.command` is only used if `transfer.advertiseOSVersion`\n+\tis true. Refer to the linkgit:git-config[1] documentation to learn more about\n+\t`transfer.advertiseOSVersion` config option.\ndiff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\nindex c28262c60b..53621c0bce 100644\n--- a/Documentation/gitprotocol-v2.txt\n+++ b/Documentation/gitprotocol-v2.txt\n@@ -203,12 +203,13 @@ in its request to the server (but it MUST NOT do so if the server did\n not advertise the os-version capability). The `X` and `Y` strings may\n contain any printable ASCII characters except space (i.e., the byte\n range 32 < x < 127), and are typically made from the result of\n-`uname -s`(OS name e.g Linux). The os-version capability can be disabled\n-entirely by setting the `transfer.advertiseOSVersion` config option\n-to `false`. The `os-version` strings are purely informative for\n-statistics and debugging purposes, and MUST NOT be used to\n-programmatically assume the presence or absence of particular\n-features.\n+`uname -s`(OS name e.g Linux).  If the `osVersion.command` is set,\n+the `X` and `Y` are made from the ouput of the command specified.\n+The os-version capability can be disabled entirely by setting the\n+`transfer.advertiseOSVersion` config option to `false`. The `os-version`\n+strings are purely informative for statistics and debugging purposes, and\n+MUST NOT be used to programmatically assume the presence or absence of\n+particular features.\n \n ls-refs\n ~~~~~~~\ndiff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh\nindex f9e2a66cba..8d5844eaf2 100755\n--- a/t/t5555-http-smart-common.sh\n+++ b/t/t5555-http-smart-common.sh\n@@ -152,6 +152,35 @@ test_expect_success 'git upload-pack --advertise-refs: v2' '\n \ttest_cmp actual expect\n '\n \n+test_expect_success 'git upload-pack --advertise-refs: v2 with osVersion.command config set' '\n+\t# test_config is used here as we are not reusing any file output from here\n+\ttest_config osVersion.command \"uname -srvm\" &&\n+\tprintf \"agent=FAKE\" >agent_and_long_os_name &&\n+\n+\tif test_have_prereq !WINDOWS\n+\tthen\n+\t\tprintf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_os_name\n+\tfi &&\n+\n+\tcat >expect <<-EOF &&\n+\tversion 2\n+\t$(cat agent_and_long_os_name)\n+\tls-refs=unborn\n+\tfetch=shallow wait-for-done\n+\tserver-option\n+\tobject-format=$(test_oid algo)\n+\t0000\n+\tEOF\n+\n+\tGIT_PROTOCOL=version=2 \\\n+\tGIT_USER_AGENT=FAKE \\\n+\tgit upload-pack --advertise-refs . >out 2>err &&\n+\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_must_be_empty err &&\n+\ttest_cmp actual expect\n+'\n+\n test_expect_success 'git receive-pack --advertise-refs: v2' '\n \t# There is no v2 yet for receive-pack, implicit v0\n \tcat >expect <<-EOF &&\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex f4668b7acd..51d99cd62c 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -41,6 +41,39 @@ test_expect_success 'test capability advertisement' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'test capability advertisement with osVersion.command config set' '\n+\t# test_config is used here as we are not reusing any file output from here\n+\ttest_config osVersion.command \"uname -srvm\" &&\n+\tprintf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_long_os_name &&\n+\n+\tif test_have_prereq !WINDOWS\n+\tthen\n+\t\tprintf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_os_name\n+\tfi &&\n+\n+\ttest_oid_cache <<-EOF &&\n+\twrong_algo sha1:sha256\n+\twrong_algo sha256:sha1\n+\tEOF\n+\tcat >expect.base_long <<-EOF &&\n+\tversion 2\n+\t$(cat agent_and_long_os_name)\n+\tls-refs=unborn\n+\tfetch=shallow wait-for-done\n+\tserver-option\n+\tobject-format=$(test_oid algo)\n+\tEOF\n+\tcat >expect.trailer_long <<-EOF &&\n+\t0000\n+\tEOF\n+\tcat expect.base_long expect.trailer_long >expect &&\n+\n+\tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n+\t\t--advertise-capabilities >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'stateless-rpc flag does not list capabilities' '\n \t# Empty request\n \ttest-tool pkt-line pack >in <<-EOF &&\ndiff --git a/version.c b/version.c\nindex 8242baf41c..b446232898 100644\n--- a/version.c\n+++ b/version.c\n@@ -1,9 +1,13 @@\n+#define USE_THE_REPOSITORY_VARIABLE\n+\n #include \"git-compat-util.h\"\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n #include \"gettext.h\"\n #include \"config.h\"\n+#include \"run-command.h\"\n+#include \"alias.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -72,6 +76,50 @@ int get_uname_info(struct strbuf *buf, unsigned int full)\n \treturn 0;\n }\n \n+/*\n+ * Return -1 if unable to retrieve the osversion.command config or\n+ * if the command is malformed; otherwise, return 0 if successful.\n+ */\n+static int fill_os_version_command(struct child_process *cmd)\n+{\n+\tconst char *os_version_command;\n+\tconst char **argv;\n+\tchar *os_version_copy;\n+\tint n;\n+\n+\tif (git_config_get_string_tmp(\"osversion.command\", &os_version_command))\n+\t\treturn -1;\n+\n+\tos_version_copy = xstrdup(os_version_command);\n+\tn = split_cmdline(os_version_copy, &argv);\n+\n+\tif (n < 0) {\n+\t\twarning(_(\"malformed osVersion.command config option: %s\"),\n+\t\t\t_(split_cmdline_strerror(n)));\n+\t\tfree(os_version_copy);\n+\t\treturn -1;\n+\t}\n+\n+\tfor (int i = 0; i < n; i++)\n+\t\tstrvec_push(&cmd->args, argv[i]);\n+\tfree(os_version_copy);\n+\tfree(argv);\n+\n+\treturn 0;\n+}\n+\n+static int capture_os_version(struct strbuf *buf)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\n+\tif (fill_os_version_command(&cmd))\n+\t\treturn -1;\n+\tif (capture_command(&cmd, buf, 0))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n const char *os_version(void)\n {\n \tstatic const char *os = NULL;\n@@ -79,7 +127,8 @@ const char *os_version(void)\n \tif (!os) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n-\t\tget_uname_info(&buf, 0);\n+\t\tif (capture_os_version(&buf))\n+\t\t\tget_uname_info(&buf, 0);\n \t\tos = strbuf_detach(&buf, NULL);\n \t}\n \n-- \n2.47.1\n\n"},{"id":"510000","messageId":"xmqqv7usylll.fsf@gitster.g","threadId":"62747","inReplyTo":"20250106103713.1452035-3-usmanakinyemi202@gmail.com","subject":"Re: [PATCH 2/4] version: refactor get_uname_info()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-06T16:04:54Z","receivedAt":"2025-01-06T16:04:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Some code from \"builtin/bugreport.c\" uses uname(2) to get system\n> information.\n>\n> Let's refactor this code into a new get_uname_info() function, so\n> that we can reuse it in a following commit.\n\nThis does two things: refactor and enhancement.  Shouldn't it do\npure refactoring in a single patch, with a follow-up patch that\nextends it to allow the caller to hide the system details?\n\n"},{"id":"510001","messageId":"xmqqfrlvzzcd.fsf@gitster.g","threadId":"62747","inReplyTo":"20250106103713.1452035-4-usmanakinyemi202@gmail.com","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-06T16:22:42Z","receivedAt":"2025-01-06T16:22:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> +\n> +transfer.advertiseOSVersion::\n> +\tWhen `true`, the `os-version` capability is advertised by clients and\n> +\tservers. It makes clients and servers send to each other a string\n> +\trepresenting the operating system name, like \"Linux\" or \"Windows\".\n> +\tThis string is retrieved from the 'sysname' field of the struct returned\n> +\tby the uname(2) system call. Defaults to true.\n\nShouldn't `sysname` be typeset as a literal, just like `true` and\n`os-version`?\n\n> +os-version\n> +~~~~~~~~~~\n> +\n> +In the same way as the `agent` capability above, the server can\n> +advertise the `os-version` capability with a value `X` (in the form\n> +`os-version=X`) to notify the client that the server is running an\n> +operating system that can be identified by `X`. The client may\n\nHmph.  I am not sure what's the value of mentioning 'X' here.  To me\n\n    ... can advertise the `os-version` capability to notify the kind\n    of operating system it is running on.\n\nconveys the same thing with much fewer bytes.\n\n> +optionally send its own `os-version` string by including the\n> +`os-version` capability with a value `Y` (in the form `os-version=Y`)\n> +in its request to the server (but it MUST NOT do so if the server did\n> +not advertise the os-version capability). The `X` and `Y` strings may\n> +contain any printable ASCII characters except space (i.e., the byte\n\nThis is misleading.  ASCII printable characters range from 33 to 126\n(inclusive), but by saying \"except space\", the readers are led to\nbelieve that the author of this documentation thinks ASCII 32 is\nprintable, too.\n\nAbout 'X' and 'Y', we can just say \"the value of this capability may\nconsist of ASCII printable characters (from 33 to 126 inclusive)\" or\nsomething.\n\nIs there a need for a registry of canonical os-version strings?  One\nreason why you would want this user-settable (as opposed to being\nderived from \"uname -s\") is that a system that is presumably the\nsame in end-user perception can call itself in different names (your\nWindows/MINGW64 example) and having the users set it to a string\nchosen from a small repertoire, the other end would be able to\nidentify them more easily.  I do not think it is a necessarily a\ngood idea to limit what value the users can set to this\nconfiguration variable, but at least with a published guideline on\ncalling various types of systems (and an explanation on the reason\nwhy we publish such a guideline), users would make an informed\ndecision when picking what string to send.\n\n> +# Trim and replace each character with ascii code below 32 or above\n> +# 127 (included) using a dot '.' character.\n> +# Octal intervals \\001-\\040 and \\177-\\377\n> +# corresponds to decimal intervals 1-32 and 127-255\n> +test_redact_non_printables () {\n> +    tr -d \"\\n\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n> +}\n\nJust being curious.  Do we need to worry about carriage-returns not\njust line-feeds, and if not why?\n\nThanks.\n"},{"id":"510027","messageId":"CAPig+cQz+2Q7Z=uMYvKNrK2MACQC130u5iiMcsUPjNTifeWSbw@mail.gmail.com","threadId":"62747","inReplyTo":"20250106103713.1452035-2-usmanakinyemi202@gmail.com","subject":"Re: [PATCH 1/4] version: refactor redact_non_printables()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-01-06T22:35:11Z","receivedAt":"2025-01-06T22:35:22Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Jan 6, 2025 at 5:37 AM Usman Akinyemi\n<usmanakinyemi202@gmail.com> wrote:\n> The git_user_agent_sanitized() function performs some sanitizing to\n> avoid special characters being sent over the line and possibly messing\n> up with the protocol or with the parsing on the other side.\n>\n> Let's extract this sanitizing into a new redact_non_printables() function,\n> as we will want to reuse it in a following patch.\n>\n> For now the new redact_non_printables() function is still static as\n> it's only needed locally.\n>\n> While at it, let's also make a few small improvements:\n>   - use 'size_t' for 'i' instead of 'int',\n>   - move the declaration of 'i' inside the 'for ( ... )',\n\nRegarding the above two items...\n\n>   - use strbuf_detach() to explicitly detach the string contained by\n>     the 'buf' strbuf.\n>\n> Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> ---\n> diff --git a/version.c b/version.c\n> @@ -6,6 +6,20 @@\n> +static void redact_non_printables(struct strbuf *buf)\n> +{\n> +       strbuf_trim(buf);\n> +       for (size_t i = 0; i < buf->len; i++) {\n> +               if (buf->buf[i] <= 32 || buf->buf[i] >= 127)\n> +                       buf->buf[i] = '.';\n> +       }\n> +}\n> @@ -27,12 +41,8 @@ const char *git_user_agent_sanitized(void)\n>                 strbuf_addstr(&buf, git_user_agent());\n> -               strbuf_trim(&buf);\n> -               for (size_t i = 0; i < buf.len; i++) {\n\n... the original code appears to have already been using `size_t` and\ndeclaring the loop variable inside the `for` statement, despite what\nthe commit message says. So, is the commit message out of date? Or are\nthe patches out of order? Or something else?\n\n> -                       if (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n> -                               buf.buf[i] = '.';\n> -               }\n> -               agent = buf.buf;\n> +               redact_non_printables(&buf);\n> +               agent = strbuf_detach(&buf, NULL);\n"},{"id":"510032","messageId":"CAPig+cQJM1TOad=buG0h2-3OnxEH6SUBz23y+wYq9GeUzhMwaA@mail.gmail.com","threadId":"62747","inReplyTo":"20250106103713.1452035-4-usmanakinyemi202@gmail.com","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-01-06T23:17:16Z","receivedAt":"2025-01-06T23:17:27Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Jan 6, 2025 at 5:37 AM Usman Akinyemi\n<usmanakinyemi202@gmail.com> wrote:\n> As some issues that can happen with a Git client can be operating system\n> specific, it can be useful for a server to know which OS a client is\n> using. In the same way it can be useful for a client to know which OS\n> a server is using.\n>\n> Let's introduce a new protocol (`os-version`) allowing Git clients and\n> servers to exchange operating system information. The protocol is\n> controlled by the new `transfer.advertiseOSVersion` config option.\n>\n> Add the `transfer.advertiseOSVersion` config option to address\n> privacy concerns issue. It defaults to `true` and can be changed to\n> `false`. When enabled, this option makes clients and servers send each\n> other the OS name (e.g., \"Linux\" or \"Windows\"). The information is\n> retrieved using the 'sysname' field of the `uname(2)` system call.\n>\n> However, there are differences between `uname(1)` (command-line utility)\n> and `uname(2)` (system call) outputs on Windows. These discrepancies\n> complicate testing on Windows platforms. For example:\n>   - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n>   .2024-02-14.20:17.UTC.x86_64\n>   - `uname(2)` output: Windows.10.0.20348\n>\n> Until a good way to test the feature on Windows is found, the\n> transfer.advertiseOSVersion is set to false on Windows during testing.\n\nThis is because the uname(2) you mention above is not actually\nsystem-supplied but is instead faked up Git itself for the Git for\nWindows port. See git/compat/mingw.c:uname().\n\nThe typical way to work around this sort of issue is to ensure that\nyou check Git against Git itself instead of checking Git against\n\"system\". To do so, you would implement a new \"test-util\" command, say\n`test-util uname`, in git/t/helpers/test-uname.c which internally\ncalls the same uname() function that other parts of Git call. Doing so\nensures consistency of output.\n\nWhether or not it makes sense to go through that extra work for this\nparticular case is a different question.\n\n> Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> ---\n> diff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh\n> @@ -123,9 +123,19 @@ test_expect_success 'git receive-pack --advertise-refs: v1' '\n>  test_expect_success 'git upload-pack --advertise-refs: v2' '\n> +       printf \"agent=FAKE\" >agent_and_os_name &&\n> +       if test_have_prereq WINDOWS\n> +       then\n> +               # We do not use test_config here so that any tests below can reuse\n> +               # the \"expect\" file from this test\n> +               git config transfer.advertiseOSVersion false\n\nShould this have a comment explaining why you're disabling\ntransfer.advertiseOSVersion, in particular that you found uname() on\nWindows unreliable, thus need to disable the check for this case?\n\nThe comment you did compose exposes a fragility of the tests: in\nparticular that subsequent tests rely upon a side-effect of this test.\nThe fact that you had to include a special comment explaining the\nproblem argues for a cleaner solution, such as splitting out part of\nthis code into a separate test which comes before this one:\nspecifically, a \"setup\"-type test which creates the \"expect\" file\nwhich gets reused by multiple tests.\n\n> +       else\n> +               printf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_os_name\n> +       fi &&\n> diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n> @@ -8,13 +8,23 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n>  test_expect_success 'test capability advertisement' '\n> +       printf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_os_name &&\n> +       if test_have_prereq WINDOWS\n> +       then\n> +               # We do not use test_config here so that tests below will be able to reuse\n> +               # the expect.base and expect.trailer files\n> +               git config transfer.advertiseOSVersion false\n\nDitto.\n\n> +       else\n> +               printf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_os_name\n> +       fi &&\n"},{"id":"510155","messageId":"CAPSxiM9krVgyXwymQSg1Y8L5qsb_xpo_FGskFcmWWBC2_Dvs0Q@mail.gmail.com","threadId":"62747","inReplyTo":"CAPig+cQz+2Q7Z=uMYvKNrK2MACQC130u5iiMcsUPjNTifeWSbw@mail.gmail.com","subject":"Re: [PATCH 1/4] version: refactor redact_non_printables()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-08T12:58:47Z","receivedAt":"2025-01-08T12:59:00Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Tue, Jan 7, 2025 at 4:05 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> On Mon, Jan 6, 2025 at 5:37 AM Usman Akinyemi\n> <usmanakinyemi202@gmail.com> wrote:\n> > The git_user_agent_sanitized() function performs some sanitizing to\n> > avoid special characters being sent over the line and possibly messing\n> > up with the protocol or with the parsing on the other side.\n> >\n> > Let's extract this sanitizing into a new redact_non_printables() function,\n> > as we will want to reuse it in a following patch.\n> >\n> > For now the new redact_non_printables() function is still static as\n> > it's only needed locally.\n> >\n> > While at it, let's also make a few small improvements:\n> >   - use 'size_t' for 'i' instead of 'int',\n> >   - move the declaration of 'i' inside the 'for ( ... )',\n>\n> Regarding the above two items...\n>\n> >   - use strbuf_detach() to explicitly detach the string contained by\n> >     the 'buf' strbuf.\n> >\n> > Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> > Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> > ---\n> > diff --git a/version.c b/version.c\n> > @@ -6,6 +6,20 @@\n> > +static void redact_non_printables(struct strbuf *buf)\n> > +{\n> > +       strbuf_trim(buf);\n> > +       for (size_t i = 0; i < buf->len; i++) {\n> > +               if (buf->buf[i] <= 32 || buf->buf[i] >= 127)\n> > +                       buf->buf[i] = '.';\n> > +       }\n> > +}\n> > @@ -27,12 +41,8 @@ const char *git_user_agent_sanitized(void)\n> >                 strbuf_addstr(&buf, git_user_agent());\n> > -               strbuf_trim(&buf);\n> > -               for (size_t i = 0; i < buf.len; i++) {\n>\n> ... the original code appears to have already been using `size_t` and\n> declaring the loop variable inside the `for` statement, despite what\n> the commit message says. So, is the commit message out of date? Or are\n> the patches out of order? Or something else?\nI just investigated what happened. Another commit already added it and\nI did a rebase on top of the \"master\".\nI did not notice it at all. The commit message is out of date.\n\nI will update it in the next iteration.\nThank you very much.\nUsman.\n>\n> > -                       if (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n> > -                               buf.buf[i] = '.';\n> > -               }\n> > -               agent = buf.buf;\n> > +               redact_non_printables(&buf);\n> > +               agent = strbuf_detach(&buf, NULL);\n"},{"id":"510156","messageId":"CAPSxiM_0h7OyQO-Of8YhcOt4KbtuoKXe111ZCvsLf5y+OgCHaw@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqfrlvzzcd.fsf@gitster.g","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-08T13:06:28Z","receivedAt":"2025-01-08T13:06:40Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Mon, Jan 6, 2025 at 9:52 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\nHi Junio,\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > +\n> > +transfer.advertiseOSVersion::\n> > +     When `true`, the `os-version` capability is advertised by clients and\n> > +     servers. It makes clients and servers send to each other a string\n> > +     representing the operating system name, like \"Linux\" or \"Windows\".\n> > +     This string is retrieved from the 'sysname' field of the struct returned\n> > +     by the uname(2) system call. Defaults to true.\n>\n> Shouldn't `sysname` be typeset as a literal, just like `true` and\n> `os-version`?\nI will do that in the next iteration. Thank you.\n>\n> > +os-version\n> > +~~~~~~~~~~\n> > +\n> > +In the same way as the `agent` capability above, the server can\n> > +advertise the `os-version` capability with a value `X` (in the form\n> > +`os-version=X`) to notify the client that the server is running an\n> > +operating system that can be identified by `X`. The client may\n>\n> Hmph.  I am not sure what's the value of mentioning 'X' here.  To me\n>\n>     ... can advertise the `os-version` capability to notify the kind\n>     of operating system it is running on.\n>\n> conveys the same thing with much fewer bytes.\nYeah, it is better, I will use it in the next iteration.\n>\n> > +optionally send its own `os-version` string by including the\n> > +`os-version` capability with a value `Y` (in the form `os-version=Y`)\n> > +in its request to the server (but it MUST NOT do so if the server did\n> > +not advertise the os-version capability). The `X` and `Y` strings may\n> > +contain any printable ASCII characters except space (i.e., the byte\n>\n> This is misleading.  ASCII printable characters range from 33 to 126\n> (inclusive), but by saying \"except space\", the readers are led to\n> believe that the author of this documentation thinks ASCII 32 is\n> printable, too.\nThanks for this, I will make changes in the next iteration.\n>\n> About 'X' and 'Y', we can just say \"the value of this capability may\n> consist of ASCII printable characters (from 33 to 126 inclusive)\" or\n> something.\n>\nNoted. Thank you.\n> Is there a need for a registry of canonical os-version strings?  One\n> reason why you would want this user-settable (as opposed to being\n> derived from \"uname -s\") is that a system that is presumably the\n> same in end-user perception can call itself in different names (your\n> Windows/MINGW64 example) and having the users set it to a string\n> chosen from a small repertoire, the other end would be able to\n> identify them more easily.  I do not think it is a necessarily a\n> good idea to limit what value the users can set to this\n> configuration variable, but at least with a published guideline on\n> calling various types of systems (and an explanation on the reason\n> why we publish such a guideline), users would make an informed\n> decision when picking what string to send.\nWe plan to implement another config option `osVersion.format`, which\nallow users to fully customize the string sent to the other side using\nplaceholders,\nsimilar to how git for-each-ref uses %() syntax. The user would be\nable to set it to\nthe string they want i.e \"Linux\" or \"Windows\" (without any\nplaceholder) and would be\nsent as-is. So, the `osVersion.format` should satisfy this need. I\nwill ensure to document\nthis option to tell that it can be used like this and will give a\nsmall list of `os-version` strings\nthat can be used in this way.\n>\n> > +# Trim and replace each character with ascii code below 32 or above\n> > +# 127 (included) using a dot '.' character.\n> > +# Octal intervals \\001-\\040 and \\177-\\377\n> > +# corresponds to decimal intervals 1-32 and 127-255\n> > +test_redact_non_printables () {\n> > +    tr -d \"\\n\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n> > +}\n>\n> Just being curious.  Do we need to worry about carriage-returns not\n> just line-feeds, and if not why?\nThe function `tr \"[\\001-\\040][\\177-\\377]\" \".\"` already replace the\ncarriage-returns with \".\"\nthe redact_non_printables() will also replace it with \".\".\nCarriage-returns octal code is 015 and\ndecimal code of 13. So, we do not need to worry about it.\n>\n> Thanks.\nThank you.\nUsman.\n"},{"id":"510157","messageId":"CAPSxiM_Cc+Si1Qoygn7me9jAqfV+B-s3p1uP6=qApnk-Q4X+wA@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqv7usylll.fsf@gitster.g","subject":"Re: [PATCH 2/4] version: refactor get_uname_info()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-08T13:06:52Z","receivedAt":"2025-01-08T13:07:03Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Hi Junio\n\nOn Mon, Jan 6, 2025 at 9:34 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > Some code from \"builtin/bugreport.c\" uses uname(2) to get system\n> > information.\n> >\n> > Let's refactor this code into a new get_uname_info() function, so\n> > that we can reuse it in a following commit.\n>\n> This does two things: refactor and enhancement.  Shouldn't it do\n> pure refactoring in a single patch, with a follow-up patch that\n> extends it to allow the caller to hide the system details?\n>\nThanks for this, I will split the commit into two patches in the next iteration.\n"},{"id":"510158","messageId":"CAPSxiM8_08xY5JSG2tU=1hLz3M+16i_jwgMXa48mvzbeYV1x-g@mail.gmail.com","threadId":"62747","inReplyTo":"CAPig+cQJM1TOad=buG0h2-3OnxEH6SUBz23y+wYq9GeUzhMwaA@mail.gmail.com","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-08T13:14:46Z","receivedAt":"2025-01-08T13:14:58Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Tue, Jan 7, 2025 at 4:47 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> On Mon, Jan 6, 2025 at 5:37 AM Usman Akinyemi\n> <usmanakinyemi202@gmail.com> wrote:\n> > As some issues that can happen with a Git client can be operating system\n> > specific, it can be useful for a server to know which OS a client is\n> > using. In the same way it can be useful for a client to know which OS\n> > a server is using.\n> >\n> > Let's introduce a new protocol (`os-version`) allowing Git clients and\n> > servers to exchange operating system information. The protocol is\n> > controlled by the new `transfer.advertiseOSVersion` config option.\n> >\n> > Add the `transfer.advertiseOSVersion` config option to address\n> > privacy concerns issue. It defaults to `true` and can be changed to\n> > `false`. When enabled, this option makes clients and servers send each\n> > other the OS name (e.g., \"Linux\" or \"Windows\"). The information is\n> > retrieved using the 'sysname' field of the `uname(2)` system call.\n> >\n> > However, there are differences between `uname(1)` (command-line utility)\n> > and `uname(2)` (system call) outputs on Windows. These discrepancies\n> > complicate testing on Windows platforms. For example:\n> >   - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n> >   .2024-02-14.20:17.UTC.x86_64\n> >   - `uname(2)` output: Windows.10.0.20348\n> >\n> > Until a good way to test the feature on Windows is found, the\n> > transfer.advertiseOSVersion is set to false on Windows during testing.\n>\n> This is because the uname(2) you mention above is not actually\n> system-supplied but is instead faked up Git itself for the Git for\n> Windows port. See git/compat/mingw.c:uname().\n>\n> The typical way to work around this sort of issue is to ensure that\n> you check Git against Git itself instead of checking Git against\n> \"system\". To do so, you would implement a new \"test-util\" command, say\n> `test-util uname`, in git/t/helpers/test-uname.c which internally\n> calls the same uname() function that other parts of Git call. Doing so\n> ensures consistency of output.\n>\n> Whether or not it makes sense to go through that extra work for this\n> particular case is a different question.\nHi Eric,\n\nThank you for the explanation. I will look into it.\n>\n> > Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> > Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> > ---\n> > diff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh\n> > @@ -123,9 +123,19 @@ test_expect_success 'git receive-pack --advertise-refs: v1' '\n> >  test_expect_success 'git upload-pack --advertise-refs: v2' '\n> > +       printf \"agent=FAKE\" >agent_and_os_name &&\n> > +       if test_have_prereq WINDOWS\n> > +       then\n> > +               # We do not use test_config here so that any tests below can reuse\n> > +               # the \"expect\" file from this test\n> > +               git config transfer.advertiseOSVersion false\n>\n> Should this have a comment explaining why you're disabling\n> transfer.advertiseOSVersion, in particular that you found uname() on\n> Windows unreliable, thus need to disable the check for this case?\n>\n> The comment you did compose exposes a fragility of the tests: in\n> particular that subsequent tests rely upon a side-effect of this test.\n> The fact that you had to include a special comment explaining the\n> problem argues for a cleaner solution, such as splitting out part of\n> this code into a separate test which comes before this one:\n> specifically, a \"setup\"-type test which creates the \"expect\" file\n> which gets reused by multiple tests.\nI will work on it and update it in the next iteration.\nThank you very much.\nUsman.\n>\n> > +       else\n> > +               printf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_os_name\n> > +       fi &&\n> > diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n> > @@ -8,13 +8,23 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> >  test_expect_success 'test capability advertisement' '\n> > +       printf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_os_name &&\n> > +       if test_have_prereq WINDOWS\n> > +       then\n> > +               # We do not use test_config here so that tests below will be able to reuse\n> > +               # the expect.base and expect.trailer files\n> > +               git config transfer.advertiseOSVersion false\n>\n> Ditto.\n>\n> > +       else\n> > +               printf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_os_name\n> > +       fi &&\n"},{"id":"510171","messageId":"xmqq1pxdnuxo.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM_0h7OyQO-Of8YhcOt4KbtuoKXe111ZCvsLf5y+OgCHaw@mail.gmail.com","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-08T16:15:31Z","receivedAt":"2025-01-08T16:15:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n>> Is there a need for a registry of canonical os-version strings?  One\n>> reason why you would want this user-settable (as opposed to being\n>> derived from \"uname -s\") is that a system that is presumably the\n>> same in end-user perception can call itself in different names (your\n>> Windows/MINGW64 example) and having the users set it to a string\n>> chosen from a small repertoire, the other end would be able to\n>> identify them more easily.  I do not think it is a necessarily a\n>> good idea to limit what value the users can set to this\n>> configuration variable, but at least with a published guideline on\n>> calling various types of systems (and an explanation on the reason\n>> why we publish such a guideline), users would make an informed\n>> decision when picking what string to send.\n>\n> We plan to implement another config option `osVersion.format`, which\n> allow users to fully customize the string sent to the other side using\n> placeholders,\n\nSorry, you lost me.\n\nI was wondering if we want to (informally at first) make it _less_\nflexible, so that we can prevent people from being \"creative\" when\nthe value of being creative is negative.  Adding even more ways to\ncustomize the string to subject the receiving/inspecting end to more\nunnecessary variations to call the same thing in different names is\nthe last thing we want to see in that context, isn't it?\n\nIf you have \"any random string goes\" configuration mechanism, it is\npretty much game over.  You do not need to add an elaborate .format\nmechanism to let users throw random garbage at the other side of the\nconnection.\n\n>> > +# Trim and replace each character with ascii code below 32 or above\n>> > +# 127 (included) using a dot '.' character.\n>> > +# Octal intervals \\001-\\040 and \\177-\\377\n>> > +# corresponds to decimal intervals 1-32 and 127-255\n>> > +test_redact_non_printables () {\n>> > +    tr -d \"\\n\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n>> > +}\n>>\n>> Just being curious.  Do we need to worry about carriage-returns not\n>> just line-feeds, and if not why?\n> The function `tr \"[\\001-\\040][\\177-\\377]\" \".\"` already replace the\n> carriage-returns with \".\"\n\nThat is exactly my point.  LF are stripped; I do not see a sensible\nreason why shouldn't CR be removed the same way.\n\nThanks.\n"},{"id":"510255","messageId":"CAPSxiM93qVjoDGBRaGAv1-o3oiKkEE0mtH9ERTN9dVxvZpCczw@mail.gmail.com","threadId":"62747","inReplyTo":"xmqq1pxdnuxo.fsf@gitster.g","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-09T14:25:32Z","receivedAt":"2025-01-09T14:25:44Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Wed, Jan 8, 2025 at 9:45 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> >> Is there a need for a registry of canonical os-version strings?  One\n> >> reason why you would want this user-settable (as opposed to being\n> >> derived from \"uname -s\") is that a system that is presumably the\n> >> same in end-user perception can call itself in different names (your\n> >> Windows/MINGW64 example) and having the users set it to a string\n> >> chosen from a small repertoire, the other end would be able to\n> >> identify them more easily.  I do not think it is a necessarily a\n> >> good idea to limit what value the users can set to this\n> >> configuration variable, but at least with a published guideline on\n> >> calling various types of systems (and an explanation on the reason\n> >> why we publish such a guideline), users would make an informed\n> >> decision when picking what string to send.\n> >\n> > We plan to implement another config option `osVersion.format`, which\n> > allow users to fully customize the string sent to the other side using\n> > placeholders,\n>\n> Sorry, you lost me.\n>\n> I was wondering if we want to (informally at first) make it _less_\n> flexible, so that we can prevent people from being \"creative\" when\n> the value of being creative is negative.  Adding even more ways to\n> customize the string to subject the receiving/inspecting end to more\n> unnecessary variations to call the same thing in different names is\n> the last thing we want to see in that context, isn't it?\n>\n> If you have \"any random string goes\" configuration mechanism, it is\n> pretty much game over.  You do not need to add an elaborate .format\n> mechanism to let users throw random garbage at the other side of the\n> connection.\nThanks for the explanation.\nInstead of having .format that will allow user to have multiple\nvariation or different placeholder,\nwe can allow it to take only specific values for examples:\n- \"full\" which would mean the same thing as  the result of `uname -srvm`,\n- \"default\" or \"short\" which would mean the same thing as  the result\nof `uname -s`,\n- \"medium\"  which would mean the same thing as  the result of `uname -sr`.\n\nWhat is your thought about this ?\n\nThank you.\n>\n> >> > +# Trim and replace each character with ascii code below 32 or above\n> >> > +# 127 (included) using a dot '.' character.\n> >> > +# Octal intervals \\001-\\040 and \\177-\\377\n> >> > +# corresponds to decimal intervals 1-32 and 127-255\n> >> > +test_redact_non_printables () {\n> >> > +    tr -d \"\\n\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n> >> > +}\n> >>\n> >> Just being curious.  Do we need to worry about carriage-returns not\n> >> just line-feeds, and if not why?\n> > The function `tr \"[\\001-\\040][\\177-\\377]\" \".\"` already replace the\n> > carriage-returns with \".\"\n>\n> That is exactly my point.  LF are stripped; I do not see a sensible\n> reason why shouldn't CR be removed the same way.\nYeah, I will add that in the next iteration.\n\nThank you.\nUsman.\n>\n> Thanks.\n"},{"id":"510258","messageId":"xmqq5xmokn0z.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM93qVjoDGBRaGAv1-o3oiKkEE0mtH9ERTN9dVxvZpCczw@mail.gmail.com","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-09T15:46:52Z","receivedAt":"2025-01-09T15:46:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Instead of having .format that will allow user to have multiple\n> variation or different placeholder,\n> we can allow it to take only specific values for examples:\n> - \"full\" which would mean the same thing as  the result of `uname -srvm`,\n> - \"default\" or \"short\" which would mean the same thing as  the result\n> of `uname -s`,\n> - \"medium\"  which would mean the same thing as  the result of `uname -sr`.\n>\n> What is your thought about this ?\n\nI think two-level is good enough.  One level is \"yes, please give\nthe minimum that would not offend even the privacy-conscious folks\n(like 'Linux', 'macOS', 'Windows' etc.)\" or \"no, please do not show\nos-version at all\".  The other is \"Please use this exact string.\"\nWe do not need anything more elaborate.\n\nThe reasoning behind this conclusion goes like this.\n\nFirst of all, I mentioned \"registry of canonical os-version strings\"\nto help the users of the \"Please use this string\" so their server do\nnot have to suffer from different names and spellings to identify\nthe same class of clients.\n\nBut the server operators that *want* such tighter control *and* are\ncapable of enforcing their choice to their users are probably $CORP\nin-house operators.  They can tell their employees what string to\nuse, or they may even do that in /etc/gitconfig on the machines they\ngive to their users.  In other words, they do not need our help at\nall.\n\nAt least that is my thought.  Others may have different opinions.\n\nThanks.\n"},{"id":"510334","messageId":"CAPSxiM8KYpmoSYaWciF_KrtHhUTPj543q9mgio+qdeB-FHeDUQ@mail.gmail.com","threadId":"62747","inReplyTo":"xmqq5xmokn0z.fsf@gitster.g","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-10T17:56:14Z","receivedAt":"2025-01-10T17:56:26Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Thu, Jan 9, 2025 at 9:16 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > Instead of having .format that will allow user to have multiple\n> > variation or different placeholder,\n> > we can allow it to take only specific values for examples:\n> > - \"full\" which would mean the same thing as  the result of `uname -srvm`,\n> > - \"default\" or \"short\" which would mean the same thing as  the result\n> > of `uname -s`,\n> > - \"medium\"  which would mean the same thing as  the result of `uname -sr`.\n> >\n> > What is your thought about this ?\n>\n> I think two-level is good enough.  One level is \"yes, please give\n> the minimum that would not offend even the privacy-conscious folks\n> (like 'Linux', 'macOS', 'Windows' etc.)\" or \"no, please do not show\n> os-version at all\".  The other is \"Please use this exact string.\"\n> We do not need anything more elaborate.\n>\n> The reasoning behind this conclusion goes like this.\n>\n> First of all, I mentioned \"registry of canonical os-version strings\"\n> to help the users of the \"Please use this string\" so their server do\n> not have to suffer from different names and spellings to identify\n> the same class of clients.\n>\n> But the server operators that *want* such tighter control *and* are\n> capable of enforcing their choice to their users are probably $CORP\n> in-house operators.  They can tell their employees what string to\n> use, or they may even do that in /etc/gitconfig on the machines they\n> give to their users.  In other words, they do not need our help at\n> all.\n>\n> At least that is my thought.  Others may have different opinions.\nHi Junio,\n\nThanks for this.\n\nSo instead of having a .format config, we should have a .string config\nwhich just\ntakes a string and uses it as the value for the `os-version` capability ?\n\nThank you.\nUsman.\n>\n> Thanks.\n"},{"id":"510340","messageId":"xmqqldvi5v5v.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM8KYpmoSYaWciF_KrtHhUTPj543q9mgio+qdeB-FHeDUQ@mail.gmail.com","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-10T19:24:44Z","receivedAt":"2025-01-10T19:24:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n>> First of all, I mentioned \"registry of canonical os-version strings\"\n>> to help the users of the \"Please use this string\" so their server do\n>> not have to suffer from different names and spellings to identify\n>> the same class of clients.\n>>\n>> But the server operators that *want* such tighter control *and* are\n>> capable of enforcing their choice to their users are probably $CORP\n>> in-house operators.  They can tell their employees what string to\n>> use, or they may even do that in /etc/gitconfig on the machines they\n>> give to their users.  In other words, they do not need our help at\n>> all.\n>>\n>> At least that is my thought.  Others may have different opinions.\n> Hi Junio,\n>\n> Thanks for this.\n>\n> So instead of having a .format config, we should have a .string config\n> which just\n> takes a string and uses it as the value for the `os-version` capability ?\n\nAh, sorry, I totally misread your patch.  I somehow thought you _already_\nhave the \"any string goes\" variant implemented in the patch being reviewed.\n\nIf there isn't any such thing, then my preference is add neither of\nthe configuration knobs and let the system provided function give a\nnot-too-specific os-version string (like \"Linux\").  Once people gain\nexperiences with that feature, then we will learn more about what\ndegree of customizability is required.\n\nSorry for the confusion.\n"},{"id":"510368","messageId":"CAPSxiM8kWoAXEvAVtp0C9NUQaas3KN=TMBA-p9o6+8KDQbmBUA@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqldvi5v5v.fsf@gitster.g","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-11T11:07:09Z","receivedAt":"2025-01-11T11:07:22Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Sat, Jan 11, 2025 at 12:54 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> >> First of all, I mentioned \"registry of canonical os-version strings\"\n> >> to help the users of the \"Please use this string\" so their server do\n> >> not have to suffer from different names and spellings to identify\n> >> the same class of clients.\n> >>\n> >> But the server operators that *want* such tighter control *and* are\n> >> capable of enforcing their choice to their users are probably $CORP\n> >> in-house operators.  They can tell their employees what string to\n> >> use, or they may even do that in /etc/gitconfig on the machines they\n> >> give to their users.  In other words, they do not need our help at\n> >> all.\n> >>\n> >> At least that is my thought.  Others may have different opinions.\n> > Hi Junio,\n> >\n> > Thanks for this.\n> >\n> > So instead of having a .format config, we should have a .string config\n> > which just\n> > takes a string and uses it as the value for the `os-version` capability ?\n>\n> Ah, sorry, I totally misread your patch.  I somehow thought you _already_\n> have the \"any string goes\" variant implemented in the patch being reviewed.\n>\n> If there isn't any such thing, then my preference is add neither of\n> the configuration knobs and let the system provided function give a\n> not-too-specific os-version string (like \"Linux\").  Once people gain\n> experiences with that feature, then we will learn more about what\n> degree of customizability is required.\n>\n> Sorry for the confusion.\nHi Junio,\n\nThanks for this.\n\nActually, in this patch series, there is a config option called\n`osVersion.command`\nThe specified command will be run and the output will be used as the\nvalue for `os-version`\ncapability. This option was particularly asked by Randall S. Becker in\na previous\nconversation https://lore.kernel.org/git/000a01dac25c$df7b23e0$9e716ba0$@nexbridge.com/\n\nThank you.\nUsman\n"},{"id":"510424","messageId":"xmqqmsfubtt8.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM8kWoAXEvAVtp0C9NUQaas3KN=TMBA-p9o6+8KDQbmBUA@mail.gmail.com","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-13T15:46:27Z","receivedAt":"2025-01-13T15:46:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Actually, in this patch series, there is a config option called\n> `osVersion.command`\n> The specified command will be run and the output will be used as the\n> value for `os-version`\n> capability.\n\nThat is essentially a \"you can throw at us any arbitrary string\".\nSo my recommendation would not change.  .format would not give us\nmuch _additional_ value in such a case.\n"},{"id":"510447","messageId":"CAPSxiM93asot47GgfK3ExNWd9M-4fjbWzRWQ=koCFdA8rNwD7g@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqmsfubtt8.fsf@gitster.g","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-13T18:26:29Z","receivedAt":"2025-01-13T18:26:41Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Mon, Jan 13, 2025 at 9:16 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > Actually, in this patch series, there is a config option called\n> > `osVersion.command`\n> > The specified command will be run and the output will be used as the\n> > value for `os-version`\n> > capability.\n>\n> That is essentially a \"you can throw at us any arbitrary string\".\n> So my recommendation would not change.  .format would not give us\n> much _additional_ value in such a case.\nHi Junio,\n\nThanks for this.  So, from what I understand, the feature and config\noption introduced by\nthis patch series is enough, no need to introduce another .format\nconfig. Right ?\n\nThanks.\n"},{"id":"510449","messageId":"xmqqfrlm8pim.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM93asot47GgfK3ExNWd9M-4fjbWzRWQ=koCFdA8rNwD7g@mail.gmail.com","subject":"Re: [PATCH 3/4] connect: advertise OS version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-13T19:47:29Z","receivedAt":"2025-01-13T19:47:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> On Mon, Jan 13, 2025 at 9:16 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>>\n>> > Actually, in this patch series, there is a config option called\n>> > `osVersion.command`\n>> > The specified command will be run and the output will be used as the\n>> > value for `os-version`\n>> > capability.\n>>\n>> That is essentially a \"you can throw at us any arbitrary string\".\n>> So my recommendation would not change.  .format would not give us\n>> much _additional_ value in such a case.\n> Hi Junio,\n>\n> Thanks for this.  So, from what I understand, the feature and config\n> option introduced by\n> this patch series is enough, no need to introduce another .format\n> config. Right ?\n\nYup.\n\nAt least until we and our userbase gain more experience with the\nfeature.\n\nThanks.\n"},{"id":"510450","messageId":"033a01db65f6$b88d4db0$29a7e910$@nexbridge.com","threadId":"62747","inReplyTo":"xmqqfrlm8pim.fsf@gitster.g","subject":"RE: [PATCH 3/4] connect: advertise OS version","fromName":"","fromEmail":"rsbecker@nexbridge.com","sentAt":"2025-01-13T20:07:00Z","receivedAt":"2025-01-13T20:07:43Z","isPatch":true,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On January 13, 2025 2:47 PM, Junio C Hamano wrote:\n>Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n>> On Mon, Jan 13, 2025 at 9:16 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>>\n>>> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>>>\n>>> > Actually, in this patch series, there is a config option called\n>>> > `osVersion.command` The specified command will be run and the\n>>> > output will be used as the value for `os-version` capability.\n>>>\n>>> That is essentially a \"you can throw at us any arbitrary string\".\n>>> So my recommendation would not change.  .format would not give us\n>>> much _additional_ value in such a case.\n>> Hi Junio,\n>>\n>> Thanks for this.  So, from what I understand, the feature and config\n>> option introduced by this patch series is enough, no need to introduce\n>> another .format config. Right ?\n>\n>Yup.\n>\n>At least until we and our userbase gain more experience with the feature.\n\nMy thought on this is somehow relating uname -? (something) with the format\ncode. Options to uname are not always standard and there are extensions, so\nthere might be some use in having a binding for non-standard stuff. For example,\non NonStop, uname -r returns the major OS level, and uname -v returns the\nminor level. My thought on this is .format R=uname -r, .format V=uname -v, or\nsomething like that. But as you said, it will take time for us to get experience\nwith this.\n\n--Randall\n\n"},{"id":"510786","messageId":"20250117104639.65608-1-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250106103713.1452035-1-usmanakinyemi202@gmail.com","subject":"[PATCH v2 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-17T10:46:12Z","receivedAt":"2025-01-17T10:46:46Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"For debugging, statistical analysis, and security purposes, it can\nbe valuable for Git servers to know the operating system the clients\nare using.\n\nFor example:\n- A server noticing that a client is using an old Git version with\nsecurity issues on one platform, like macOS, could verify if the\nuser is indeed running macOS before sending a message to upgrade.\"\n- Similarly, a server identifying a client that could benefit from\nan upgrade (e.g., for performance reasons) could better customize the\nmessage it sends to nudge the client to upgrade.\n\nSo let's add a new 'os-version' capability to the v2 protocol, in the\nsame way as the existing 'agent' capability that lets clients and servers\nexchange the Git version they are running.\n\nBy default this sends similar info as `git bugreport` is already sending,\nwhich uses uname(2). The difference is that it is sanitized in the same\nway as the Git version sent by the 'agent' capability is sanitized\n(by replacing characters having an ascii code less than 32 or more\nthan 127 with '.'). Also, it only sends the result of `uname -s` i.e\njust only the operating system name (e.g \"Linux\").\n\nDue to privacy issues and concerns, let's add the `transfer.advertiseOSVersion`\nconfig option. This boolean option is enabled by default, but allows users to\ndisable this feature completely by setting it to \"false\".\n\nTo provide flexibility and customization, let also add the `osversion.command`\nconfig option. This allows users to specify a custom command whose output will\nbe used as the string exchanged via the \"os-version\" capability. If this option\nis not set, the default behavior exchanges only the operating system name,\nsuch as \"Linux\" or \"Windows\". This option was particularly suggested by Randall S. Becker\nin a previous conversation. You can find the reference here\nhttps://lore.kernel.org/git/000a01dac25c$df7b23e0$9e716ba0$@nexbridge.com/\n\nNote that, due to differences between `uname(1)` (command-line\nutility) and `uname(2)` (system call) outputs on Windows,\n`transfer.advertiseOSVersion` is set to false on Windows during\ntesting. See the message part of patch 5/6 for more details.\n\nMy mentor, Christian Couder, sent a previous patch series about this\nbefore. You can find it here\nhttps://lore.kernel.org/git/20240619125708.3719150-1-christian.couder@gmail.com/\n\nChanges since v1\n================\n  - Refactored documentation for improved clarity.\n  - Splitted patch \"refactor get_uname_info()\" into two patches with first\n    part doing refactoring and the second part doing enhancement for code\n    clearity and cleanliness.\n  - Made test_redact_non_printables() to trim carriage-returns.\n  - Fixed outdated commit message.\n  - Splitted part of the \"test capability advertisement\" into a setup\"-type\n    to remove side-effect dependency.\n  - Changed the name of some created files used in testing for better\n    clearity of what their content is.\n  - Added comment to os_version(), os_version_sanitized() and advertise_os_version()\n    for improved clarity of what they do.\n\nUsman Akinyemi (6):\n  version: refactor redact_non_printables()\n  version: refactor get_uname_info()\n  version: extend get_uname_info() to hide system details\n  t5701: add setup test to remove side-effect dependency\n  connect: advertise OS version\n  version: introduce osversion.command config for os-version output\n\n Documentation/config/transfer.txt |  16 ++++\n Documentation/gitprotocol-v2.txt  |  17 ++++\n builtin/bugreport.c               |  13 +--\n connect.c                         |   3 +\n serve.c                           |  14 ++++\n t/t5555-http-smart-common.sh      |  38 ++++++++-\n t/t5701-git-serve.sh              |  59 ++++++++++++-\n t/test-lib-functions.sh           |   8 ++\n version.c                         | 135 ++++++++++++++++++++++++++++--\n version.h                         |  28 +++++++\n 10 files changed, 309 insertions(+), 22 deletions(-)\n\nRange-diff versus v1:\n\n1:  d23091031c ! 1:  97bccab6d5 version: refactor redact_non_printables()\n    @@ Commit message\n         For now the new redact_non_printables() function is still static as\n         it's only needed locally.\n     \n    -    While at it, let's also make a few small improvements:\n    -      - use 'size_t' for 'i' instead of 'int',\n    -      - move the declaration of 'i' inside the 'for ( ... )',\n    -      - use strbuf_detach() to explicitly detach the string contained by\n    -        the 'buf' strbuf.\n    +    While at it, let's use strbuf_detach() to explicitly detach the string\n    +    contained by the 'buf' strbuf.\n     \n         Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n2:  1336622be9 ! 2:  1f8a4024a4 version: refactor get_uname_info()\n    @@ Commit message\n         Let's refactor this code into a new get_uname_info() function, so\n         that we can reuse it in a following commit.\n     \n    -    We may need to refactor this function in the future if an\n    -    `osVersion.format` config option is added, but for now we only\n    -    need it to accept a \"full\" flag that makes it switch between providing\n    -    full OS information and providing only the OS name. The mode\n    -    providing only the OS name is needed in a following commit\n    -\n         Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n     \n    @@ builtin/bugreport.c: static void get_system_info(struct strbuf *sys_info)\n     -\t\t\t    uname_info.release,\n     -\t\t\t    uname_info.version,\n     -\t\t\t    uname_info.machine);\n    -+\tget_uname_info(sys_info, 1);\n    ++\tget_uname_info(sys_info);\n      \n      \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n      \tget_compiler_info(sys_info);\n    @@ version.c: const char *git_user_agent_sanitized(void)\n      \treturn agent;\n      }\n     +\n    -+int get_uname_info(struct strbuf *buf, unsigned int full)\n    ++int get_uname_info(struct strbuf *buf)\n     +{\n     +\tstruct utsname uname_info;\n     +\n    @@ version.c: const char *git_user_agent_sanitized(void)\n     +\t\treturn -1;\n     +\t}\n     +\n    -+\tif (full)\n    -+\t\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n    -+\t\t\t    uname_info.sysname,\n    -+\t\t\t    uname_info.release,\n    -+\t\t\t    uname_info.version,\n    -+\t\t\t    uname_info.machine);\n    -+\telse\n    -+\t\tstrbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n    ++\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n    ++\t\t    uname_info.sysname,\n    ++\t\t    uname_info.release,\n    ++\t\t    uname_info.version,\n    ++\t\t    uname_info.machine);\n     +\treturn 0;\n     +}\n     \n    @@ version.h: extern const char git_built_from_commit_string[];\n     +  Return -1 and put an error message into 'buf' in case of uname()\n     +  error. Return 0 and put uname info into 'buf' otherwise.\n     +*/\n    -+int get_uname_info(struct strbuf *buf, unsigned int full);\n    ++int get_uname_info(struct strbuf *buf);\n     +\n      #endif /* VERSION_H */\n-:  ---------- > 3:  962b42702f version: extend get_uname_info() to hide system details\n-:  ---------- > 4:  7f0ec75a0d t5701: add setup test to remove side-effect dependency\n3:  b90a24813f ! 5:  499eda49cf connect: advertise OS version\n    @@ Commit message\n         controlled by the new `transfer.advertiseOSVersion` config option.\n     \n         Add the `transfer.advertiseOSVersion` config option to address\n    -    privacy concerns issue. It defaults to `true` and can be changed to\n    +    privacy concerns. It defaults to `true` and can be changed to\n         `false`. When enabled, this option makes clients and servers send each\n         other the OS name (e.g., \"Linux\" or \"Windows\"). The information is\n         retrieved using the 'sysname' field of the `uname(2)` system call.\n    @@ Commit message\n           .2024-02-14.20:17.UTC.x86_64\n           - `uname(2)` output: Windows.10.0.20348\n     \n    -    Until a good way to test the feature on Windows is found, the\n    -    transfer.advertiseOSVersion is set to false on Windows during testing.\n    +    On Windows, uname(2) is not actually system-supplied but is instead\n    +    already faked up by Git itself. We could have overcome the test issue\n    +    on Windows by implementing a new `uname` subcommand in `test-tool`\n    +    using uname(2), but except uname(2), which would be tested against\n    +    itself, there would be nothing platform specific, so it's just simpler\n    +    to disable the tests on Windows.\n     \n         Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n    @@ Documentation/config/transfer.txt: transfer.bundleURI::\n     +\tWhen `true`, the `os-version` capability is advertised by clients and\n     +\tservers. It makes clients and servers send to each other a string\n     +\trepresenting the operating system name, like \"Linux\" or \"Windows\".\n    -+\tThis string is retrieved from the 'sysname' field of the struct returned\n    ++\tThis string is retrieved from the `sysname` field of the struct returned\n     +\tby the uname(2) system call. Defaults to true.\n     \n      ## Documentation/gitprotocol-v2.txt ##\n    @@ Documentation/gitprotocol-v2.txt: printable ASCII characters except space (i.e.,\n     +~~~~~~~~~~\n     +\n     +In the same way as the `agent` capability above, the server can\n    -+advertise the `os-version` capability with a value `X` (in the form\n    -+`os-version=X`) to notify the client that the server is running an\n    -+operating system that can be identified by `X`. The client may\n    -+optionally send its own `os-version` string by including the\n    -+`os-version` capability with a value `Y` (in the form `os-version=Y`)\n    -+in its request to the server (but it MUST NOT do so if the server did\n    -+not advertise the os-version capability). The `X` and `Y` strings may\n    -+contain any printable ASCII characters except space (i.e., the byte\n    -+range 32 < x < 127), and are typically made from the result of\n    ++advertise the `os-version` capability to notify the client the\n    ++kind of operating system it is running on. The client may optionally\n    ++send its own `os-version` capability, to notify the server the kind of\n    ++operating system it is also running on in its request to the server\n    ++(but it MUST NOT do so if the server did not advertise the os-version\n    ++capability). The value of this capability may consist of ASCII printable\n    ++characters(from 33 to 126 inclusive) and are typically made from the result of\n     +`uname -s`(OS name e.g Linux). The os-version capability can be disabled\n     +entirely by setting the `transfer.advertiseOSVersion` config option\n     +to `false`. The `os-version` strings are purely informative for\n    @@ t/t5555-http-smart-common.sh: test_expect_success 'git receive-pack --advertise-\n      '\n      \n      test_expect_success 'git upload-pack --advertise-refs: v2' '\n    -+\tprintf \"agent=FAKE\" >agent_and_os_name &&\n    ++\tprintf \"agent=FAKE\" >agent_and_osversion &&\n     +\tif test_have_prereq WINDOWS\n     +\tthen\n    -+\t\t# We do not use test_config here so that any tests below can reuse\n    -+\t\t# the \"expect\" file from this test\n     +\t\tgit config transfer.advertiseOSVersion false\n     +\telse\n    -+\t\tprintf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_os_name\n    ++\t\tprintf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_osversion\n     +\tfi &&\n     +\n      \tcat >expect <<-EOF &&\n      \tversion 2\n     -\tagent=FAKE\n    -+\t$(cat agent_and_os_name)\n    ++\t$(cat agent_and_osversion)\n      \tls-refs=unborn\n      \tfetch=shallow wait-for-done\n      \tserver-option\n     \n      ## t/t5701-git-serve.sh ##\n    -@@ t/t5701-git-serve.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n    - . ./test-lib.sh\n    - \n    - test_expect_success 'test capability advertisement' '\n    -+\tprintf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_os_name &&\n    +@@ t/t5701-git-serve.sh: test_expect_success 'setup to generate files with expected content' '\n    + \tcat >expect.trailer <<-EOF &&\n    + \t0000\n    + \tEOF\n    ++\n     +\tif test_have_prereq WINDOWS\n     +\tthen\n    -+\t\t# We do not use test_config here so that tests below will be able to reuse\n    -+\t\t# the expect.base and expect.trailer files\n     +\t\tgit config transfer.advertiseOSVersion false\n     +\telse\n    -+\t\tprintf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_os_name\n    ++\t\tprintf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_osversion\n     +\tfi &&\n     +\n    - \ttest_oid_cache <<-EOF &&\n    - \twrong_algo sha1:sha256\n    - \twrong_algo sha256:sha1\n    ++\tcat >expect_osversion.base <<-EOF\n    ++\tversion 2\n    ++\t$(cat agent_and_osversion)\n    ++\tls-refs=unborn\n    ++\tfetch=shallow wait-for-done\n    ++\tserver-option\n    ++\tobject-format=$(test_oid algo)\n    ++\tEOF\n    + '\n    + \n    + test_expect_success 'test capability advertisement' '\n    +-\tcat expect.base expect.trailer >expect &&\n    ++\tcat expect_osversion.base expect.trailer >expect &&\n    + \n    + \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n    + \t\t--advertise-capabilities >out &&\n    +@@ t/t5701-git-serve.sh: test_expect_success 'test capability advertisement with uploadpack.advertiseBund\n    + \tcat >expect.extra <<-EOF &&\n    + \tbundle-uri\n      \tEOF\n    - \tcat >expect.base <<-EOF &&\n    - \tversion 2\n    --\tagent=git/$(git version | cut -d\" \" -f3)\n    -+\t$(cat agent_and_os_name)\n    - \tls-refs=unborn\n    - \tfetch=shallow wait-for-done\n    - \tserver-option\n    +-\tcat expect.base \\\n    ++\tcat expect_osversion.base \\\n    + \t    expect.extra \\\n    + \t    expect.trailer >expect &&\n    + \n     \n      ## t/test-lib-functions.sh ##\n     @@ t/test-lib-functions.sh: test_trailing_hash () {\n    @@ t/test-lib-functions.sh: test_trailing_hash () {\n     +# Octal intervals \\001-\\040 and \\177-\\377\n     +# corresponds to decimal intervals 1-32 and 127-255\n     +test_redact_non_printables () {\n    -+    tr -d \"\\n\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n    ++    tr -d \"\\n\\r\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n     +}\n     \n      ## version.c ##\n    @@ version.c\n      const char git_version_string[] = GIT_VERSION;\n      const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n     @@ version.c: int get_uname_info(struct strbuf *buf, unsigned int full)\n    - \t\tstrbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n    + \t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n      \treturn 0;\n      }\n     +\n    @@ version.h: const char *git_user_agent_sanitized(void);\n      */\n      int get_uname_info(struct strbuf *buf, unsigned int full);\n      \n    ++/*\n    ++  Retrieve and cache system information for subsequent calls.\n    ++  Return a pointer to the cached system information string.\n    ++*/\n     +const char *os_version(void);\n    ++\n    ++/*\n    ++  Retrieve system information string from os_version(). Then\n    ++  sanitize and cache it. Return a pointer to the sanitized\n    ++  system information string.\n    ++*/\n     +const char *os_version_sanitized(void);\n    ++\n    ++/*\n    ++  Retrieve and cache whether os-version capability is enabled.\n    ++  Return 1 if enabled, 0 if disabled.\n    ++*/\n     +int advertise_os_version(struct repository *r);\n     +\n      #endif /* VERSION_H */\n4:  745e63060e ! 6:  a1637dc7cf version: introduce osversion.command config for os-version output\n    @@ Commit message\n         Let's introduce a new configuration option, `osversion.command`, to handle\n         the string exchange between servers and clients. This option allows\n         customization of the exchanged string by leveraging the output of the\n    -    specified command. If this is not set, the `os-version` capability\n    -    exchange just the operating system name.\n    +    specified command. This customization might be especially useful on some\n    +    quite uncommon platforms like NonStop where interesting OS information is\n    +    available from other means than uname(2).\n     \n    +    If this new configuration option is not set, the `os-version` capability\n    +    exchanges just the operating system name.\n    +\n    +    Helped-by: Randall S. Becker <rsbecker@nexbridge.com>\n         Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n     \n    @@ Documentation/config/transfer.txt\n     @@ Documentation/config/transfer.txt: transfer.advertiseOSVersion::\n      \tservers. It makes clients and servers send to each other a string\n      \trepresenting the operating system name, like \"Linux\" or \"Windows\".\n    - \tThis string is retrieved from the 'sysname' field of the struct returned\n    + \tThis string is retrieved from the `sysname` field of the struct returned\n     -\tby the uname(2) system call. Defaults to true.\n     +\tby the uname(2) system call. If the `osVersion.command` is set, the\n     +\toutput of the command specified will be the string exchanged by the clients\n    @@ Documentation/config/transfer.txt: transfer.advertiseOSVersion::\n     +\t`transfer.advertiseOSVersion` config option.\n     \n      ## Documentation/gitprotocol-v2.txt ##\n    -@@ Documentation/gitprotocol-v2.txt: in its request to the server (but it MUST NOT do so if the server did\n    - not advertise the os-version capability). The `X` and `Y` strings may\n    - contain any printable ASCII characters except space (i.e., the byte\n    - range 32 < x < 127), and are typically made from the result of\n    +@@ Documentation/gitprotocol-v2.txt: the presence or absence of particular features.\n    + os-version\n    + ~~~~~~~~~~\n    + \n    +-In the same way as the `agent` capability above, the server can\n    +-advertise the `os-version` capability to notify the client the\n    +-kind of operating system it is running on. The client may optionally\n    +-send its own `os-version` capability, to notify the server the kind of\n    +-operating system it is also running on in its request to the server\n    +-(but it MUST NOT do so if the server did not advertise the os-version\n    +-capability). The value of this capability may consist of ASCII printable\n    ++In the same way as the `agent` capability above, the server can advertise\n    ++the `os-version` capability to notify the client the kind of operating system\n    ++it is running on. The client may optionally send its own `os-version` capability,\n    ++to notify the server the kind of operating system it is also running on in its\n    ++request to the server (but it MUST NOT do so if the server did not advertise the\n    ++os-version capability). The value of this capability may consist of ASCII printable\n    + characters(from 33 to 126 inclusive) and are typically made from the result of\n     -`uname -s`(OS name e.g Linux). The os-version capability can be disabled\n     -entirely by setting the `transfer.advertiseOSVersion` config option\n     -to `false`. The `os-version` strings are purely informative for\n     -statistics and debugging purposes, and MUST NOT be used to\n     -programmatically assume the presence or absence of particular\n     -features.\n    -+`uname -s`(OS name e.g Linux).  If the `osVersion.command` is set,\n    -+the `X` and `Y` are made from the ouput of the command specified.\n    -+The os-version capability can be disabled entirely by setting the\n    -+`transfer.advertiseOSVersion` config option to `false`. The `os-version`\n    -+strings are purely informative for statistics and debugging purposes, and\n    -+MUST NOT be used to programmatically assume the presence or absence of\n    -+particular features.\n    ++`uname -s`(OS name e.g Linux). If the `osVersion.command` is set, the value of this\n    ++capability are made from the ouput of the command specified. The os-version capability\n    ++can be disabled entirely by setting the `transfer.advertiseOSVersion` config option\n    ++to `false`. The `os-version` strings are purely informative for statistics and\n    ++debugging purposes, and MUST NOT be used to programmatically assume the presence or\n    ++absence of particular features.\n      \n      ls-refs\n      ~~~~~~~\n    @@ t/t5555-http-smart-common.sh: test_expect_success 'git upload-pack --advertise-r\n      '\n      \n     +test_expect_success 'git upload-pack --advertise-refs: v2 with osVersion.command config set' '\n    -+\t# test_config is used here as we are not reusing any file output from here\n     +\ttest_config osVersion.command \"uname -srvm\" &&\n    -+\tprintf \"agent=FAKE\" >agent_and_long_os_name &&\n    ++\tprintf \"agent=FAKE\" >agent_and_long_osversion &&\n     +\n     +\tif test_have_prereq !WINDOWS\n     +\tthen\n    -+\t\tprintf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_os_name\n    ++\t\tprintf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_osversion\n     +\tfi &&\n     +\n     +\tcat >expect <<-EOF &&\n     +\tversion 2\n    -+\t$(cat agent_and_long_os_name)\n    ++\t$(cat agent_and_long_osversion)\n     +\tls-refs=unborn\n     +\tfetch=shallow wait-for-done\n     +\tserver-option\n    @@ t/t5701-git-serve.sh: test_expect_success 'test capability advertisement' '\n      '\n      \n     +test_expect_success 'test capability advertisement with osVersion.command config set' '\n    -+\t# test_config is used here as we are not reusing any file output from here\n     +\ttest_config osVersion.command \"uname -srvm\" &&\n    -+\tprintf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_long_os_name &&\n    ++\tprintf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_long_osversion &&\n     +\n     +\tif test_have_prereq !WINDOWS\n     +\tthen\n    -+\t\tprintf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_os_name\n    ++\t\tprintf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_osversion\n     +\tfi &&\n     +\n     +\ttest_oid_cache <<-EOF &&\n     +\twrong_algo sha1:sha256\n     +\twrong_algo sha256:sha1\n     +\tEOF\n    -+\tcat >expect.base_long <<-EOF &&\n    ++\tcat >expect_long.base <<-EOF &&\n     +\tversion 2\n    -+\t$(cat agent_and_long_os_name)\n    ++\t$(cat agent_and_long_osversion)\n     +\tls-refs=unborn\n     +\tfetch=shallow wait-for-done\n     +\tserver-option\n     +\tobject-format=$(test_oid algo)\n     +\tEOF\n    -+\tcat >expect.trailer_long <<-EOF &&\n    -+\t0000\n    -+\tEOF\n    -+\tcat expect.base_long expect.trailer_long >expect &&\n    ++\tcat expect_long.base expect.trailer >expect &&\n     +\n     +\tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n     +\t\t--advertise-capabilities >out &&\n\n-- \n2.48.0\n\n"},{"id":"510787","messageId":"20250117104639.65608-2-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250117104639.65608-1-usmanakinyemi202@gmail.com","subject":"[PATCH v2 1/6] version: refactor redact_non_printables()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-17T10:46:13Z","receivedAt":"2025-01-17T10:46:50Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"The git_user_agent_sanitized() function performs some sanitizing to\navoid special characters being sent over the line and possibly messing\nup with the protocol or with the parsing on the other side.\n\nLet's extract this sanitizing into a new redact_non_printables() function,\nas we will want to reuse it in a following patch.\n\nFor now the new redact_non_printables() function is still static as\nit's only needed locally.\n\nWhile at it, let's use strbuf_detach() to explicitly detach the string\ncontained by the 'buf' strbuf.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n version.c | 22 ++++++++++++++++------\n 1 file changed, 16 insertions(+), 6 deletions(-)\n\ndiff --git a/version.c b/version.c\nindex 4d763ab48d..78f025c808 100644\n--- a/version.c\n+++ b/version.c\n@@ -6,6 +6,20 @@\n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n \n+/*\n+ * Trim and replace each character with ascii code below 32 or above\n+ * 127 (included) using a dot '.' character.\n+ * TODO: ensure consecutive non-printable characters are only replaced once\n+*/\n+static void redact_non_printables(struct strbuf *buf)\n+{\n+\tstrbuf_trim(buf);\n+\tfor (size_t i = 0; i < buf->len; i++) {\n+\t\tif (buf->buf[i] <= 32 || buf->buf[i] >= 127)\n+\t\t\tbuf->buf[i] = '.';\n+\t}\n+}\n+\n const char *git_user_agent(void)\n {\n \tstatic const char *agent = NULL;\n@@ -27,12 +41,8 @@ const char *git_user_agent_sanitized(void)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&buf, git_user_agent());\n-\t\tstrbuf_trim(&buf);\n-\t\tfor (size_t i = 0; i < buf.len; i++) {\n-\t\t\tif (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n-\t\t\t\tbuf.buf[i] = '.';\n-\t\t}\n-\t\tagent = buf.buf;\n+\t\tredact_non_printables(&buf);\n+\t\tagent = strbuf_detach(&buf, NULL);\n \t}\n \n \treturn agent;\n-- \n2.48.0\n\n"},{"id":"510788","messageId":"20250117104639.65608-3-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250117104639.65608-1-usmanakinyemi202@gmail.com","subject":"[PATCH v2 2/6] version: refactor get_uname_info()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-17T10:46:14Z","receivedAt":"2025-01-17T10:46:54Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Some code from \"builtin/bugreport.c\" uses uname(2) to get system\ninformation.\n\nLet's refactor this code into a new get_uname_info() function, so\nthat we can reuse it in a following commit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c | 13 ++-----------\n version.c           | 20 ++++++++++++++++++++\n version.h           |  7 +++++++\n 3 files changed, 29 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 7c2df035c9..5e13d532a8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -12,10 +12,10 @@\n #include \"diagnose.h\"\n #include \"object-file.h\"\n #include \"setup.h\"\n+#include \"version.h\"\n \n static void get_system_info(struct strbuf *sys_info)\n {\n-\tstruct utsname uname_info;\n \tchar *shell = NULL;\n \n \t/* get git version from native cmd */\n@@ -24,16 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tif (uname(&uname_info))\n-\t\tstrbuf_addf(sys_info, _(\"uname() failed with error '%s' (%d)\\n\"),\n-\t\t\t    strerror(errno),\n-\t\t\t    errno);\n-\telse\n-\t\tstrbuf_addf(sys_info, \"%s %s %s %s\\n\",\n-\t\t\t    uname_info.sysname,\n-\t\t\t    uname_info.release,\n-\t\t\t    uname_info.version,\n-\t\t\t    uname_info.machine);\n+\tget_uname_info(sys_info);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 78f025c808..96f474c8e6 100644\n--- a/version.c\n+++ b/version.c\n@@ -2,6 +2,7 @@\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n+#include \"gettext.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -47,3 +48,22 @@ const char *git_user_agent_sanitized(void)\n \n \treturn agent;\n }\n+\n+int get_uname_info(struct strbuf *buf)\n+{\n+\tstruct utsname uname_info;\n+\n+\tif (uname(&uname_info)) {\n+\t\tstrbuf_addf(buf, _(\"uname() failed with error '%s' (%d)\\n\"),\n+\t\t\t    strerror(errno),\n+\t\t\t    errno);\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t    uname_info.sysname,\n+\t\t    uname_info.release,\n+\t\t    uname_info.version,\n+\t\t    uname_info.machine);\n+\treturn 0;\n+}\ndiff --git a/version.h b/version.h\nindex 7c62e80577..afe3dbbab7 100644\n--- a/version.h\n+++ b/version.h\n@@ -7,4 +7,11 @@ extern const char git_built_from_commit_string[];\n const char *git_user_agent(void);\n const char *git_user_agent_sanitized(void);\n \n+/*\n+  Try to get information about the system using uname(2).\n+  Return -1 and put an error message into 'buf' in case of uname()\n+  error. Return 0 and put uname info into 'buf' otherwise.\n+*/\n+int get_uname_info(struct strbuf *buf);\n+\n #endif /* VERSION_H */\n-- \n2.48.0\n\n"},{"id":"510789","messageId":"20250117104639.65608-4-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250117104639.65608-1-usmanakinyemi202@gmail.com","subject":"[PATCH v2 3/6] version: extend get_uname_info() to hide system details","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-17T10:46:15Z","receivedAt":"2025-01-17T10:46:58Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently, get_uname_info() function provides the full OS information.\nIn a follwing commit, we will need it to provide only the OS name.\n\nLet's extend it to accept a \"full\" flag that makes it switch between\nproviding full OS information and providing only the OS name.\n\nWe may need to refactor this function in the future if an\n`osVersion.format` is added.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c |  2 +-\n version.c           | 16 +++++++++-------\n version.h           |  2 +-\n 3 files changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 5e13d532a8..e3288a86c8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -24,7 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tget_uname_info(sys_info);\n+\tget_uname_info(sys_info, 1);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 96f474c8e6..46835ec83f 100644\n--- a/version.c\n+++ b/version.c\n@@ -49,7 +49,7 @@ const char *git_user_agent_sanitized(void)\n \treturn agent;\n }\n \n-int get_uname_info(struct strbuf *buf)\n+int get_uname_info(struct strbuf *buf, unsigned int full)\n {\n \tstruct utsname uname_info;\n \n@@ -59,11 +59,13 @@ int get_uname_info(struct strbuf *buf)\n \t\t\t    errno);\n \t\treturn -1;\n \t}\n-\n-\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n-\t\t    uname_info.sysname,\n-\t\t    uname_info.release,\n-\t\t    uname_info.version,\n-\t\t    uname_info.machine);\n+\tif (full)\n+\t\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t\t    uname_info.sysname,\n+\t\t\t    uname_info.release,\n+\t\t\t    uname_info.version,\n+\t\t\t    uname_info.machine);\n+\telse\n+\t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n \treturn 0;\n }\ndiff --git a/version.h b/version.h\nindex afe3dbbab7..5eb586c0bd 100644\n--- a/version.h\n+++ b/version.h\n@@ -12,6 +12,6 @@ const char *git_user_agent_sanitized(void);\n   Return -1 and put an error message into 'buf' in case of uname()\n   error. Return 0 and put uname info into 'buf' otherwise.\n */\n-int get_uname_info(struct strbuf *buf);\n+int get_uname_info(struct strbuf *buf, unsigned int full);\n \n #endif /* VERSION_H */\n-- \n2.48.0\n\n"},{"id":"510790","messageId":"20250117104639.65608-5-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250117104639.65608-1-usmanakinyemi202@gmail.com","subject":"[PATCH v2 4/6] t5701: add setup test to remove side-effect dependency","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-17T10:46:16Z","receivedAt":"2025-01-17T10:47:02Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently, the \"test capability advertisement\" test creates some files\nwith expected content which are used by other tests below it.\n\nTo remove that side-effect from this test, let's split up part of\nit into a \"setup\"-type test which creates the files with expected content\nwhich gets reused by multiple tests. This will be useful in a following\ncommit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n t/t5701-git-serve.sh | 10 ++++++++--\n 1 file changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex de904c1655..0c0a5b2aec 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -7,14 +7,17 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n \n-test_expect_success 'test capability advertisement' '\n+test_expect_success 'setup to generate files with expected content' '\n+\tprintf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_osversion &&\n+\n \ttest_oid_cache <<-EOF &&\n \twrong_algo sha1:sha256\n \twrong_algo sha256:sha1\n \tEOF\n+\n \tcat >expect.base <<-EOF &&\n \tversion 2\n-\tagent=git/$(git version | cut -d\" \" -f3)\n+\t$(cat agent_and_osversion)\n \tls-refs=unborn\n \tfetch=shallow wait-for-done\n \tserver-option\n@@ -23,6 +26,9 @@ test_expect_success 'test capability advertisement' '\n \tcat >expect.trailer <<-EOF &&\n \t0000\n \tEOF\n+'\n+\n+test_expect_success 'test capability advertisement' '\n \tcat expect.base expect.trailer >expect &&\n \n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n-- \n2.48.0\n\n"},{"id":"510791","messageId":"20250117104639.65608-6-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250117104639.65608-1-usmanakinyemi202@gmail.com","subject":"[PATCH v2 5/6] connect: advertise OS version","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-17T10:46:17Z","receivedAt":"2025-01-17T10:47:06Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"As some issues that can happen with a Git client can be operating system\nspecific, it can be useful for a server to know which OS a client is\nusing. In the same way it can be useful for a client to know which OS\na server is using.\n\nLet's introduce a new protocol (`os-version`) allowing Git clients and\nservers to exchange operating system information. The protocol is\ncontrolled by the new `transfer.advertiseOSVersion` config option.\n\nAdd the `transfer.advertiseOSVersion` config option to address\nprivacy concerns. It defaults to `true` and can be changed to\n`false`. When enabled, this option makes clients and servers send each\nother the OS name (e.g., \"Linux\" or \"Windows\"). The information is\nretrieved using the 'sysname' field of the `uname(2)` system call.\n\nHowever, there are differences between `uname(1)` (command-line utility)\nand `uname(2)` (system call) outputs on Windows. These discrepancies\ncomplicate testing on Windows platforms. For example:\n  - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n  .2024-02-14.20:17.UTC.x86_64\n  - `uname(2)` output: Windows.10.0.20348\n\nOn Windows, uname(2) is not actually system-supplied but is instead\nalready faked up by Git itself. We could have overcome the test issue\non Windows by implementing a new `uname` subcommand in `test-tool`\nusing uname(2), but except uname(2), which would be tested against\nitself, there would be nothing platform specific, so it's just simpler\nto disable the tests on Windows.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n Documentation/config/transfer.txt |  7 ++++++\n Documentation/gitprotocol-v2.txt  | 18 +++++++++++++\n connect.c                         |  3 +++\n serve.c                           | 14 +++++++++++\n t/t5555-http-smart-common.sh      | 10 +++++++-\n t/t5701-git-serve.sh              | 20 +++++++++++++--\n t/test-lib-functions.sh           |  8 ++++++\n version.c                         | 42 +++++++++++++++++++++++++++++++\n version.h                         | 21 ++++++++++++++++\n 9 files changed, 140 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt\nindex f1ce50f4a6..c368a893bd 100644\n--- a/Documentation/config/transfer.txt\n+++ b/Documentation/config/transfer.txt\n@@ -125,3 +125,10 @@ transfer.bundleURI::\n transfer.advertiseObjectInfo::\n \tWhen `true`, the `object-info` capability is advertised by\n \tservers. Defaults to false.\n+\n+transfer.advertiseOSVersion::\n+\tWhen `true`, the `os-version` capability is advertised by clients and\n+\tservers. It makes clients and servers send to each other a string\n+\trepresenting the operating system name, like \"Linux\" or \"Windows\".\n+\tThis string is retrieved from the `sysname` field of the struct returned\n+\tby the uname(2) system call. Defaults to true.\ndiff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\nindex 1652fef3ae..a332b55e4c 100644\n--- a/Documentation/gitprotocol-v2.txt\n+++ b/Documentation/gitprotocol-v2.txt\n@@ -190,6 +190,24 @@ printable ASCII characters except space (i.e., the byte range 32 < x <\n and debugging purposes, and MUST NOT be used to programmatically assume\n the presence or absence of particular features.\n \n+os-version\n+~~~~~~~~~~\n+\n+In the same way as the `agent` capability above, the server can\n+advertise the `os-version` capability to notify the client the\n+kind of operating system it is running on. The client may optionally\n+send its own `os-version` capability, to notify the server the kind of\n+operating system it is also running on in its request to the server\n+(but it MUST NOT do so if the server did not advertise the os-version\n+capability). The value of this capability may consist of ASCII printable\n+characters(from 33 to 126 inclusive) and are typically made from the result of\n+`uname -s`(OS name e.g Linux). The os-version capability can be disabled\n+entirely by setting the `transfer.advertiseOSVersion` config option\n+to `false`. The `os-version` strings are purely informative for\n+statistics and debugging purposes, and MUST NOT be used to\n+programmatically assume the presence or absence of particular\n+features.\n+\n ls-refs\n ~~~~~~~\n \ndiff --git a/connect.c b/connect.c\nindex 10fad43e98..6d5792b63c 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -492,6 +492,9 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_write_fmt(fd_out, \"agent=%s\", git_user_agent_sanitized());\n \n+\tif (server_supports_v2(\"os-version\") && advertise_os_version(the_repository))\n+\t\tpacket_write_fmt(fd_out, \"os-version=%s\", os_version_sanitized());\n+\n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n \t\tint hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo == GIT_HASH_UNKNOWN)\ndiff --git a/serve.c b/serve.c\nindex c8694e3751..5b0d54ae9a 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -31,6 +31,16 @@ static int agent_advertise(struct repository *r UNUSED,\n \treturn 1;\n }\n \n+static int os_version_advertise(struct repository *r,\n+\t\t\t   struct strbuf *value)\n+{\n+\tif (!advertise_os_version(r))\n+\t\treturn 0;\n+\tif (value)\n+\t\tstrbuf_addstr(value, os_version_sanitized());\n+\treturn 1;\n+}\n+\n static int object_format_advertise(struct repository *r,\n \t\t\t\t   struct strbuf *value)\n {\n@@ -123,6 +133,10 @@ static struct protocol_capability capabilities[] = {\n \t\t.name = \"agent\",\n \t\t.advertise = agent_advertise,\n \t},\n+\t{\n+\t\t.name = \"os-version\",\n+\t\t.advertise = os_version_advertise,\n+\t},\n \t{\n \t\t.name = \"ls-refs\",\n \t\t.advertise = ls_refs_advertise,\ndiff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh\nindex e47ea1ad10..6f357a005a 100755\n--- a/t/t5555-http-smart-common.sh\n+++ b/t/t5555-http-smart-common.sh\n@@ -123,9 +123,17 @@ test_expect_success 'git receive-pack --advertise-refs: v1' '\n '\n \n test_expect_success 'git upload-pack --advertise-refs: v2' '\n+\tprintf \"agent=FAKE\" >agent_and_osversion &&\n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tgit config transfer.advertiseOSVersion false\n+\telse\n+\t\tprintf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_osversion\n+\tfi &&\n+\n \tcat >expect <<-EOF &&\n \tversion 2\n-\tagent=FAKE\n+\t$(cat agent_and_osversion)\n \tls-refs=unborn\n \tfetch=shallow wait-for-done\n \tserver-option\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 0c0a5b2aec..8a783b3924 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -26,10 +26,26 @@ test_expect_success 'setup to generate files with expected content' '\n \tcat >expect.trailer <<-EOF &&\n \t0000\n \tEOF\n+\n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tgit config transfer.advertiseOSVersion false\n+\telse\n+\t\tprintf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_osversion\n+\tfi &&\n+\n+\tcat >expect_osversion.base <<-EOF\n+\tversion 2\n+\t$(cat agent_and_osversion)\n+\tls-refs=unborn\n+\tfetch=shallow wait-for-done\n+\tserver-option\n+\tobject-format=$(test_oid algo)\n+\tEOF\n '\n \n test_expect_success 'test capability advertisement' '\n-\tcat expect.base expect.trailer >expect &&\n+\tcat expect_osversion.base expect.trailer >expect &&\n \n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n \t\t--advertise-capabilities >out &&\n@@ -357,7 +373,7 @@ test_expect_success 'test capability advertisement with uploadpack.advertiseBund\n \tcat >expect.extra <<-EOF &&\n \tbundle-uri\n \tEOF\n-\tcat expect.base \\\n+\tcat expect_osversion.base \\\n \t    expect.extra \\\n \t    expect.trailer >expect &&\n \ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 78e054ab50..f7ff38521c 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -2007,3 +2007,11 @@ test_trailing_hash () {\n \t\ttest-tool hexdump |\n \t\tsed \"s/ //g\"\n }\n+\n+# Trim and replace each character with ascii code below 32 or above\n+# 127 (included) using a dot '.' character.\n+# Octal intervals \\001-\\040 and \\177-\\377\n+# corresponds to decimal intervals 1-32 and 127-255\n+test_redact_non_printables () {\n+    tr -d \"\\n\\r\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n+}\ndiff --git a/version.c b/version.c\nindex 46835ec83f..ea334c3e9c 100644\n--- a/version.c\n+++ b/version.c\n@@ -3,6 +3,7 @@\n #include \"version-def.h\"\n #include \"strbuf.h\"\n #include \"gettext.h\"\n+#include \"config.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -69,3 +70,44 @@ int get_uname_info(struct strbuf *buf, unsigned int full)\n \t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n \treturn 0;\n }\n+\n+const char *os_version(void)\n+{\n+\tstatic const char *os = NULL;\n+\n+\tif (!os) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tget_uname_info(&buf, 0);\n+\t\tos = strbuf_detach(&buf, NULL);\n+\t}\n+\n+\treturn os;\n+}\n+\n+const char *os_version_sanitized(void)\n+{\n+\tstatic const char *os_sanitized = NULL;\n+\n+\tif (!os_sanitized) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tstrbuf_addstr(&buf, os_version());\n+\t\tredact_non_printables(&buf);\n+\t\tos_sanitized = strbuf_detach(&buf, NULL);\n+\t}\n+\n+\treturn os_sanitized;\n+}\n+\n+int advertise_os_version(struct repository *r)\n+{\n+\tstatic int transfer_advertise_os_version = -1;\n+\n+\tif (transfer_advertise_os_version == -1) {\n+\t\trepo_config_get_bool(r, \"transfer.advertiseosversion\", &transfer_advertise_os_version);\n+\t\t/* enabled by default */\n+\t\ttransfer_advertise_os_version = !!transfer_advertise_os_version;\n+\t}\n+\treturn transfer_advertise_os_version;\n+}\ndiff --git a/version.h b/version.h\nindex 5eb586c0bd..3e983bc623 100644\n--- a/version.h\n+++ b/version.h\n@@ -1,6 +1,8 @@\n #ifndef VERSION_H\n #define VERSION_H\n \n+struct repository;\n+\n extern const char git_version_string[];\n extern const char git_built_from_commit_string[];\n \n@@ -14,4 +16,23 @@ const char *git_user_agent_sanitized(void);\n */\n int get_uname_info(struct strbuf *buf, unsigned int full);\n \n+/*\n+  Retrieve and cache system information for subsequent calls.\n+  Return a pointer to the cached system information string.\n+*/\n+const char *os_version(void);\n+\n+/*\n+  Retrieve system information string from os_version(). Then\n+  sanitize and cache it. Return a pointer to the sanitized\n+  system information string.\n+*/\n+const char *os_version_sanitized(void);\n+\n+/*\n+  Retrieve and cache whether os-version capability is enabled.\n+  Return 1 if enabled, 0 if disabled.\n+*/\n+int advertise_os_version(struct repository *r);\n+\n #endif /* VERSION_H */\n-- \n2.48.0\n\n"},{"id":"510792","messageId":"20250117104639.65608-7-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250117104639.65608-1-usmanakinyemi202@gmail.com","subject":"[PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-17T10:46:18Z","receivedAt":"2025-01-17T10:47:10Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently by default, the new `os-version` capability only exchange the\noperating system name between servers and clients i.e \"Linux\" or\n\"Windows\".\n\nLet's introduce a new configuration option, `osversion.command`, to handle\nthe string exchange between servers and clients. This option allows\ncustomization of the exchanged string by leveraging the output of the\nspecified command. This customization might be especially useful on some\nquite uncommon platforms like NonStop where interesting OS information is\navailable from other means than uname(2).\n\nIf this new configuration option is not set, the `os-version` capability\nexchanges just the operating system name.\n\nHelped-by: Randall S. Becker <rsbecker@nexbridge.com>\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n Documentation/config/transfer.txt | 11 ++++++-\n Documentation/gitprotocol-v2.txt  | 25 ++++++++-------\n t/t5555-http-smart-common.sh      | 28 +++++++++++++++++\n t/t5701-git-serve.sh              | 29 ++++++++++++++++++\n version.c                         | 51 ++++++++++++++++++++++++++++++-\n 5 files changed, 129 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt\nindex c368a893bd..c9f38c5796 100644\n--- a/Documentation/config/transfer.txt\n+++ b/Documentation/config/transfer.txt\n@@ -131,4 +131,13 @@ transfer.advertiseOSVersion::\n \tservers. It makes clients and servers send to each other a string\n \trepresenting the operating system name, like \"Linux\" or \"Windows\".\n \tThis string is retrieved from the `sysname` field of the struct returned\n-\tby the uname(2) system call. Defaults to true.\n+\tby the uname(2) system call. If the `osVersion.command` is set, the\n+\toutput of the command specified will be the string exchanged by the clients\n+\tand the servers. Defaults to true.\n+\n+osVersion.command::\n+\tIf this variable is set, the specified command will be run and the output\n+\twill be used as the value `X` for `os-version` capability (in the form\n+\t`os-version=X`). `osVersion.command` is only used if `transfer.advertiseOSVersion`\n+\tis true. Refer to the linkgit:git-config[1] documentation to learn more about\n+\t`transfer.advertiseOSVersion` config option.\ndiff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\nindex a332b55e4c..93a2e97ec0 100644\n--- a/Documentation/gitprotocol-v2.txt\n+++ b/Documentation/gitprotocol-v2.txt\n@@ -193,20 +193,19 @@ the presence or absence of particular features.\n os-version\n ~~~~~~~~~~\n \n-In the same way as the `agent` capability above, the server can\n-advertise the `os-version` capability to notify the client the\n-kind of operating system it is running on. The client may optionally\n-send its own `os-version` capability, to notify the server the kind of\n-operating system it is also running on in its request to the server\n-(but it MUST NOT do so if the server did not advertise the os-version\n-capability). The value of this capability may consist of ASCII printable\n+In the same way as the `agent` capability above, the server can advertise\n+the `os-version` capability to notify the client the kind of operating system\n+it is running on. The client may optionally send its own `os-version` capability,\n+to notify the server the kind of operating system it is also running on in its\n+request to the server (but it MUST NOT do so if the server did not advertise the\n+os-version capability). The value of this capability may consist of ASCII printable\n characters(from 33 to 126 inclusive) and are typically made from the result of\n-`uname -s`(OS name e.g Linux). The os-version capability can be disabled\n-entirely by setting the `transfer.advertiseOSVersion` config option\n-to `false`. The `os-version` strings are purely informative for\n-statistics and debugging purposes, and MUST NOT be used to\n-programmatically assume the presence or absence of particular\n-features.\n+`uname -s`(OS name e.g Linux). If the `osVersion.command` is set, the value of this\n+capability are made from the ouput of the command specified. The os-version capability\n+can be disabled entirely by setting the `transfer.advertiseOSVersion` config option\n+to `false`. The `os-version` strings are purely informative for statistics and\n+debugging purposes, and MUST NOT be used to programmatically assume the presence or\n+absence of particular features.\n \n ls-refs\n ~~~~~~~\ndiff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh\nindex 6f357a005a..1a3df3d090 100755\n--- a/t/t5555-http-smart-common.sh\n+++ b/t/t5555-http-smart-common.sh\n@@ -150,6 +150,34 @@ test_expect_success 'git upload-pack --advertise-refs: v2' '\n \ttest_cmp actual expect\n '\n \n+test_expect_success 'git upload-pack --advertise-refs: v2 with osVersion.command config set' '\n+\ttest_config osVersion.command \"uname -srvm\" &&\n+\tprintf \"agent=FAKE\" >agent_and_long_osversion &&\n+\n+\tif test_have_prereq !WINDOWS\n+\tthen\n+\t\tprintf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_osversion\n+\tfi &&\n+\n+\tcat >expect <<-EOF &&\n+\tversion 2\n+\t$(cat agent_and_long_osversion)\n+\tls-refs=unborn\n+\tfetch=shallow wait-for-done\n+\tserver-option\n+\tobject-format=$(test_oid algo)\n+\t0000\n+\tEOF\n+\n+\tGIT_PROTOCOL=version=2 \\\n+\tGIT_USER_AGENT=FAKE \\\n+\tgit upload-pack --advertise-refs . >out 2>err &&\n+\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_must_be_empty err &&\n+\ttest_cmp actual expect\n+'\n+\n test_expect_success 'git receive-pack --advertise-refs: v2' '\n \t# There is no v2 yet for receive-pack, implicit v0\n \tcat >expect <<-EOF &&\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 8a783b3924..1395ac4eba 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -53,6 +53,35 @@ test_expect_success 'test capability advertisement' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'test capability advertisement with osVersion.command config set' '\n+\ttest_config osVersion.command \"uname -srvm\" &&\n+\tprintf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_long_osversion &&\n+\n+\tif test_have_prereq !WINDOWS\n+\tthen\n+\t\tprintf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_osversion\n+\tfi &&\n+\n+\ttest_oid_cache <<-EOF &&\n+\twrong_algo sha1:sha256\n+\twrong_algo sha256:sha1\n+\tEOF\n+\tcat >expect_long.base <<-EOF &&\n+\tversion 2\n+\t$(cat agent_and_long_osversion)\n+\tls-refs=unborn\n+\tfetch=shallow wait-for-done\n+\tserver-option\n+\tobject-format=$(test_oid algo)\n+\tEOF\n+\tcat expect_long.base expect.trailer >expect &&\n+\n+\tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n+\t\t--advertise-capabilities >out &&\n+\ttest-tool pkt-line unpack <out >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'stateless-rpc flag does not list capabilities' '\n \t# Empty request\n \ttest-tool pkt-line pack >in <<-EOF &&\ndiff --git a/version.c b/version.c\nindex ea334c3e9c..2aa55e56b5 100644\n--- a/version.c\n+++ b/version.c\n@@ -1,9 +1,13 @@\n+#define USE_THE_REPOSITORY_VARIABLE\n+\n #include \"git-compat-util.h\"\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n #include \"gettext.h\"\n #include \"config.h\"\n+#include \"run-command.h\"\n+#include \"alias.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -71,6 +75,50 @@ int get_uname_info(struct strbuf *buf, unsigned int full)\n \treturn 0;\n }\n \n+/*\n+ * Return -1 if unable to retrieve the osversion.command config or\n+ * if the command is malformed; otherwise, return 0 if successful.\n+ */\n+static int fill_os_version_command(struct child_process *cmd)\n+{\n+\tconst char *os_version_command;\n+\tconst char **argv;\n+\tchar *os_version_copy;\n+\tint n;\n+\n+\tif (git_config_get_string_tmp(\"osversion.command\", &os_version_command))\n+\t\treturn -1;\n+\n+\tos_version_copy = xstrdup(os_version_command);\n+\tn = split_cmdline(os_version_copy, &argv);\n+\n+\tif (n < 0) {\n+\t\twarning(_(\"malformed osVersion.command config option: %s\"),\n+\t\t\t_(split_cmdline_strerror(n)));\n+\t\tfree(os_version_copy);\n+\t\treturn -1;\n+\t}\n+\n+\tfor (int i = 0; i < n; i++)\n+\t\tstrvec_push(&cmd->args, argv[i]);\n+\tfree(os_version_copy);\n+\tfree(argv);\n+\n+\treturn 0;\n+}\n+\n+static int capture_os_version(struct strbuf *buf)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\n+\tif (fill_os_version_command(&cmd))\n+\t\treturn -1;\n+\tif (capture_command(&cmd, buf, 0))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n const char *os_version(void)\n {\n \tstatic const char *os = NULL;\n@@ -78,7 +126,8 @@ const char *os_version(void)\n \tif (!os) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n-\t\tget_uname_info(&buf, 0);\n+\t\tif (capture_os_version(&buf))\n+\t\t\tget_uname_info(&buf, 0);\n \t\tos = strbuf_detach(&buf, NULL);\n \t}\n \n-- \n2.48.0\n\n"},{"id":"510820","messageId":"xmqqfrlhl2jv.fsf@gitster.g","threadId":"62747","inReplyTo":"20250117104639.65608-2-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v2 1/6] version: refactor redact_non_printables()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-17T18:26:28Z","receivedAt":"2025-01-17T18:26:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> The git_user_agent_sanitized() function performs some sanitizing to\n> avoid special characters being sent over the line and possibly messing\n> up with the protocol or with the parsing on the other side.\n>\n> Let's extract this sanitizing into a new redact_non_printables() function,\n> as we will want to reuse it in a following patch.\n>\n> For now the new redact_non_printables() function is still static as\n> it's only needed locally.\n>\n> While at it, let's use strbuf_detach() to explicitly detach the string\n> contained by the 'buf' strbuf.\n>\n> Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> ---\n>  version.c | 22 ++++++++++++++++------\n>  1 file changed, 16 insertions(+), 6 deletions(-)\n>\n> diff --git a/version.c b/version.c\n> index 4d763ab48d..78f025c808 100644\n> --- a/version.c\n> +++ b/version.c\n> @@ -6,6 +6,20 @@\n>  const char git_version_string[] = GIT_VERSION;\n>  const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n>  \n> +/*\n> + * Trim and replace each character with ascii code below 32 or above\n> + * 127 (included) using a dot '.' character.\n\n/*\n * Trim and replace each byte outside ASCII printable\n * (33 to 127, inclusive) with a dot '.'.\n */\n\nperhaps?\n\n> + * TODO: ensure consecutive non-printable characters are only replaced once\n\nI am not sure what your plans are for this change.  Has the list\nreached the consensus to squish consecutive redaction dots into one\nin the user-agent string?  If not, let's not mention it.  Making an\nincompatible change to the user-agent string is not the primary aim\nof this topic anyway.\n\n> +*/\n\nFunny indentation.  The asterisk should have a SP before it, just\nlike on the previous lines.\n\n> +static void redact_non_printables(struct strbuf *buf)\n> +{\n> +\tstrbuf_trim(buf);\n> +\tfor (size_t i = 0; i < buf->len; i++) {\n> +\t\tif (buf->buf[i] <= 32 || buf->buf[i] >= 127)\n\n<sane-ctype.h> defines isprint() we can use here.\n\n> +\t\t\tbuf->buf[i] = '.';\n> +\t}\n> +}\n\nDo we want to do anything special when the resulting buf->buf[]\nbecomes empty or just full of dots without anything else?  Should\nthe caller be told about such a condition, or is it callers'\nresponsibility to check if they care?  I am inclined to say that it\nis the latter.\n\n> @@ -27,12 +41,8 @@ const char *git_user_agent_sanitized(void)\n>  \t\tstruct strbuf buf = STRBUF_INIT;\n>  \n>  \t\tstrbuf_addstr(&buf, git_user_agent());\n> -\t\tstrbuf_trim(&buf);\n> -\t\tfor (size_t i = 0; i < buf.len; i++) {\n> -\t\t\tif (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n> -\t\t\t\tbuf.buf[i] = '.';\n> -\t\t}\n> -\t\tagent = buf.buf;\n> +\t\tredact_non_printables(&buf);\n> +\t\tagent = strbuf_detach(&buf, NULL);\n>  \t}\n>  \n>  \treturn agent;\n"},{"id":"510821","messageId":"xmqqbjw5l2ie.fsf@gitster.g","threadId":"62747","inReplyTo":"20250117104639.65608-4-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v2 3/6] version: extend get_uname_info() to hide system details","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-17T18:27:21Z","receivedAt":"2025-01-17T18:27:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Currently, get_uname_info() function provides the full OS information.\n> In a follwing commit, we will need it to provide only the OS name.\n>\n> Let's extend it to accept a \"full\" flag that makes it switch between\n> providing full OS information and providing only the OS name.\n>\n> We may need to refactor this function in the future if an\n> `osVersion.format` is added.\n>\n> Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> ---\n\nNice that this is made into a separate commit from the previous step.\n"},{"id":"510822","messageId":"xmqq4j1xkzir.fsf@gitster.g","threadId":"62747","inReplyTo":"20250117104639.65608-5-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v2 4/6] t5701: add setup test to remove side-effect dependency","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-17T19:31:56Z","receivedAt":"2025-01-17T19:32:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> -test_expect_success 'test capability advertisement' '\n> +test_expect_success 'setup to generate files with expected content' '\n> +\tprintf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_osversion &&\n\nIs this required to be \"printf\" and not \"echo\", if so why?\n\n\"git version\" could contain any character if the builder gives a\ncustom version string by saving it in the \"version\" file (we use the\nmechanism when we create a distribution tarball, for example).  What\nhappens if it contains say \"%s\" or something?\n\nIf you _really_ need to use printf, you'd want to do so more like:\n\n\tprintf \"agent=git/%s\" \"$(git version | cut ...)\"\n\nIs it required that agent_and_osversion lack the terminating LF?\nThe use of printf without terminating \"\\n\" at the end of the format\nstring hints the readers that it is the case.  If you did not intend\nthat, perhaps doing\n\n\tprintf \"agent=git/%s\\n\" \"$(git version | cut ...)\"\n\nwould avoid misleading them.\n"},{"id":"510823","messageId":"xmqqy0z9jksb.fsf@gitster.g","threadId":"62747","inReplyTo":"20250117104639.65608-6-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v2 5/6] connect: advertise OS version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-17T19:35:32Z","receivedAt":"2025-01-17T19:35:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> +os-version\n> +~~~~~~~~~~\n> +\n> ...\n> +characters(from 33 to 126 inclusive) and are typically made from the result of\n\nCompared to the preceding few paragraphs, this paragraph is overly\nwide (the previous iteration was much better).\n\nI'll review this step separately later.\n\n"},{"id":"510825","messageId":"xmqqplkljk7c.fsf@gitster.g","threadId":"62747","inReplyTo":"xmqqfrlhl2jv.fsf@gitster.g","subject":"Re: [PATCH v2 1/6] version: refactor redact_non_printables()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-17T19:48:07Z","receivedAt":"2025-01-17T19:48:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> /*\n>  * Trim and replace each byte outside ASCII printable\n>  * (33 to 127, inclusive) with a dot '.'.\n>  */\n>\n> perhaps?\n\n\"127\" -> \"126\"; that is what an inclusive range should say.\n\nSorry for a noise.\n"},{"id":"510838","messageId":"CAPig+cR6oCLw5h78NTrxDztTCLn4eseidk7wBc3JgVzKEE3+zA@mail.gmail.com","threadId":"62747","inReplyTo":"20250117104639.65608-7-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-01-17T21:44:01Z","receivedAt":"2025-01-17T21:44:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Jan 17, 2025 at 5:47 AM Usman Akinyemi\n<usmanakinyemi202@gmail.com> wrote:\n> Currently by default, the new `os-version` capability only exchange the\n> operating system name between servers and clients i.e \"Linux\" or\n> \"Windows\".\n>\n> Let's introduce a new configuration option, `osversion.command`, to handle\n> the string exchange between servers and clients. This option allows\n> customization of the exchanged string by leveraging the output of the\n> specified command. This customization might be especially useful on some\n> quite uncommon platforms like NonStop where interesting OS information is\n> available from other means than uname(2).\n>\n> If this new configuration option is not set, the `os-version` capability\n> exchanges just the operating system name.\n>\n> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> ---\n> diff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh\n> @@ -150,6 +150,34 @@ test_expect_success 'git upload-pack --advertise-refs: v2' '\n> +test_expect_success 'git upload-pack --advertise-refs: v2 with osVersion.command config set' '\n> +       test_config osVersion.command \"uname -srvm\" &&\n> +       printf \"agent=FAKE\" >agent_and_long_osversion &&\n> +\n> +       if test_have_prereq !WINDOWS\n> +       then\n> +               printf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_osversion\n> +       fi &&\n\nAs an aid to future readers, please add an explanation either in the\ncommit message or as a comment here in the code explaining why Windows\nis being singled out as special.\n\n> diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n> @@ -53,6 +53,35 @@ test_expect_success 'test capability advertisement' '\n> +test_expect_success 'test capability advertisement with osVersion.command config set' '\n> +       test_config osVersion.command \"uname -srvm\" &&\n> +       printf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_long_osversion &&\n> +\n> +       if test_have_prereq !WINDOWS\n> +       then\n> +               printf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_osversion\n> +       fi &&\n\nDitto.\n"},{"id":"510849","messageId":"xmqq4j1xjd2m.fsf@gitster.g","threadId":"62747","inReplyTo":"20250117104639.65608-6-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v2 5/6] connect: advertise OS version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-17T22:22:09Z","receivedAt":"2025-01-17T22:22:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> As some issues that can happen with a Git client can be operating system\n> specific, it can be useful for a server to know which OS a client is\n> using. In the same way it can be useful for a client to know which OS\n> a server is using.\n\nHmph.  The other end may be running different version of Git, and\nthe version difference of _our_ software is probably more relevant.\nFor that matter, they may even be running something entirely\ndifferent from our software, like Gerrit.  So I am not sure I am\nconvinced that os-version thing is a good thing to have with that\nparagraph.\n\n> Let's introduce a new protocol (`os-version`) allowing Git clients and\n> servers to exchange operating system information. The protocol is\n> controlled by the new `transfer.advertiseOSVersion` config option.\n\nThe last sentence is redundant and can safely removed.  The next\nparagraph describes it better than \"is controlled by\".\n\n> Add the `transfer.advertiseOSVersion` config option to address\n> privacy concerns. It defaults to `true` and can be changed to\n> `false`. When enabled, this option makes clients and servers send each\n> other the OS name (e.g., \"Linux\" or \"Windows\"). The information is\n> retrieved using the 'sysname' field of the `uname(2)` system call.\n\nAdd \"or its equivalent\" at the end.\n\nmacOS may have one, but it probably is not quite correct to say that\nWindows have uname system call (otherwise we wouldn't be emulating\nit on top of GetVersion ourselves).\n\n> However, there are differences between `uname(1)` (command-line utility)\n> and `uname(2)` (system call) outputs on Windows. These discrepancies\n> complicate testing on Windows platforms. For example:\n>   - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n>   .2024-02-14.20:17.UTC.x86_64\n>   - `uname(2)` output: Windows.10.0.20348\n>\n> On Windows, uname(2) is not actually system-supplied but is instead\n> already faked up by Git itself. We could have overcome the test issue\n> on Windows by implementing a new `uname` subcommand in `test-tool`\n> using uname(2), but except uname(2), which would be tested against\n> itself, there would be nothing platform specific, so it's just simpler\n> to disable the tests on Windows.\n\nOK.\n\n> +transfer.advertiseOSVersion::\n> +\tWhen `true`, the `os-version` capability is advertised by clients and\n> +\tservers. It makes clients and servers send to each other a string\n> +\trepresenting the operating system name, like \"Linux\" or \"Windows\".\n> +\tThis string is retrieved from the `sysname` field of the struct returned\n> +\tby the uname(2) system call. Defaults to true.\n\nPresumably, both ends of the connection independently choose whether\nthey enable or disable this variable, so we have 2x2=4 combinations\n(here, versions of Git before the os-version capability support is\nintroduced behave the same way as an installation with this\nconfiguration variable set to false).\n\nAnd among these four combinations, only one of them results in \"send\nto each other\", but the description above is fuzzy.\n\n> diff --git a/connect.c b/connect.c\n> index 10fad43e98..6d5792b63c 100644\n> --- a/connect.c\n> +++ b/connect.c\n> @@ -492,6 +492,9 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)\n>  \tif (server_supports_v2(\"agent\"))\n>  \t\tpacket_write_fmt(fd_out, \"agent=%s\", git_user_agent_sanitized());\n>  \n> +\tif (server_supports_v2(\"os-version\") && advertise_os_version(the_repository))\n> +\t\tpacket_write_fmt(fd_out, \"os-version=%s\", os_version_sanitized());\n\nNot a new problem, because the new code is pretty-much a straight\ncopy from the existing \"agent\" code, but do we ever use unsanitized\nversions of git-user-agent and os-version?  If not, I am wondering\nif we should sanitize immediately when we obtain the raw string and\nkeep it, get rid of _santized() function from the public API, and\nmake anybody calling git_user_agent() and os_version() to get\nsanitized safe-to-use strings.\n\nI see http.c throws git_user_agent() without doing any sanitization\nat the cURL library, but it may be a mistake that we may want to fix\n(outside the scope of this topic).  Since the contrast between the\nos_version() vs the os_version_sanitized() is *new* in this series,\nhowever, we probably would want to get it right from the beginning.\n\nSo the question is again, do we ever need to use os_version() that\nis a raw string that may require sanitizing?  I do not think of any\noffhand.\n"},{"id":"510850","messageId":"xmqqwmethxyq.fsf@gitster.g","threadId":"62747","inReplyTo":"20250117104639.65608-7-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-17T22:33:49Z","receivedAt":"2025-01-17T22:33:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Let's introduce a new configuration option, `osversion.command`, to handle\n> the string exchange between servers and clients. This option allows\n> customization of the exchanged string by leveraging the output of the\n> specified command. This customization might be especially useful on some\n> quite uncommon platforms like NonStop where interesting OS information is\n> available from other means than uname(2).\n\nAfter reading the above rationale, I doubt the usefulness of this\nfeature even more.\n\nShouldn't that kind of anomalies be handled by compat/ layer to make\ntheir uname(2) emulated, or allow get_uname_info() to be customized\nat compile time by platform implementations, to yield more useful\npieces of information instead?\n\nThat way, we do not need to add another mechanism that lets people\nspawn an arbitrary command while Git is running, we do not need to\nworry about security implications, and we do not need to worry about\npeople abusing the facility to throw totally random and useless\ngarbage information at the other end to make their stats useless.\n\nI'll skip the overly wide documentation changes.\n\n> diff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt\n> ...\n> diff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\n> ...\n\n> +test_expect_success 'test capability advertisement with osVersion.command config set' '\n> +\ttest_config osVersion.command \"uname -srvm\" &&\n\nIf osversion.command configuration variable turns out to be\nacceptable addition, I do not think we want to use \"uname -srvm\" as\nits value for its test.  Do you know for sure how portable srvm is?\n\nIf you use something like \"printf ' \\001a\\011b\\015\\012c '\", you do\nnot even have to worry about how portable srvm is and on top, you\ncan test your unprintable-redacting logic in the code.\n\nBut all of that may be moot, if we take the \"fewer customization at\nruntime\" approach.\n\nThanks.\n"},{"id":"510853","messageId":"00bb01db6931$d7cd6dc0$87684940$@nexbridge.com","threadId":"62747","inReplyTo":"xmqq4j1xjd2m.fsf@gitster.g","subject":"RE: [PATCH v2 5/6] connect: advertise OS version","fromName":"","fromEmail":"rsbecker@nexbridge.com","sentAt":"2025-01-17T22:47:46Z","receivedAt":"2025-01-17T22:48:55Z","isPatch":true,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On January 17, 2025 5:22 PM, Junio C Hamano wrote:\n>Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n>> As some issues that can happen with a Git client can be operating\n>> system specific, it can be useful for a server to know which OS a\n>> client is using. In the same way it can be useful for a client to know\n>> which OS a server is using.\n>\n>Hmph.  The other end may be running different version of Git, and the\nversion\n>difference of _our_ software is probably more relevant.\n>For that matter, they may even be running something entirely different from\nour\n>software, like Gerrit.  So I am not sure I am convinced that os-version\nthing is a good\n>thing to have with that paragraph.\n>\n>> Let's introduce a new protocol (`os-version`) allowing Git clients and\n>> servers to exchange operating system information. The protocol is\n>> controlled by the new `transfer.advertiseOSVersion` config option.\n>\n>The last sentence is redundant and can safely removed.  The next paragraph\n>describes it better than \"is controlled by\".\n>\n>> Add the `transfer.advertiseOSVersion` config option to address privacy\n>> concerns. It defaults to `true` and can be changed to `false`. When\n>> enabled, this option makes clients and servers send each other the OS\n>> name (e.g., \"Linux\" or \"Windows\"). The information is retrieved using\n>> the 'sysname' field of the `uname(2)` system call.\n>\n>Add \"or its equivalent\" at the end.\n\n>macOS may have one, but it probably is not quite correct to say that\nWindows have\n>uname system call (otherwise we wouldn't be emulating it on top of\nGetVersion\n>ourselves).\n>\n>> However, there are differences between `uname(1)` (command-line\n>> utility) and `uname(2)` (system call) outputs on Windows. These\n>> discrepancies complicate testing on Windows platforms. For example:\n>>   - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n>>   .2024-02-14.20:17.UTC.x86_64\n>>   - `uname(2)` output: Windows.10.0.20348\n>>\n>> On Windows, uname(2) is not actually system-supplied but is instead\n>> already faked up by Git itself. We could have overcome the test issue\n>> on Windows by implementing a new `uname` subcommand in `test-tool`\n>> using uname(2), but except uname(2), which would be tested against\n>> itself, there would be nothing platform specific, so it's just simpler\n>> to disable the tests on Windows.\n>\n>OK.\n>\n>> +transfer.advertiseOSVersion::\n>> +\tWhen `true`, the `os-version` capability is advertised by clients\nand\n>> +\tservers. It makes clients and servers send to each other a string\n>> +\trepresenting the operating system name, like \"Linux\" or \"Windows\".\n>> +\tThis string is retrieved from the `sysname` field of the struct\nreturned\n>> +\tby the uname(2) system call. Defaults to true.\n>\n>Presumably, both ends of the connection independently choose whether they\n>enable or disable this variable, so we have 2x2=4 combinations (here,\nversions of\n>Git before the os-version capability support is introduced behave the same\nway as\n>an installation with this configuration variable set to false).\n>\n>And among these four combinations, only one of them results in \"send to\neach\n>other\", but the description above is fuzzy.\n>\n>> diff --git a/connect.c b/connect.c\n>> index 10fad43e98..6d5792b63c 100644\n>> --- a/connect.c\n>> +++ b/connect.c\n>> @@ -492,6 +492,9 @@ static void send_capabilities(int fd_out, struct\n>packet_reader *reader)\n>>  \tif (server_supports_v2(\"agent\"))\n>>  \t\tpacket_write_fmt(fd_out, \"agent=%s\",\ngit_user_agent_sanitized());\n>>\n>> +\tif (server_supports_v2(\"os-version\") &&\n>advertise_os_version(the_repository))\n>> +\t\tpacket_write_fmt(fd_out, \"os-version=%s\",\n>os_version_sanitized());\n>\n>Not a new problem, because the new code is pretty-much a straight copy from\nthe\n>existing \"agent\" code, but do we ever use unsanitized versions of\ngit-user-agent and\n>os-version?  If not, I am wondering if we should sanitize immediately when\nwe\n>obtain the raw string and keep it, get rid of _santized() function from the\npublic API,\n>and make anybody calling git_user_agent() and os_version() to get sanitized\nsafe-\n>to-use strings.\n>\n>I see http.c throws git_user_agent() without doing any sanitization at the\ncURL\n>library, but it may be a mistake that we may want to fix (outside the scope\nof this\n>topic).  Since the contrast between the\n>os_version() vs the os_version_sanitized() is *new* in this series,\nhowever, we\n>probably would want to get it right from the beginning.\n>\n>So the question is again, do we ever need to use os_version() that is a raw\nstring\n>that may require sanitizing?  I do not think of any offhand.\n\nuname(2) is definitely not portable. uname(1) is almost always available,\nbut\nthere is no guarantee about uname(2). I am not entirely happy having my\nbuilds break if having to write one between rc0 and rc1 when this rolls. How\nis this being handled? os_version() is also not portable. What if we had\nsomething that asked for specific elements of the string, by name or id.\n\n\n"},{"id":"510854","messageId":"00bc01db6932$1eed21a0$5cc764e0$@nexbridge.com","threadId":"62747","inReplyTo":"xmqqwmethxyq.fsf@gitster.g","subject":"RE: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"","fromEmail":"rsbecker@nexbridge.com","sentAt":"2025-01-17T22:49:46Z","receivedAt":"2025-01-17T22:50:02Z","isPatch":true,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On January 17, 2025 5:34 PM, Junio C Hamano wrote:\n>Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n>> Let's introduce a new configuration option, `osversion.command`, to\n>> handle the string exchange between servers and clients. This option\n>> allows customization of the exchanged string by leveraging the output\n>> of the specified command. This customization might be especially\n>> useful on some quite uncommon platforms like NonStop where interesting\n>> OS information is available from other means than uname(2).\n>\n>After reading the above rationale, I doubt the usefulness of this feature\neven more.\n>\n>Shouldn't that kind of anomalies be handled by compat/ layer to make their\n>uname(2) emulated, or allow get_uname_info() to be customized at compile\ntime\n>by platform implementations, to yield more useful pieces of information\ninstead?\n>\n>That way, we do not need to add another mechanism that lets people spawn an\n>arbitrary command while Git is running, we do not need to worry about\nsecurity\n>implications, and we do not need to worry about people abusing the facility\nto\n>throw totally random and useless garbage information at the other end to\nmake\n>their stats useless.\n>\n>I'll skip the overly wide documentation changes.\n>\n>> diff --git a/Documentation/config/transfer.txt\n>> b/Documentation/config/transfer.txt\n>> ...\n>> diff --git a/Documentation/gitprotocol-v2.txt\n>> b/Documentation/gitprotocol-v2.txt\n>> ...\n>\n>> +test_expect_success 'test capability advertisement with\nosVersion.command\n>config set' '\n>> +\ttest_config osVersion.command \"uname -srvm\" &&\n>\n>If osversion.command configuration variable turns out to be acceptable\naddition, I\n>do not think we want to use \"uname -srvm\" as its value for its test.  Do\nyou know\n>for sure how portable srvm is?\n>\n>If you use something like \"printf ' \\001a\\011b\\015\\012c '\", you do not even\nhave\n>to worry about how portable srvm is and on top, you can test your\nunprintable-\n>redacting logic in the code.\n>\n>But all of that may be moot, if we take the \"fewer customization at\nruntime\"\n>approach.\n\nOn my box, uname -srvm = \"NONSTOP_KERNEL L24 08 NSV-D\". Is this going to\nBreak anything?\n\n"},{"id":"510855","messageId":"xmqqjzathwkc.fsf@gitster.g","threadId":"62747","inReplyTo":"00bb01db6931$d7cd6dc0$87684940$@nexbridge.com","subject":"Re: [PATCH v2 5/6] connect: advertise OS version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-17T23:04:03Z","receivedAt":"2025-01-17T23:04:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"<rsbecker@nexbridge.com> writes:\n\n>>So the question is again, do we ever need to use os_version() that is a raw\n> string\n>>that may require sanitizing?  I do not think of any offhand.\n>\n> uname(2) is definitely not portable. uname(1) is almost always available,\n> but\n> there is no guarantee about uname(2). I am not entirely happy having my\n> builds break if having to write one between rc0 and rc1 when this rolls. How\n> is this being handled? os_version() is also not portable. What if we had\n> something that asked for specific elements of the string, by name or id.\n\nSorry, I fail to see anything in your paragraph that is relevant to\nwhat I said.  Especially os_version() is a function that is\nimplemented in the patchset, not something you would complain about\nbeing \"not portable\".\n"},{"id":"510856","messageId":"xmqqfrlhhwga.fsf@gitster.g","threadId":"62747","inReplyTo":"00bc01db6932$1eed21a0$5cc764e0$@nexbridge.com","subject":"Re: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-17T23:06:29Z","receivedAt":"2025-01-17T23:06:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"<rsbecker@nexbridge.com> writes:\n\n> On my box, uname -srvm = \"NONSTOP_KERNEL L24 08 NSV-D\". Is this going to\n> Break anything?\n\nIf you are happy with that string, then there is no need for\nosversion.command configuration variable, is there?\n\n"},{"id":"510857","messageId":"00bd01db6936$28fa89e0$7aef9da0$@nexbridge.com","threadId":"62747","inReplyTo":"xmqqfrlhhwga.fsf@gitster.g","subject":"RE: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"","fromEmail":"rsbecker@nexbridge.com","sentAt":"2025-01-17T23:18:41Z","receivedAt":"2025-01-17T23:18:58Z","isPatch":true,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On January 17, 2025 6:06 PM, Junio C Hamano wrote:\n><rsbecker@nexbridge.com> writes:\n>\n>> On my box, uname -srvm = \"NONSTOP_KERNEL L24 08 NSV-D\". Is this going\n>> to Break anything?\n>\n>If you are happy with that string, then there is no need for\nosversion.command\n>configuration variable, is there?\n\nI am fine with that string. If that's what will work by default, it should\nbe fine.\n\nSorry about my confusion.\n\n"},{"id":"510968","messageId":"CAPSxiM-NPobarwmeRA+Z1L1DCLMEJy=1REobt3tyCKKFZOO_gw@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqfrlhl2jv.fsf@gitster.g","subject":"Re: [PATCH v2 1/6] version: refactor redact_non_printables()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-20T17:10:11Z","receivedAt":"2025-01-20T17:10:23Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Fri, Jan 17, 2025 at 11:56 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > The git_user_agent_sanitized() function performs some sanitizing to\n> > avoid special characters being sent over the line and possibly messing\n> > up with the protocol or with the parsing on the other side.\n> >\n> > Let's extract this sanitizing into a new redact_non_printables() function,\n> > as we will want to reuse it in a following patch.\n> >\n> > For now the new redact_non_printables() function is still static as\n> > it's only needed locally.\n> >\n> > While at it, let's use strbuf_detach() to explicitly detach the string\n> > contained by the 'buf' strbuf.\n> >\n> > Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> > Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> > ---\n> >  version.c | 22 ++++++++++++++++------\n> >  1 file changed, 16 insertions(+), 6 deletions(-)\n> >\n> > diff --git a/version.c b/version.c\n> > index 4d763ab48d..78f025c808 100644\n> > --- a/version.c\n> > +++ b/version.c\n> > @@ -6,6 +6,20 @@\n> >  const char git_version_string[] = GIT_VERSION;\n> >  const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n> >\n> > +/*\n> > + * Trim and replace each character with ascii code below 32 or above\n> > + * 127 (included) using a dot '.' character.\n>\n> /*\n>  * Trim and replace each byte outside ASCII printable\n>  * (33 to 127, inclusive) with a dot '.'.\n>  */\n>\n> perhaps?\nThis sounds confusing, it sounds like the byte we are replacing with dot are\nin the range of 33 to 127 whereas, it is those outside these range.\n>\n> > + * TODO: ensure consecutive non-printable characters are only replaced once\n>\n> I am not sure what your plans are for this change.  Has the list\n> reached the consensus to squish consecutive redaction dots into one\n> in the user-agent string?  If not, let's not mention it.  Making an\n> incompatible change to the user-agent string is not the primary aim\n> of this topic anyway.\n>\n> > +*/\n>\n> Funny indentation.  The asterisk should have a SP before it, just\n> like on the previous lines.\nMistake, thanks for catching it, will make a change to it in the next\npatch series.\n>\n> > +static void redact_non_printables(struct strbuf *buf)\n> > +{\n> > +     strbuf_trim(buf);\n> > +     for (size_t i = 0; i < buf->len; i++) {\n> > +             if (buf->buf[i] <= 32 || buf->buf[i] >= 127)\n>\n> <sane-ctype.h> defines isprint() we can use here.\nI think it would be better to add this in another commit so that one commit\ndoes one thing. I will add it after this patch series got settled,\nwhat do you think ?\n>\n> > +                     buf->buf[i] = '.';\n> > +     }\n> > +}\n>\n> Do we want to do anything special when the resulting buf->buf[]\n> becomes empty or just full of dots without anything else?  Should\n> the caller be told about such a condition, or is it callers'\n> responsibility to check if they care?  I am inclined to say that it\n> is the latter.\nI agreed.\n>\n> > @@ -27,12 +41,8 @@ const char *git_user_agent_sanitized(void)\n> >               struct strbuf buf = STRBUF_INIT;\n> >\n> >               strbuf_addstr(&buf, git_user_agent());\n> > -             strbuf_trim(&buf);\n> > -             for (size_t i = 0; i < buf.len; i++) {\n> > -                     if (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n> > -                             buf.buf[i] = '.';\n> > -             }\n> > -             agent = buf.buf;\n> > +             redact_non_printables(&buf);\n> > +             agent = strbuf_detach(&buf, NULL);\n> >       }\n> >\n> >       return agent;\n"},{"id":"510969","messageId":"CAPSxiM9qRQ2HuTJDmhq_xeCRmn+yUvjXokwEwJE0S4av9Y-TKg@mail.gmail.com","threadId":"62747","inReplyTo":"xmqq4j1xkzir.fsf@gitster.g","subject":"Re: [PATCH v2 4/6] t5701: add setup test to remove side-effect dependency","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-20T17:32:05Z","receivedAt":"2025-01-20T17:32:17Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Sat, Jan 18, 2025 at 1:02 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > -test_expect_success 'test capability advertisement' '\n> > +test_expect_success 'setup to generate files with expected content' '\n> > +     printf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_osversion &&\n>\n> Is this required to be \"printf\" and not \"echo\", if so why?\n>\n> \"git version\" could contain any character if the builder gives a\n> custom version string by saving it in the \"version\" file (we use the\n> mechanism when we create a distribution tarball, for example).  What\n> happens if it contains say \"%s\" or something?\nThere is not any requirement to use \"printf\" here, I did not think about\nthis case before, I will change it to \"echo\"\n>\n> If you _really_ need to use printf, you'd want to do so more like:\n>\n>         printf \"agent=git/%s\" \"$(git version | cut ...)\"\n>\n> Is it required that agent_and_osversion lack the terminating LF?\n> The use of printf without terminating \"\\n\" at the end of the format\n> string hints the readers that it is the case.  If you did not intend\n> that, perhaps doing\n>\n>         printf \"agent=git/%s\\n\" \"$(git version | cut ...)\"\n>\n> would avoid misleading them.\nYeah, that is true, I could not notice this as the next commit of the\npatch series\nwas able to fix it. I will change it to \"echo\", with this, it will be better.\n\nThank you.\n"},{"id":"510971","messageId":"CAPSxiM__kNH7+itsiOHzztJZZvpw-AcR1s=ZfSm=OLv4p0POsw@mail.gmail.com","threadId":"62747","inReplyTo":"xmqq4j1xjd2m.fsf@gitster.g","subject":"Re: [PATCH v2 5/6] connect: advertise OS version","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-20T18:15:00Z","receivedAt":"2025-01-20T18:15:13Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Sat, Jan 18, 2025 at 3:52 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > As some issues that can happen with a Git client can be operating system\n> > specific, it can be useful for a server to know which OS a client is\n> > using. In the same way it can be useful for a client to know which OS\n> > a server is using.\n>\n> Hmph.  The other end may be running different version of Git, and\n> the version difference of _our_ software is probably more relevant.\n> For that matter, they may even be running something entirely\n> different from our software, like Gerrit.  So I am not sure I am\n> convinced that os-version thing is a good thing to have with that\n> paragraph.\nHi Junio,\n\nWhat could be a better way of describing this ? Also, user-agent capability is\nalready sharing the information about the version of Git.\n>\n> > Let's introduce a new protocol (`os-version`) allowing Git clients and\n> > servers to exchange operating system information. The protocol is\n> > controlled by the new `transfer.advertiseOSVersion` config option.\n>\n> The last sentence is redundant and can safely removed.  The next\n> paragraph describes it better than \"is controlled by\".\nNoted, I will do that.\n>\n> > Add the `transfer.advertiseOSVersion` config option to address\n> > privacy concerns. It defaults to `true` and can be changed to\n> > `false`. When enabled, this option makes clients and servers send each\n> > other the OS name (e.g., \"Linux\" or \"Windows\"). The information is\n> > retrieved using the 'sysname' field of the `uname(2)` system call.\n>\n> Add \"or its equivalent\" at the end.\n>\n> macOS may have one, but it probably is not quite correct to say that\n> Windows have uname system call (otherwise we wouldn't be emulating\n> it on top of GetVersion ourselves).\nYeah, noted. I will do that in the next iteration.\n>\n> > However, there are differences between `uname(1)` (command-line utility)\n> > and `uname(2)` (system call) outputs on Windows. These discrepancies\n> > complicate testing on Windows platforms. For example:\n> >   - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n> >   .2024-02-14.20:17.UTC.x86_64\n> >   - `uname(2)` output: Windows.10.0.20348\n> >\n> > On Windows, uname(2) is not actually system-supplied but is instead\n> > already faked up by Git itself. We could have overcome the test issue\n> > on Windows by implementing a new `uname` subcommand in `test-tool`\n> > using uname(2), but except uname(2), which would be tested against\n> > itself, there would be nothing platform specific, so it's just simpler\n> > to disable the tests on Windows.\n>\n> OK.\n>\n> > +transfer.advertiseOSVersion::\n> > +     When `true`, the `os-version` capability is advertised by clients and\n> > +     servers. It makes clients and servers send to each other a string\n> > +     representing the operating system name, like \"Linux\" or \"Windows\".\n> > +     This string is retrieved from the `sysname` field of the struct returned\n> > +     by the uname(2) system call. Defaults to true.\n>\n> Presumably, both ends of the connection independently choose whether\n> they enable or disable this variable, so we have 2x2=4 combinations\n> (here, versions of Git before the os-version capability support is\n> introduced behave the same way as an installation with this\n> configuration variable set to false).\n>\n> And among these four combinations, only one of them results in \"send\n> to each other\", but the description above is fuzzy.\nYeah, describing the four combinations would better right ?\n>\n> > diff --git a/connect.c b/connect.c\n> > index 10fad43e98..6d5792b63c 100644\n> > --- a/connect.c\n> > +++ b/connect.c\n> > @@ -492,6 +492,9 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)\n> >       if (server_supports_v2(\"agent\"))\n> >               packet_write_fmt(fd_out, \"agent=%s\", git_user_agent_sanitized());\n> >\n> > +     if (server_supports_v2(\"os-version\") && advertise_os_version(the_repository))\n> > +             packet_write_fmt(fd_out, \"os-version=%s\", os_version_sanitized());\n>\n> Not a new problem, because the new code is pretty-much a straight\n> copy from the existing \"agent\" code, but do we ever use unsanitized\n> versions of git-user-agent and os-version?  If not, I am wondering\n> if we should sanitize immediately when we obtain the raw string and\n> keep it, get rid of _santized() function from the public API, and\n> make anybody calling git_user_agent() and os_version() to get\n> sanitized safe-to-use strings.\n>\n> I see http.c throws git_user_agent() without doing any sanitization\n> at the cURL library, but it may be a mistake that we may want to fix\n> (outside the scope of this topic).  Since the contrast between the\n> os_version() vs the os_version_sanitized() is *new* in this series,\n> however, we probably would want to get it right from the beginning.\n>\n> So the question is again, do we ever need to use os_version() that\n> is a raw string that may require sanitizing?  I do not think of any\n> offhand.\nIn this case, I guess there has to be a conclusion on what to do.\n"},{"id":"510972","messageId":"CAPSxiM9Vm7DxDeneTvWLYaZnPK1Zpk79xfcH5US7-+R39v-i7g@mail.gmail.com","threadId":"62747","inReplyTo":"CAPig+cR6oCLw5h78NTrxDztTCLn4eseidk7wBc3JgVzKEE3+zA@mail.gmail.com","subject":"Re: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-20T18:17:28Z","receivedAt":"2025-01-20T18:17:40Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Sat, Jan 18, 2025 at 3:14 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> On Fri, Jan 17, 2025 at 5:47 AM Usman Akinyemi\n> <usmanakinyemi202@gmail.com> wrote:\n> > Currently by default, the new `os-version` capability only exchange the\n> > operating system name between servers and clients i.e \"Linux\" or\n> > \"Windows\".\n> >\n> > Let's introduce a new configuration option, `osversion.command`, to handle\n> > the string exchange between servers and clients. This option allows\n> > customization of the exchanged string by leveraging the output of the\n> > specified command. This customization might be especially useful on some\n> > quite uncommon platforms like NonStop where interesting OS information is\n> > available from other means than uname(2).\n> >\n> > If this new configuration option is not set, the `os-version` capability\n> > exchanges just the operating system name.\n> >\n> > Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> > ---\n> > diff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh\n> > @@ -150,6 +150,34 @@ test_expect_success 'git upload-pack --advertise-refs: v2' '\n> > +test_expect_success 'git upload-pack --advertise-refs: v2 with osVersion.command config set' '\n> > +       test_config osVersion.command \"uname -srvm\" &&\n> > +       printf \"agent=FAKE\" >agent_and_long_osversion &&\n> > +\n> > +       if test_have_prereq !WINDOWS\n> > +       then\n> > +               printf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_osversion\n> > +       fi &&\n>\n> As an aid to future readers, please add an explanation either in the\n> commit message or as a comment here in the code explaining why Windows\n> is being singled out as special.\n>\nHi Eric,\n\nThe previous commit which introduced this has this information,\ncan we do some form of referencing ?\n\n> > diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n> > @@ -53,6 +53,35 @@ test_expect_success 'test capability advertisement' '\n> > +test_expect_success 'test capability advertisement with osVersion.command config set' '\n> > +       test_config osVersion.command \"uname -srvm\" &&\n> > +       printf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_long_osversion &&\n> > +\n> > +       if test_have_prereq !WINDOWS\n> > +       then\n> > +               printf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_osversion\n> > +       fi &&\n>\n> Ditto.\n"},{"id":"510973","messageId":"CAPig+cSE=2jZvTUHsBnRRMizC3pxRwyud+TGDV7iw=QNmFmycQ@mail.gmail.com","threadId":"62747","inReplyTo":"CAPSxiM9Vm7DxDeneTvWLYaZnPK1Zpk79xfcH5US7-+R39v-i7g@mail.gmail.com","subject":"Re: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-01-20T18:41:17Z","receivedAt":"2025-01-20T18:41:29Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Jan 20, 2025 at 1:17 PM Usman Akinyemi\n<usmanakinyemi202@gmail.com> wrote:\n> On Sat, Jan 18, 2025 at 3:14 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n> > On Fri, Jan 17, 2025 at 5:47 AM Usman Akinyemi\n> > <usmanakinyemi202@gmail.com> wrote:\n> > > +       if test_have_prereq !WINDOWS\n> > > +       then\n> > > +               printf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_osversion\n> > > +       fi &&\n> >\n> > As an aid to future readers, please add an explanation either in the\n> > commit message or as a comment here in the code explaining why Windows\n> > is being singled out as special.\n>\n> The previous commit which introduced this has this information,\n> can we do some form of referencing ?\n\nMy main concern is that someone looking at this change in the future\n-- who did not have the benefit of reading the cover letter or the\nreview discussion -- may have a hard time understanding why Windows is\nsingled out by this patch. As long as you give some sort of\nexplanation, whether in the code or in the commit message, then you\nsave that future user from having to figure it out on his or her own.\n\nSo, your suggestion of referencing some other commit may work.\nAugmenting the commit message of this patch with something along the\nlines of:\n\n   As with the previous commit, we skip the tests on Windows.\n\nmay be enough to tell the reader where to look for the explanation.\n"},{"id":"510974","messageId":"CAPSxiM_9JKxu5QkxjHUFDM6cb5rSs400O72eYCiM8jHF9dr7Vw@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqwmethxyq.fsf@gitster.g","subject":"Re: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-20T18:58:11Z","receivedAt":"2025-01-20T18:58:22Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Sat, Jan 18, 2025 at 4:03 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > Let's introduce a new configuration option, `osversion.command`, to handle\n> > the string exchange between servers and clients. This option allows\n> > customization of the exchanged string by leveraging the output of the\n> > specified command. This customization might be especially useful on some\n> > quite uncommon platforms like NonStop where interesting OS information is\n> > available from other means than uname(2).\n>\n> After reading the above rationale, I doubt the usefulness of this\n> feature even more.\n>\n> Shouldn't that kind of anomalies be handled by compat/ layer to make\n> their uname(2) emulated, or allow get_uname_info() to be customized\n> at compile time by platform implementations, to yield more useful\n> pieces of information instead?\n>\n> That way, we do not need to add another mechanism that lets people\n> spawn an arbitrary command while Git is running, we do not need to\n> worry about security implications, and we do not need to worry about\n> people abusing the facility to throw totally random and useless\n> garbage information at the other end to make their stats useless.\nHi Junio,\n\nThanks for the review.\nThis config option was added at Randall's request.\n\nRandall wrote:\n\n\"Instead of an override, what about a knob that specifies the uname\ncommand to use to build the value. Personally, I would use `uname -s\n-r -v` on NonStop to get the kernel version used in the build. The\ndifficulty on my platform is that this is not truly useful info. The\neffective build OS compatibility version is in a #define\n__L_Series_RVU and __H_Series_RVU, so the knob might be needed in\ngit_compat_util.h or similar. This comes from the compiler arguments,\nwhich are not yet captured.\"\n\nSo, the difficulty is that the compile time information might not be useful.\n\nThis patch is the last patch of the series and can be a stand alone also.\n\nThank you.\n\n>\n> I'll skip the overly wide documentation changes.\n>\n> > diff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt\n> > ...\n> > diff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\n> > ...\n>\n> > +test_expect_success 'test capability advertisement with osVersion.command config set' '\n> > +     test_config osVersion.command \"uname -srvm\" &&\n>\n> If osversion.command configuration variable turns out to be\n> acceptable addition, I do not think we want to use \"uname -srvm\" as\n> its value for its test.  Do you know for sure how portable srvm is?\n>\n> If you use something like \"printf ' \\001a\\011b\\015\\012c '\", you do\n> not even have to worry about how portable srvm is and on top, you\n> can test your unprintable-redacting logic in the code.\n>\n> But all of that may be moot, if we take the \"fewer customization at\n> runtime\" approach.\n>\n> Thanks.\n"},{"id":"510975","messageId":"CAPSxiM9QoCy7e+R9GAoELP1i+bssPoq2ogXPnbW59-ta6wjdoQ@mail.gmail.com","threadId":"62747","inReplyTo":"CAPig+cSE=2jZvTUHsBnRRMizC3pxRwyud+TGDV7iw=QNmFmycQ@mail.gmail.com","subject":"Re: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-20T19:08:17Z","receivedAt":"2025-01-20T19:08:29Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Tue, Jan 21, 2025 at 12:11 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> On Mon, Jan 20, 2025 at 1:17 PM Usman Akinyemi\n> <usmanakinyemi202@gmail.com> wrote:\n> > On Sat, Jan 18, 2025 at 3:14 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n> > > On Fri, Jan 17, 2025 at 5:47 AM Usman Akinyemi\n> > > <usmanakinyemi202@gmail.com> wrote:\n> > > > +       if test_have_prereq !WINDOWS\n> > > > +       then\n> > > > +               printf \"\\nos-version=%s\\n\" $(uname -srvm | test_redact_non_printables) >>agent_and_long_osversion\n> > > > +       fi &&\n> > >\n> > > As an aid to future readers, please add an explanation either in the\n> > > commit message or as a comment here in the code explaining why Windows\n> > > is being singled out as special.\n> >\n> > The previous commit which introduced this has this information,\n> > can we do some form of referencing ?\n>\n> My main concern is that someone looking at this change in the future\n> -- who did not have the benefit of reading the cover letter or the\n> review discussion -- may have a hard time understanding why Windows is\n> singled out by this patch. As long as you give some sort of\n> explanation, whether in the code or in the commit message, then you\n> save that future user from having to figure it out on his or her own.\n>\n> So, your suggestion of referencing some other commit may work.\n> Augmenting the commit message of this patch with something along the\n> lines of:\n>\n>    As with the previous commit, we skip the tests on Windows.\n>\n> may be enough to tell the reader where to look for the explanation.\nYeah, thanks, this looks better and clearer. I will add this in the\nnext iteration\nif we agree to include the osversion.command config.\n\nThank you.\nUsman Akinyemi.\n"},{"id":"510976","messageId":"xmqqy0z52rg4.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM9qRQ2HuTJDmhq_xeCRmn+yUvjXokwEwJE0S4av9Y-TKg@mail.gmail.com","subject":"Re: [PATCH v2 4/6] t5701: add setup test to remove side-effect dependency","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-20T19:52:43Z","receivedAt":"2025-01-20T19:52:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Yeah, that is true, I could not notice this as the next commit of the\n> patch series\n> was able to fix it. I will change it to \"echo\", with this, it will be better.\n\nIf we want to prepare ourselves against any arbitrary garbage the\nbuilder may throw at us, using printf with _fixed_ format and feed\nthe potentially arbitrary garbage as its parameter to be\ninterpolated is the safest approach, so writing it as\n\n    printf \"agent=git/%s\\n\" \"$(git version | cut ...)\"\n\nwould signal the readers that whoever wrote it knew what they were\ndoing and was being extra careful.\n\nTHanks.\n"},{"id":"510995","messageId":"CAP8UFD3ccT=bAy=fsHaha=yNEDOuFpEsJ5tR7zQ1VJWtgNDh9Q@mail.gmail.com","threadId":"62747","inReplyTo":"CAPSxiM-NPobarwmeRA+Z1L1DCLMEJy=1REobt3tyCKKFZOO_gw@mail.gmail.com","subject":"Re: [PATCH v2 1/6] version: refactor redact_non_printables()","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-01-21T08:12:24Z","receivedAt":"2025-01-21T08:12:38Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Jan 20, 2025 at 6:10 PM Usman Akinyemi\n<usmanakinyemi202@gmail.com> wrote:\n>\n> On Fri, Jan 17, 2025 at 11:56 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> > > +static void redact_non_printables(struct strbuf *buf)\n> > > +{\n> > > +     strbuf_trim(buf);\n> > > +     for (size_t i = 0; i < buf->len; i++) {\n> > > +             if (buf->buf[i] <= 32 || buf->buf[i] >= 127)\n> >\n> > <sane-ctype.h> defines isprint() we can use here.\n> I think it would be better to add this in another commit so that one commit\n> does one thing. I will add it after this patch series got settled,\n> what do you think ?\n\nAlternatively it could be done in its own preparatory patch at the\nbeginning of this patch series.\n\n<sane-ctype.h> has:\n\n#define isprint(x) ((x) >= 0x20 && (x) <= 0x7e)\n\nSo if we wanted to use isprint() we would have to use something like:\n\n    for (size_t i = 0; i < buf->len; i++) {\n            if (!isprint(buf->buf[i]) || buf->buf[i] == ' ')\n                    buf->buf[i] = '.';\n    }\n\nIt would have been nicer if we didn't need a special case for SP. So I\nwould say it's likely a matter of taste if the result is nicer than\nthe original.\n\n> >\n> > > +                     buf->buf[i] = '.';\n> > > +     }\n> > > +}\n"},{"id":"511002","messageId":"CAPSxiM90EGU+eYN9KTpOXX4nK3Rhu605CCQd+-TzYK51svHStg@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqy0z52rg4.fsf@gitster.g","subject":"Re: [PATCH v2 4/6] t5701: add setup test to remove side-effect dependency","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-21T13:43:54Z","receivedAt":"2025-01-21T13:44:07Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Tue, Jan 21, 2025 at 1:22 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > Yeah, that is true, I could not notice this as the next commit of the\n> > patch series\n> > was able to fix it. I will change it to \"echo\", with this, it will be better.\n>\n> If we want to prepare ourselves against any arbitrary garbage the\n> builder may throw at us, using printf with _fixed_ format and feed\n> the potentially arbitrary garbage as its parameter to be\n> interpolated is the safest approach, so writing it as\n>\n>     printf \"agent=git/%s\\n\" \"$(git version | cut ...)\"\n>\n> would signal the readers that whoever wrote it knew what they were\n> doing and was being extra careful.\n>\n> THanks.\nYeah, I will add this in the next iteration.\nThanks.\n"},{"id":"511008","messageId":"xmqq5xm82gi1.fsf@gitster.g","threadId":"62747","inReplyTo":"CAP8UFD3ccT=bAy=fsHaha=yNEDOuFpEsJ5tR7zQ1VJWtgNDh9Q@mail.gmail.com","subject":"Re: [PATCH v2 1/6] version: refactor redact_non_printables()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-21T18:01:26Z","receivedAt":"2025-01-21T18:01:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Mon, Jan 20, 2025 at 6:10 PM Usman Akinyemi\n> <usmanakinyemi202@gmail.com> wrote:\n>>\n>> On Fri, Jan 17, 2025 at 11:56 PM Junio C Hamano <gitster@pobox.com> wrote:\n>> >\n>> > Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n>> > > +static void redact_non_printables(struct strbuf *buf)\n>> > > +{\n>> > > +     strbuf_trim(buf);\n>> > > +     for (size_t i = 0; i < buf->len; i++) {\n>> > > +             if (buf->buf[i] <= 32 || buf->buf[i] >= 127)\n>> >\n>> > <sane-ctype.h> defines isprint() we can use here.\n>> I think it would be better to add this in another commit so that one commit\n>> does one thing. I will add it after this patch series got settled,\n>> what do you think ?\n>\n> Alternatively it could be done in its own preparatory patch at the\n> beginning of this patch series.\n\nYup, a preliminary clean-up sounds fine, but so does a follow-up\nafter all the dust settles.\n\nThanks.\n"},{"id":"511009","messageId":"xmqqr04w0yxy.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM__kNH7+itsiOHzztJZZvpw-AcR1s=ZfSm=OLv4p0POsw@mail.gmail.com","subject":"Re: [PATCH v2 5/6] connect: advertise OS version","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-21T19:06:01Z","receivedAt":"2025-01-21T19:06:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> On Sat, Jan 18, 2025 at 3:52 AM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>>\n>> > As some issues that can happen with a Git client can be operating system\n>> > specific, it can be useful for a server to know which OS a client is\n>> > using. In the same way it can be useful for a client to know which OS\n>> > a server is using.\n>>\n>> Hmph.  The other end may be running different version of Git, and\n>> the version difference of _our_ software is probably more relevant.\n>> For that matter, they may even be running something entirely\n>> different from our software, like Gerrit.  So I am not sure I am\n>> convinced that os-version thing is a good thing to have with that\n>> paragraph.\n> Hi Junio,\n>\n> What could be a better way of describing this ? Also, user-agent capability is\n> already sharing the information about the version of Git.\n\nI dunno.  I only said that what you said does not convince me that\nos-version is a good thing.  Try harder, perhaps, to be more\nconvincing?  After all it is your itch.\n\nAn alternative that may be conceptually cleaner is to encourage\npeople to include not just Git version but OS variant information in\nthe existing \"agent\" capability, making it easier to do (which\nprobably means an addition to configuration knob), and encourage\nimplementors of other Git-compatible software to also let their\nsystems identify themselves via the \"agent\" capability.\n\nAs we have documented that \"agent\" strings are purely informative,\nthere shouldn't be any problem if we started identifying the version\nof Git running on one end as \"git/2.47.0 Linux\" (instead of\n\"git/2.47.0\").\n\n>> And among these four combinations, only one of them results in \"send\n>> to each other\", but the description above is fuzzy.\n> Yeah, describing the four combinations would better right ?\n\nI do not think readers necessarily would want to hear about the four\ncombinations; a paragraph that makes it clear that the configuration\nis independently set on either end of the connection will make it\nobvious to them without being told.\n\n>> So the question is again, do we ever need to use os_version() that\n>> is a raw string that may require sanitizing?  I do not think of any\n>> offhand.\n> In this case, I guess there has to be a conclusion on what to do.\n\nAs I didn't hear any concrete use cases of the version string before\nsanitizing, I would suggest to make os_version() to\n\n - ask for a string from the underlying system layer (like uname(2)\n   or its emulation), \n\n - immediately sanitize it,\n\n - cache the result from the above (just like the\n   os_version_sanitized() in the posted patch does with a variable\n   of type \"static char *\" in the function scope), and\n\n - keep returning that sanitized version to the callers.\n\nto simplify the API surface.\n\nThanks.\n"},{"id":"511010","messageId":"xmqqjzao0yk6.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM_9JKxu5QkxjHUFDM6cb5rSs400O72eYCiM8jHF9dr7Vw@mail.gmail.com","subject":"Re: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-21T19:14:17Z","receivedAt":"2025-01-21T19:14:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n>> That way, we do not need to add another mechanism that lets people\n>> spawn an arbitrary command while Git is running, we do not need to\n>> worry about security implications, and we do not need to worry about\n>> people abusing the facility to throw totally random and useless\n>> garbage information at the other end to make their stats useless.\n>\n> Thanks for the review.\n> This config option was added at Randall's request.\n>\n> Randall wrote:\n>\n> \"Instead of an override, what about a knob that specifies the uname\n> command to use to build the value. Personally, I would use `uname -s\n> -r -v` on NonStop to get the kernel version used in the build. The\n> difficulty on my platform is that this is not truly useful info. The\n> effective build OS compatibility version is in a #define\n> __L_Series_RVU and __H_Series_RVU, so the knob might be needed in\n> git_compat_util.h or similar. This comes from the compiler arguments,\n> which are not yet captured.\"\n>\n> So, the difficulty is that the compile time information might not be useful.\n\nIt only tells us that uname(2) gives useless information on the\nplatform, but there are other ways to ask the system for more useful\ninformation.  Isn't that the same deal with how useful information\nis obtained from not uname(2), since a useful one does not exist\nthere, but from GetVersion() on mingw?  We do not have to spawn an\nexternal process on MinGW to do this---we shouldn't have to do so on\nNonStop, either.  We should be able to make a call into a NonStop\nspecific code you or Randal add in compat/ from get_uname_info()\nto hide the platform-specific details, no?\n\n"},{"id":"511013","messageId":"02e901db6c3d$d7c1d190$874574b0$@nexbridge.com","threadId":"62747","inReplyTo":"xmqqjzao0yk6.fsf@gitster.g","subject":"RE: [PATCH v2 6/6] version: introduce osversion.command config for os-version output","fromName":"","fromEmail":"rsbecker@nexbridge.com","sentAt":"2025-01-21T19:51:14Z","receivedAt":"2025-01-21T19:51:39Z","isPatch":true,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On January 21, 2025 2:14 PM, Junio C Hamano wrote:\n>Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n>>> That way, we do not need to add another mechanism that lets people\n>>> spawn an arbitrary command while Git is running, we do not need to\n>>> worry about security implications, and we do not need to worry about\n>>> people abusing the facility to throw totally random and useless\n>>> garbage information at the other end to make their stats useless.\n>>\n>> Thanks for the review.\n>> This config option was added at Randall's request.\n>>\n>> Randall wrote:\n>>\n>> \"Instead of an override, what about a knob that specifies the uname\n>> command to use to build the value. Personally, I would use `uname -s\n>> -r -v` on NonStop to get the kernel version used in the build. The\n>> difficulty on my platform is that this is not truly useful info. The\n>> effective build OS compatibility version is in a #define\n>> __L_Series_RVU and __H_Series_RVU, so the knob might be needed in\n>> git_compat_util.h or similar. This comes from the compiler arguments,\n>> which are not yet captured.\"\n>>\n>> So, the difficulty is that the compile time information might not be\nuseful.\n>\n>It only tells us that uname(2) gives useless information on the platform,\nbut there\n>are other ways to ask the system for more useful information.  Isn't that\nthe same\n>deal with how useful information is obtained from not uname(2), since a\nuseful one\n>does not exist there, but from GetVersion() on mingw?  We do not have to\nspawn\n>an external process on MinGW to do this---we shouldn't have to do so on\nNonStop,\n>either.  We should be able to make a call into a NonStop specific code you\nor Randal\n>add in compat/ from get_uname_info() to hide the platform-specific details,\nno?\n\nI agree. One this series is finalized, I can put together a patch to obtain\nOS details\non NonStop from proprietary calls. Not something I am happy about doing, but\nit is what it is. I still do not get why people cannot just run 'uname -a'\ninstead of\nthis integration. From a support standpoint, knowing what OS level was used\nin\nthe build is more useful that git telling me what I can get from uname. But\nI\naccept that others want this, so I'm going with it - once the code is\naccepted\ninto base git.\n\n"},{"id":"511156","messageId":"20250124122217.250925-1-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250117104639.65608-1-usmanakinyemi202@gmail.com","subject":"[PATCH v3 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-24T12:21:35Z","receivedAt":"2025-01-24T12:22:24Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"For debugging, statistical analysis, and security purposes, it can\nbe valuable for Git servers to know the operating system the clients\nare using.\n\nFor example:\n- A server noticing that a client is using an old Git version with\nsecurity issues on one platform, like macOS, could verify if the\nuser is indeed running macOS before sending a message to upgrade.\"\n- Similarly, a server identifying a client that could benefit from\nan upgrade (e.g., for performance reasons) could better customize the\nmessage it sends to nudge the client to upgrade.\n\nSo let's add a new 'os-version' capability to the v2 protocol, in the\nsame way as the existing 'agent' capability that lets clients and servers\nexchange the Git version they are running.\n\nHaving the `os-version` protocol capability separately from other protocol\ncapabilities like `agent` is beneficial in ways like:\n\n- It provides a clear separation between Git versioning and OS-specific,\nconcerns making troubleshooting and environment analysis more modular.\n- It ensures we do not disrupt people's scripts that collect statistics\nfrom other protocol capabilities like `agent`.\n- It offers flexibility for possible future extensibility, allowing us to\nadd additional system-level details without modifying existing `agent`\nparsing logic.\n- It provides better control over privacy and security by allowing\nselective exposure of OS information.\n\nBy default this sends similar info as `git bugreport` is already sending,\nwhich uses uname(2). The difference is that it is sanitized in the same\nway as the Git version sent by the 'agent' capability is sanitized\n(by replacing characters having an ascii code less than 32 or more\nthan 127 with '.'). Also, it only sends the result of `uname -s` i.e\njust only the operating system name (e.g \"Linux\").\n\nDue to privacy issues and concerns, let's add the `transfer.advertiseOSVersion`\nconfig option. This boolean option is enabled by default, but allows users to\ndisable this feature completely by setting it to \"false\".\n\nNote that, due to differences between `uname(1)` (command-line\nutility) and `uname(2)` (system call) outputs on Windows,\n`transfer.advertiseOSVersion` is set to false on Windows during\ntesting. See the message part of patch 5/6 for more details.\n\nMy mentor, Christian Couder, sent a previous patch series about this\nbefore. You can find it here\nhttps://lore.kernel.org/git/20240619125708.3719150-1-christian.couder@gmail.com/\n\nChanges since v2\n================\n  - Dropped the last patch which introduced `osversion.command`.\n  - Use isprint() for checking printables byte in a preparatory\n  patch. \n  - Add a few reasons why we should have `os-version` as a separate\n  capability in the commit message that introduces it.\n  - Improve how `printf` is used in the tests for better clarity.\n  - Refactor documentation for improved clarity.\n  - Retrieve and immediately sanitize the system information in the\n  same function for simpler API surface.\n\nUsman Akinyemi (6):\n  version: replace manual ASCII checks with isprint() for clarity\n  version: refactor redact_non_printables()\n  version: refactor get_uname_info()\n  version: extend get_uname_info() to hide system details\n  t5701: add setup test to remove side-effect dependency\n  connect: advertise OS version\n\n Documentation/config/transfer.txt |  7 +++\n Documentation/gitprotocol-v2.txt  | 17 +++++++\n builtin/bugreport.c               | 13 +-----\n connect.c                         |  3 ++\n serve.c                           | 14 ++++++\n t/t5555-http-smart-common.sh      | 10 ++++-\n t/t5701-git-serve.sh              | 30 +++++++++++--\n t/test-lib-functions.sh           |  8 ++++\n version.c                         | 73 ++++++++++++++++++++++++++++---\n version.h                         | 22 ++++++++++\n 10 files changed, 175 insertions(+), 22 deletions(-)\n\nRange-diff versus v2:\n\n-:  ---------- > 1:  82b62c5e66 version: replace manual ASCII checks with isprint() for clarity\n1:  97bccab6d5 ! 2:  0a7d7ce871 version: refactor redact_non_printables()\n    @@ version.c\n     +/*\n     + * Trim and replace each character with ascii code below 32 or above\n     + * 127 (included) using a dot '.' character.\n    -+ * TODO: ensure consecutive non-printable characters are only replaced once\n    -+*/\n    ++ */\n     +static void redact_non_printables(struct strbuf *buf)\n     +{\n     +\tstrbuf_trim(buf);\n     +\tfor (size_t i = 0; i < buf->len; i++) {\n    -+\t\tif (buf->buf[i] <= 32 || buf->buf[i] >= 127)\n    ++\t\tif (!isprint(buf->buf[i]) || buf->buf[i] == ' ')\n     +\t\t\tbuf->buf[i] = '.';\n     +\t}\n     +}\n    @@ version.c: const char *git_user_agent_sanitized(void)\n      \t\tstrbuf_addstr(&buf, git_user_agent());\n     -\t\tstrbuf_trim(&buf);\n     -\t\tfor (size_t i = 0; i < buf.len; i++) {\n    --\t\t\tif (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n    +-\t\t\tif (!isprint(buf.buf[i]) || buf.buf[i] == ' ')\n     -\t\t\t\tbuf.buf[i] = '.';\n     -\t\t}\n     -\t\tagent = buf.buf;\n2:  1f8a4024a4 ! 3:  0187db59a4 version: refactor get_uname_info()\n    @@ builtin/bugreport.c: static void get_system_info(struct strbuf *sys_info)\n     \n      ## version.c ##\n     @@\n    - #include \"version.h\"\n      #include \"version-def.h\"\n      #include \"strbuf.h\"\n    + #include \"sane-ctype.h\"\n     +#include \"gettext.h\"\n      \n      const char git_version_string[] = GIT_VERSION;\n3:  962b42702f ! 4:  d3a3573594 version: extend get_uname_info() to hide system details\n    @@ Commit message\n         version: extend get_uname_info() to hide system details\n     \n         Currently, get_uname_info() function provides the full OS information.\n    -    In a follwing commit, we will need it to provide only the OS name.\n    +    In a following commit, we will need it to provide only the OS name.\n     \n         Let's extend it to accept a \"full\" flag that makes it switch between\n         providing full OS information and providing only the OS name.\n4:  7f0ec75a0d ! 5:  d9edd2ffc8 t5701: add setup test to remove side-effect dependency\n    @@ t/t5701-git-serve.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n      \n     -test_expect_success 'test capability advertisement' '\n     +test_expect_success 'setup to generate files with expected content' '\n    -+\tprintf \"agent=git/$(git version | cut -d\" \" -f3)\" >agent_and_osversion &&\n    ++\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_and_osversion &&\n     +\n      \ttest_oid_cache <<-EOF &&\n      \twrong_algo sha1:sha256\n    @@ t/t5701-git-serve.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n      \tls-refs=unborn\n      \tfetch=shallow wait-for-done\n      \tserver-option\n    -@@ t/t5701-git-serve.sh: test_expect_success 'test capability advertisement' '\n    - \tcat >expect.trailer <<-EOF &&\n    + \tobject-format=$(test_oid algo)\n    + \tEOF\n    +-\tcat >expect.trailer <<-EOF &&\n    ++\tcat >expect.trailer <<-EOF\n      \t0000\n      \tEOF\n     +'\n5:  007f8582d9 ! 6:  8a936b25f7 connect: advertise OS version\n    @@ Commit message\n         a server is using.\n     \n         Let's introduce a new protocol (`os-version`) allowing Git clients and\n    -    servers to exchange operating system information. The protocol is\n    -    controlled by the new `transfer.advertiseOSVersion` config option.\n    +    servers to exchange operating system information.\n    +\n    +    Having the `os-version` protocol capability separately from other protocol\n    +    capabilities like `agent` is beneficial in ways like:\n    +\n    +    - It provides a clear separation between Git versioning and OS-specific,\n    +    concerns making troubleshooting and environment analysis more modular.\n    +    - It ensures we do not disrupt people's scripts that collect statistics\n    +    from other protocol like `agent`.\n    +    - It offers flexibility for possible future extensibility, allowing us to\n    +    add additional system-level details without modifying existing `agent`\n    +    parsing logic.\n    +    - It provides better control over privacy and security by allowing\n    +    selective exposure of OS information.\n     \n         Add the `transfer.advertiseOSVersion` config option to address\n         privacy concerns. It defaults to `true` and can be changed to\n         `false`. When enabled, this option makes clients and servers send each\n         other the OS name (e.g., \"Linux\" or \"Windows\"). The information is\n    -    retrieved using the 'sysname' field of the `uname(2)` system call.\n    +    retrieved using the 'sysname' field of the `uname(2)` system call or its\n    +    equivalent.\n     \n         However, there are differences between `uname(1)` (command-line utility)\n         and `uname(2)` (system call) outputs on Windows. These discrepancies\n    @@ Documentation/config/transfer.txt: transfer.bundleURI::\n      \tservers. Defaults to false.\n     +\n     +transfer.advertiseOSVersion::\n    -+\tWhen `true`, the `os-version` capability is advertised by clients and\n    -+\tservers. It makes clients and servers send to each other a string\n    -+\trepresenting the operating system name, like \"Linux\" or \"Windows\".\n    -+\tThis string is retrieved from the `sysname` field of the struct returned\n    -+\tby the uname(2) system call. Defaults to true.\n    ++\tWhen set to `true` on the server, the server will advertise its\n    ++\t`os-version` capability to the client. On the client side, if set\n    ++\tto `true`, it will advertise its `os-version` capability to the\n    ++\tserver only if the server also advertises its `os-version` capability.\n    ++\tDefaults to true.\n     \n      ## Documentation/gitprotocol-v2.txt ##\n     @@ Documentation/gitprotocol-v2.txt: printable ASCII characters except space (i.e., the byte range 32 < x <\n    @@ Documentation/gitprotocol-v2.txt: printable ASCII characters except space (i.e.,\n     +In the same way as the `agent` capability above, the server can\n     +advertise the `os-version` capability to notify the client the\n     +kind of operating system it is running on. The client may optionally\n    -+send its own `os-version` capability, to notify the server the kind of\n    -+operating system it is also running on in its request to the server\n    ++send its own `os-version` capability, to notify the server the kind\n    ++of operating system it is also running on in its request to the server\n     +(but it MUST NOT do so if the server did not advertise the os-version\n     +capability). The value of this capability may consist of ASCII printable\n    -+characters(from 33 to 126 inclusive) and are typically made from the result of\n    -+`uname -s`(OS name e.g Linux). The os-version capability can be disabled\n    -+entirely by setting the `transfer.advertiseOSVersion` config option\n    -+to `false`. The `os-version` strings are purely informative for\n    ++characters(from 33 to 126 inclusive) and are typically made from the\n    ++result of `uname -s`(OS name e.g Linux). The os-version capability can\n    ++be disabled entirely by setting the `transfer.advertiseOSVersion` config\n    ++option to `false`. The `os-version` strings are purely informative for\n     +statistics and debugging purposes, and MUST NOT be used to\n    -+programmatically assume the presence or absence of particular\n    -+features.\n    ++programmatically assume the presence or absence of particular features.\n     +\n      ls-refs\n      ~~~~~~~\n    @@ t/t5555-http-smart-common.sh: test_expect_success 'git receive-pack --advertise-\n      '\n      \n      test_expect_success 'git upload-pack --advertise-refs: v2' '\n    -+\tprintf \"agent=FAKE\" >agent_and_osversion &&\n    ++\tprintf \"agent=FAKE\\n\" >agent_and_osversion &&\n     +\tif test_have_prereq WINDOWS\n     +\tthen\n     +\t\tgit config transfer.advertiseOSVersion false\n     +\telse\n    -+\t\tprintf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_osversion\n    ++\t\tprintf \"os-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_osversion\n     +\tfi &&\n     +\n      \tcat >expect <<-EOF &&\n    @@ t/t5555-http-smart-common.sh: test_expect_success 'git receive-pack --advertise-\n     \n      ## t/t5701-git-serve.sh ##\n     @@ t/t5701-git-serve.sh: test_expect_success 'setup to generate files with expected content' '\n    - \tcat >expect.trailer <<-EOF &&\n    + \tserver-option\n    + \tobject-format=$(test_oid algo)\n    + \tEOF\n    +-\tcat >expect.trailer <<-EOF\n    ++\tcat >expect.trailer <<-EOF &&\n      \t0000\n      \tEOF\n     +\n    @@ t/t5701-git-serve.sh: test_expect_success 'setup to generate files with expected\n     +\tthen\n     +\t\tgit config transfer.advertiseOSVersion false\n     +\telse\n    -+\t\tprintf \"\\nos-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_osversion\n    ++\t\tprintf \"os-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_osversion\n     +\tfi &&\n     +\n     +\tcat >expect_osversion.base <<-EOF\n    @@ t/test-lib-functions.sh: test_trailing_hash () {\n     +# Trim and replace each character with ascii code below 32 or above\n     +# 127 (included) using a dot '.' character.\n     +# Octal intervals \\001-\\040 and \\177-\\377\n    -+# corresponds to decimal intervals 1-32 and 127-255\n    ++# correspond to decimal intervals 1-32 and 127-255\n     +test_redact_non_printables () {\n     +    tr -d \"\\n\\r\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n     +}\n     \n      ## version.c ##\n     @@\n    - #include \"version-def.h\"\n      #include \"strbuf.h\"\n    + #include \"sane-ctype.h\"\n      #include \"gettext.h\"\n     +#include \"config.h\"\n      \n    @@ version.c: int get_uname_info(struct strbuf *buf, unsigned int full)\n      \treturn 0;\n      }\n     +\n    -+const char *os_version(void)\n    ++const char *os_version_sanitized(void)\n     +{\n     +\tstatic const char *os = NULL;\n     +\n    @@ version.c: int get_uname_info(struct strbuf *buf, unsigned int full)\n     +\t\tstruct strbuf buf = STRBUF_INIT;\n     +\n     +\t\tget_uname_info(&buf, 0);\n    ++\t\t/* Sanitize the os information immediately */\n    ++\t\tredact_non_printables(&buf);\n     +\t\tos = strbuf_detach(&buf, NULL);\n     +\t}\n     +\n     +\treturn os;\n     +}\n     +\n    -+const char *os_version_sanitized(void)\n    -+{\n    -+\tstatic const char *os_sanitized = NULL;\n    -+\n    -+\tif (!os_sanitized) {\n    -+\t\tstruct strbuf buf = STRBUF_INIT;\n    -+\n    -+\t\tstrbuf_addstr(&buf, os_version());\n    -+\t\tredact_non_printables(&buf);\n    -+\t\tos_sanitized = strbuf_detach(&buf, NULL);\n    -+\t}\n    -+\n    -+\treturn os_sanitized;\n    -+}\n    -+\n     +int advertise_os_version(struct repository *r)\n     +{\n     +\tstatic int transfer_advertise_os_version = -1;\n    @@ version.h: const char *git_user_agent_sanitized(void);\n      int get_uname_info(struct strbuf *buf, unsigned int full);\n      \n     +/*\n    -+  Retrieve and cache system information for subsequent calls.\n    -+  Return a pointer to the cached system information string.\n    -+*/\n    -+const char *os_version(void);\n    -+\n    -+/*\n    -+  Retrieve system information string from os_version(). Then\n    -+  sanitize and cache it. Return a pointer to the sanitized\n    -+  system information string.\n    ++  Retrieve, sanitize and cache system information for subsequent\n    ++  calls. Return a pointer to the sanitized system information\n    ++  string.\n     +*/\n     +const char *os_version_sanitized(void);\n     +\n6:  10a07a3095 < -:  ---------- version: introduce osversion.command config for os-version output\n-- \n2.48.0\n\n"},{"id":"511157","messageId":"20250124122217.250925-2-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250124122217.250925-1-usmanakinyemi202@gmail.com","subject":"[PATCH v3 1/6] version: replace manual ASCII checks with isprint() for clarity","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-24T12:21:36Z","receivedAt":"2025-01-24T12:22:28Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Since the isprint() function checks for printable characters, let's\nreplace the existing hardcoded ASCII checks with it. However, since\nthe original checks also handled spaces, we need to account for spaces\nexplicitly in the new check.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n version.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/version.c b/version.c\nindex 4d763ab48d..6cfbb8ca56 100644\n--- a/version.c\n+++ b/version.c\n@@ -2,6 +2,7 @@\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n+#include \"sane-ctype.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -29,7 +30,7 @@ const char *git_user_agent_sanitized(void)\n \t\tstrbuf_addstr(&buf, git_user_agent());\n \t\tstrbuf_trim(&buf);\n \t\tfor (size_t i = 0; i < buf.len; i++) {\n-\t\t\tif (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n+\t\t\tif (!isprint(buf.buf[i]) || buf.buf[i] == ' ')\n \t\t\t\tbuf.buf[i] = '.';\n \t\t}\n \t\tagent = buf.buf;\n-- \n2.48.0\n\n"},{"id":"511158","messageId":"20250124122217.250925-3-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250124122217.250925-1-usmanakinyemi202@gmail.com","subject":"[PATCH v3 2/6] version: refactor redact_non_printables()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-24T12:21:37Z","receivedAt":"2025-01-24T12:22:32Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"The git_user_agent_sanitized() function performs some sanitizing to\navoid special characters being sent over the line and possibly messing\nup with the protocol or with the parsing on the other side.\n\nLet's extract this sanitizing into a new redact_non_printables() function,\nas we will want to reuse it in a following patch.\n\nFor now the new redact_non_printables() function is still static as\nit's only needed locally.\n\nWhile at it, let's use strbuf_detach() to explicitly detach the string\ncontained by the 'buf' strbuf.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n version.c | 21 +++++++++++++++------\n 1 file changed, 15 insertions(+), 6 deletions(-)\n\ndiff --git a/version.c b/version.c\nindex 6cfbb8ca56..60df71fd0e 100644\n--- a/version.c\n+++ b/version.c\n@@ -7,6 +7,19 @@\n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n \n+/*\n+ * Trim and replace each character with ascii code below 32 or above\n+ * 127 (included) using a dot '.' character.\n+ */\n+static void redact_non_printables(struct strbuf *buf)\n+{\n+\tstrbuf_trim(buf);\n+\tfor (size_t i = 0; i < buf->len; i++) {\n+\t\tif (!isprint(buf->buf[i]) || buf->buf[i] == ' ')\n+\t\t\tbuf->buf[i] = '.';\n+\t}\n+}\n+\n const char *git_user_agent(void)\n {\n \tstatic const char *agent = NULL;\n@@ -28,12 +41,8 @@ const char *git_user_agent_sanitized(void)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&buf, git_user_agent());\n-\t\tstrbuf_trim(&buf);\n-\t\tfor (size_t i = 0; i < buf.len; i++) {\n-\t\t\tif (!isprint(buf.buf[i]) || buf.buf[i] == ' ')\n-\t\t\t\tbuf.buf[i] = '.';\n-\t\t}\n-\t\tagent = buf.buf;\n+\t\tredact_non_printables(&buf);\n+\t\tagent = strbuf_detach(&buf, NULL);\n \t}\n \n \treturn agent;\n-- \n2.48.0\n\n"},{"id":"511159","messageId":"20250124122217.250925-4-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250124122217.250925-1-usmanakinyemi202@gmail.com","subject":"[PATCH v3 3/6] version: refactor get_uname_info()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-24T12:21:38Z","receivedAt":"2025-01-24T12:22:36Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Some code from \"builtin/bugreport.c\" uses uname(2) to get system\ninformation.\n\nLet's refactor this code into a new get_uname_info() function, so\nthat we can reuse it in a following commit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c | 13 ++-----------\n version.c           | 20 ++++++++++++++++++++\n version.h           |  7 +++++++\n 3 files changed, 29 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 7c2df035c9..5e13d532a8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -12,10 +12,10 @@\n #include \"diagnose.h\"\n #include \"object-file.h\"\n #include \"setup.h\"\n+#include \"version.h\"\n \n static void get_system_info(struct strbuf *sys_info)\n {\n-\tstruct utsname uname_info;\n \tchar *shell = NULL;\n \n \t/* get git version from native cmd */\n@@ -24,16 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tif (uname(&uname_info))\n-\t\tstrbuf_addf(sys_info, _(\"uname() failed with error '%s' (%d)\\n\"),\n-\t\t\t    strerror(errno),\n-\t\t\t    errno);\n-\telse\n-\t\tstrbuf_addf(sys_info, \"%s %s %s %s\\n\",\n-\t\t\t    uname_info.sysname,\n-\t\t\t    uname_info.release,\n-\t\t\t    uname_info.version,\n-\t\t\t    uname_info.machine);\n+\tget_uname_info(sys_info);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 60df71fd0e..3ec8b8243d 100644\n--- a/version.c\n+++ b/version.c\n@@ -3,6 +3,7 @@\n #include \"version-def.h\"\n #include \"strbuf.h\"\n #include \"sane-ctype.h\"\n+#include \"gettext.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -47,3 +48,22 @@ const char *git_user_agent_sanitized(void)\n \n \treturn agent;\n }\n+\n+int get_uname_info(struct strbuf *buf)\n+{\n+\tstruct utsname uname_info;\n+\n+\tif (uname(&uname_info)) {\n+\t\tstrbuf_addf(buf, _(\"uname() failed with error '%s' (%d)\\n\"),\n+\t\t\t    strerror(errno),\n+\t\t\t    errno);\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t    uname_info.sysname,\n+\t\t    uname_info.release,\n+\t\t    uname_info.version,\n+\t\t    uname_info.machine);\n+\treturn 0;\n+}\ndiff --git a/version.h b/version.h\nindex 7c62e80577..afe3dbbab7 100644\n--- a/version.h\n+++ b/version.h\n@@ -7,4 +7,11 @@ extern const char git_built_from_commit_string[];\n const char *git_user_agent(void);\n const char *git_user_agent_sanitized(void);\n \n+/*\n+  Try to get information about the system using uname(2).\n+  Return -1 and put an error message into 'buf' in case of uname()\n+  error. Return 0 and put uname info into 'buf' otherwise.\n+*/\n+int get_uname_info(struct strbuf *buf);\n+\n #endif /* VERSION_H */\n-- \n2.48.0\n\n"},{"id":"511160","messageId":"20250124122217.250925-5-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250124122217.250925-1-usmanakinyemi202@gmail.com","subject":"[PATCH v3 4/6] version: extend get_uname_info() to hide system details","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-24T12:21:39Z","receivedAt":"2025-01-24T12:22:40Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently, get_uname_info() function provides the full OS information.\nIn a following commit, we will need it to provide only the OS name.\n\nLet's extend it to accept a \"full\" flag that makes it switch between\nproviding full OS information and providing only the OS name.\n\nWe may need to refactor this function in the future if an\n`osVersion.format` is added.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c |  2 +-\n version.c           | 16 +++++++++-------\n version.h           |  2 +-\n 3 files changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 5e13d532a8..e3288a86c8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -24,7 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tget_uname_info(sys_info);\n+\tget_uname_info(sys_info, 1);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 3ec8b8243d..d95221a72a 100644\n--- a/version.c\n+++ b/version.c\n@@ -49,7 +49,7 @@ const char *git_user_agent_sanitized(void)\n \treturn agent;\n }\n \n-int get_uname_info(struct strbuf *buf)\n+int get_uname_info(struct strbuf *buf, unsigned int full)\n {\n \tstruct utsname uname_info;\n \n@@ -59,11 +59,13 @@ int get_uname_info(struct strbuf *buf)\n \t\t\t    errno);\n \t\treturn -1;\n \t}\n-\n-\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n-\t\t    uname_info.sysname,\n-\t\t    uname_info.release,\n-\t\t    uname_info.version,\n-\t\t    uname_info.machine);\n+\tif (full)\n+\t\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t\t    uname_info.sysname,\n+\t\t\t    uname_info.release,\n+\t\t\t    uname_info.version,\n+\t\t\t    uname_info.machine);\n+\telse\n+\t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n \treturn 0;\n }\ndiff --git a/version.h b/version.h\nindex afe3dbbab7..5eb586c0bd 100644\n--- a/version.h\n+++ b/version.h\n@@ -12,6 +12,6 @@ const char *git_user_agent_sanitized(void);\n   Return -1 and put an error message into 'buf' in case of uname()\n   error. Return 0 and put uname info into 'buf' otherwise.\n */\n-int get_uname_info(struct strbuf *buf);\n+int get_uname_info(struct strbuf *buf, unsigned int full);\n \n #endif /* VERSION_H */\n-- \n2.48.0\n\n"},{"id":"511161","messageId":"20250124122217.250925-6-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250124122217.250925-1-usmanakinyemi202@gmail.com","subject":"[PATCH v3 5/6] t5701: add setup test to remove side-effect dependency","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-24T12:21:40Z","receivedAt":"2025-01-24T12:22:44Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently, the \"test capability advertisement\" test creates some files\nwith expected content which are used by other tests below it.\n\nTo remove that side-effect from this test, let's split up part of\nit into a \"setup\"-type test which creates the files with expected content\nwhich gets reused by multiple tests. This will be useful in a following\ncommit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n t/t5701-git-serve.sh | 12 +++++++++---\n 1 file changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex de904c1655..9394235fa0 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -7,22 +7,28 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n \n-test_expect_success 'test capability advertisement' '\n+test_expect_success 'setup to generate files with expected content' '\n+\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_and_osversion &&\n+\n \ttest_oid_cache <<-EOF &&\n \twrong_algo sha1:sha256\n \twrong_algo sha256:sha1\n \tEOF\n+\n \tcat >expect.base <<-EOF &&\n \tversion 2\n-\tagent=git/$(git version | cut -d\" \" -f3)\n+\t$(cat agent_and_osversion)\n \tls-refs=unborn\n \tfetch=shallow wait-for-done\n \tserver-option\n \tobject-format=$(test_oid algo)\n \tEOF\n-\tcat >expect.trailer <<-EOF &&\n+\tcat >expect.trailer <<-EOF\n \t0000\n \tEOF\n+'\n+\n+test_expect_success 'test capability advertisement' '\n \tcat expect.base expect.trailer >expect &&\n \n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n-- \n2.48.0\n\n"},{"id":"511162","messageId":"20250124122217.250925-7-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250124122217.250925-1-usmanakinyemi202@gmail.com","subject":"[PATCH v3 6/6] connect: advertise OS version","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-24T12:21:41Z","receivedAt":"2025-01-24T12:22:48Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"As some issues that can happen with a Git client can be operating system\nspecific, it can be useful for a server to know which OS a client is\nusing. In the same way it can be useful for a client to know which OS\na server is using.\n\nLet's introduce a new protocol (`os-version`) allowing Git clients and\nservers to exchange operating system information.\n\nHaving the `os-version` protocol capability separately from other protocol\ncapabilities like `agent` is beneficial in ways like:\n\n- It provides a clear separation between Git versioning and OS-specific,\nconcerns making troubleshooting and environment analysis more modular.\n- It ensures we do not disrupt people's scripts that collect statistics\nfrom other protocol capabilities like `agent`.\n- It offers flexibility for possible future extensibility, allowing us to\nadd additional system-level details without modifying existing `agent`\nparsing logic.\n- It provides better control over privacy and security by allowing\nselective exposure of OS information.\n\nAdd the `transfer.advertiseOSVersion` config option to address\nprivacy concerns. It defaults to `true` and can be changed to\n`false`. When enabled, this option makes clients and servers send each\nother the OS name (e.g., \"Linux\" or \"Windows\"). The information is\nretrieved using the 'sysname' field of the `uname(2)` system call or its\nequivalent.\n\nHowever, there are differences between `uname(1)` (command-line utility)\nand `uname(2)` (system call) outputs on Windows. These discrepancies\ncomplicate testing on Windows platforms. For example:\n  - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n  .2024-02-14.20:17.UTC.x86_64\n  - `uname(2)` output: Windows.10.0.20348\n\nOn Windows, uname(2) is not actually system-supplied but is instead\nalready faked up by Git itself. We could have overcome the test issue\non Windows by implementing a new `uname` subcommand in `test-tool`\nusing uname(2), but except uname(2), which would be tested against\nitself, there would be nothing platform specific, so it's just simpler\nto disable the tests on Windows.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n Documentation/config/transfer.txt |  7 +++++++\n Documentation/gitprotocol-v2.txt  | 17 +++++++++++++++++\n connect.c                         |  3 +++\n serve.c                           | 14 ++++++++++++++\n t/t5555-http-smart-common.sh      | 10 +++++++++-\n t/t5701-git-serve.sh              | 22 +++++++++++++++++++---\n t/test-lib-functions.sh           |  8 ++++++++\n version.c                         | 29 +++++++++++++++++++++++++++++\n version.h                         | 15 +++++++++++++++\n 9 files changed, 121 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt\nindex f1ce50f4a6..016eb27430 100644\n--- a/Documentation/config/transfer.txt\n+++ b/Documentation/config/transfer.txt\n@@ -125,3 +125,10 @@ transfer.bundleURI::\n transfer.advertiseObjectInfo::\n \tWhen `true`, the `object-info` capability is advertised by\n \tservers. Defaults to false.\n+\n+transfer.advertiseOSVersion::\n+\tWhen set to `true` on the server, the server will advertise its\n+\t`os-version` capability to the client. On the client side, if set\n+\tto `true`, it will advertise its `os-version` capability to the\n+\tserver only if the server also advertises its `os-version` capability.\n+\tDefaults to true.\ndiff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\nindex 1652fef3ae..62f7ae3423 100644\n--- a/Documentation/gitprotocol-v2.txt\n+++ b/Documentation/gitprotocol-v2.txt\n@@ -190,6 +190,23 @@ printable ASCII characters except space (i.e., the byte range 32 < x <\n and debugging purposes, and MUST NOT be used to programmatically assume\n the presence or absence of particular features.\n \n+os-version\n+~~~~~~~~~~\n+\n+In the same way as the `agent` capability above, the server can\n+advertise the `os-version` capability to notify the client the\n+kind of operating system it is running on. The client may optionally\n+send its own `os-version` capability, to notify the server the kind\n+of operating system it is also running on in its request to the server\n+(but it MUST NOT do so if the server did not advertise the os-version\n+capability). The value of this capability may consist of ASCII printable\n+characters(from 33 to 126 inclusive) and are typically made from the\n+result of `uname -s`(OS name e.g Linux). The os-version capability can\n+be disabled entirely by setting the `transfer.advertiseOSVersion` config\n+option to `false`. The `os-version` strings are purely informative for\n+statistics and debugging purposes, and MUST NOT be used to\n+programmatically assume the presence or absence of particular features.\n+\n ls-refs\n ~~~~~~~\n \ndiff --git a/connect.c b/connect.c\nindex 10fad43e98..6d5792b63c 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -492,6 +492,9 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)\n \tif (server_supports_v2(\"agent\"))\n \t\tpacket_write_fmt(fd_out, \"agent=%s\", git_user_agent_sanitized());\n \n+\tif (server_supports_v2(\"os-version\") && advertise_os_version(the_repository))\n+\t\tpacket_write_fmt(fd_out, \"os-version=%s\", os_version_sanitized());\n+\n \tif (server_feature_v2(\"object-format\", &hash_name)) {\n \t\tint hash_algo = hash_algo_by_name(hash_name);\n \t\tif (hash_algo == GIT_HASH_UNKNOWN)\ndiff --git a/serve.c b/serve.c\nindex c8694e3751..5b0d54ae9a 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -31,6 +31,16 @@ static int agent_advertise(struct repository *r UNUSED,\n \treturn 1;\n }\n \n+static int os_version_advertise(struct repository *r,\n+\t\t\t   struct strbuf *value)\n+{\n+\tif (!advertise_os_version(r))\n+\t\treturn 0;\n+\tif (value)\n+\t\tstrbuf_addstr(value, os_version_sanitized());\n+\treturn 1;\n+}\n+\n static int object_format_advertise(struct repository *r,\n \t\t\t\t   struct strbuf *value)\n {\n@@ -123,6 +133,10 @@ static struct protocol_capability capabilities[] = {\n \t\t.name = \"agent\",\n \t\t.advertise = agent_advertise,\n \t},\n+\t{\n+\t\t.name = \"os-version\",\n+\t\t.advertise = os_version_advertise,\n+\t},\n \t{\n \t\t.name = \"ls-refs\",\n \t\t.advertise = ls_refs_advertise,\ndiff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh\nindex e47ea1ad10..b1af37a4a2 100755\n--- a/t/t5555-http-smart-common.sh\n+++ b/t/t5555-http-smart-common.sh\n@@ -123,9 +123,17 @@ test_expect_success 'git receive-pack --advertise-refs: v1' '\n '\n \n test_expect_success 'git upload-pack --advertise-refs: v2' '\n+\tprintf \"agent=FAKE\\n\" >agent_and_osversion &&\n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tgit config transfer.advertiseOSVersion false\n+\telse\n+\t\tprintf \"os-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_osversion\n+\tfi &&\n+\n \tcat >expect <<-EOF &&\n \tversion 2\n-\tagent=FAKE\n+\t$(cat agent_and_osversion)\n \tls-refs=unborn\n \tfetch=shallow wait-for-done\n \tserver-option\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 9394235fa0..2616132b95 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -23,13 +23,29 @@ test_expect_success 'setup to generate files with expected content' '\n \tserver-option\n \tobject-format=$(test_oid algo)\n \tEOF\n-\tcat >expect.trailer <<-EOF\n+\tcat >expect.trailer <<-EOF &&\n \t0000\n \tEOF\n+\n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tgit config transfer.advertiseOSVersion false\n+\telse\n+\t\tprintf \"os-version=%s\\n\" $(uname -s | test_redact_non_printables) >>agent_and_osversion\n+\tfi &&\n+\n+\tcat >expect_osversion.base <<-EOF\n+\tversion 2\n+\t$(cat agent_and_osversion)\n+\tls-refs=unborn\n+\tfetch=shallow wait-for-done\n+\tserver-option\n+\tobject-format=$(test_oid algo)\n+\tEOF\n '\n \n test_expect_success 'test capability advertisement' '\n-\tcat expect.base expect.trailer >expect &&\n+\tcat expect_osversion.base expect.trailer >expect &&\n \n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n \t\t--advertise-capabilities >out &&\n@@ -357,7 +373,7 @@ test_expect_success 'test capability advertisement with uploadpack.advertiseBund\n \tcat >expect.extra <<-EOF &&\n \tbundle-uri\n \tEOF\n-\tcat expect.base \\\n+\tcat expect_osversion.base \\\n \t    expect.extra \\\n \t    expect.trailer >expect &&\n \ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 78e054ab50..3465904323 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -2007,3 +2007,11 @@ test_trailing_hash () {\n \t\ttest-tool hexdump |\n \t\tsed \"s/ //g\"\n }\n+\n+# Trim and replace each character with ascii code below 32 or above\n+# 127 (included) using a dot '.' character.\n+# Octal intervals \\001-\\040 and \\177-\\377\n+# correspond to decimal intervals 1-32 and 127-255\n+test_redact_non_printables () {\n+    tr -d \"\\n\\r\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n+}\ndiff --git a/version.c b/version.c\nindex d95221a72a..083154a6cb 100644\n--- a/version.c\n+++ b/version.c\n@@ -4,6 +4,7 @@\n #include \"strbuf.h\"\n #include \"sane-ctype.h\"\n #include \"gettext.h\"\n+#include \"config.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -69,3 +70,31 @@ int get_uname_info(struct strbuf *buf, unsigned int full)\n \t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n \treturn 0;\n }\n+\n+const char *os_version_sanitized(void)\n+{\n+\tstatic const char *os = NULL;\n+\n+\tif (!os) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tget_uname_info(&buf, 0);\n+\t\t/* Sanitize the os information immediately */\n+\t\tredact_non_printables(&buf);\n+\t\tos = strbuf_detach(&buf, NULL);\n+\t}\n+\n+\treturn os;\n+}\n+\n+int advertise_os_version(struct repository *r)\n+{\n+\tstatic int transfer_advertise_os_version = -1;\n+\n+\tif (transfer_advertise_os_version == -1) {\n+\t\trepo_config_get_bool(r, \"transfer.advertiseosversion\", &transfer_advertise_os_version);\n+\t\t/* enabled by default */\n+\t\ttransfer_advertise_os_version = !!transfer_advertise_os_version;\n+\t}\n+\treturn transfer_advertise_os_version;\n+}\ndiff --git a/version.h b/version.h\nindex 5eb586c0bd..300ee73df5 100644\n--- a/version.h\n+++ b/version.h\n@@ -1,6 +1,8 @@\n #ifndef VERSION_H\n #define VERSION_H\n \n+struct repository;\n+\n extern const char git_version_string[];\n extern const char git_built_from_commit_string[];\n \n@@ -14,4 +16,17 @@ const char *git_user_agent_sanitized(void);\n */\n int get_uname_info(struct strbuf *buf, unsigned int full);\n \n+/*\n+  Retrieve, sanitize and cache system information for subsequent\n+  calls. Return a pointer to the sanitized system information\n+  string.\n+*/\n+const char *os_version_sanitized(void);\n+\n+/*\n+  Retrieve and cache whether os-version capability is enabled.\n+  Return 1 if enabled, 0 if disabled.\n+*/\n+int advertise_os_version(struct repository *r);\n+\n #endif /* VERSION_H */\n-- \n2.48.0\n\n"},{"id":"511185","messageId":"xmqq5xm4ytb2.fsf@gitster.g","threadId":"62747","inReplyTo":"20250124122217.250925-6-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v3 5/6] t5701: add setup test to remove side-effect dependency","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-24T18:12:49Z","receivedAt":"2025-01-24T18:12:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Currently, the \"test capability advertisement\" test creates some files\n> with expected content which are used by other tests below it.\n>\n> To remove that side-effect from this test, let's split up part of\n> it into a \"setup\"-type test which creates the files with expected content\n> which gets reused by multiple tests. This will be useful in a following\n> commit.\n>\n> Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> ---\n>  t/t5701-git-serve.sh | 12 +++++++++---\n>  1 file changed, 9 insertions(+), 3 deletions(-)\n\nNice clean-up.\n\n>\n> diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n> index de904c1655..9394235fa0 100755\n> --- a/t/t5701-git-serve.sh\n> +++ b/t/t5701-git-serve.sh\n> @@ -7,22 +7,28 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n>  \n>  . ./test-lib.sh\n>  \n> -test_expect_success 'test capability advertisement' '\n> +test_expect_success 'setup to generate files with expected content' '\n> +\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_and_osversion &&\n> +\n>  \ttest_oid_cache <<-EOF &&\n>  \twrong_algo sha1:sha256\n>  \twrong_algo sha256:sha1\n>  \tEOF\n> +\n>  \tcat >expect.base <<-EOF &&\n>  \tversion 2\n> -\tagent=git/$(git version | cut -d\" \" -f3)\n> +\t$(cat agent_and_osversion)\n>  \tls-refs=unborn\n>  \tfetch=shallow wait-for-done\n>  \tserver-option\n>  \tobject-format=$(test_oid algo)\n>  \tEOF\n> -\tcat >expect.trailer <<-EOF &&\n> +\tcat >expect.trailer <<-EOF\n>  \t0000\n>  \tEOF\n> +'\n> +\n> +test_expect_success 'test capability advertisement' '\n>  \tcat expect.base expect.trailer >expect &&\n>  \n>  \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n"},{"id":"511186","messageId":"xmqqy0z0xeq6.fsf@gitster.g","threadId":"62747","inReplyTo":"20250124122217.250925-2-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v3 1/6] version: replace manual ASCII checks with isprint() for clarity","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-24T18:13:05Z","receivedAt":"2025-01-24T18:13:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Since the isprint() function checks for printable characters, let's\n> replace the existing hardcoded ASCII checks with it. However, since\n> the original checks also handled spaces, we need to account for spaces\n> explicitly in the new check.\n>\n> Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> ---\n>  version.c | 3 ++-\n>  1 file changed, 2 insertions(+), 1 deletion(-)\n\nThanks.  Nicely done as a separate step.\n\n> diff --git a/version.c b/version.c\n> index 4d763ab48d..6cfbb8ca56 100644\n> --- a/version.c\n> +++ b/version.c\n> @@ -2,6 +2,7 @@\n>  #include \"version.h\"\n>  #include \"version-def.h\"\n>  #include \"strbuf.h\"\n> +#include \"sane-ctype.h\"\n>  \n>  const char git_version_string[] = GIT_VERSION;\n>  const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n> @@ -29,7 +30,7 @@ const char *git_user_agent_sanitized(void)\n>  \t\tstrbuf_addstr(&buf, git_user_agent());\n>  \t\tstrbuf_trim(&buf);\n>  \t\tfor (size_t i = 0; i < buf.len; i++) {\n> -\t\t\tif (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n> +\t\t\tif (!isprint(buf.buf[i]) || buf.buf[i] == ' ')\n>  \t\t\t\tbuf.buf[i] = '.';\n>  \t\t}\n>  \t\tagent = buf.buf;\n"},{"id":"511187","messageId":"xmqqed0sxdiz.fsf@gitster.g","threadId":"62747","inReplyTo":"20250124122217.250925-1-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v3 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-24T18:39:00Z","receivedAt":"2025-01-24T18:39:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> For debugging, statistical analysis, and security purposes, it can\n> be valuable for Git servers to know the operating system the clients\n> are using.\n\nOK.  I think the reorganization done in this round makes it much\neasier to see what is going on in each step.  Very well done.\n\nThe only remaining issue from my point of view is if we really want\nthis as a separate and new knob with capability, or if we would be\nbetter off to carry this kind of extra piece of information by\nenhancing existing \"agent\" capability.  Given what Web Browsers do\nin their UA strings, it does feel cumbersome for analitics tools to\npay attention to two separate input sources (os-version and agent).\n\nHas somebody brought up any downsides of cramming the OS information\nto the existing agent thing?  I have not thought of any possible\ndownsides since I made this suggestion in a previous review of this\ntopic, but I may be missing something obvious, so...\n\nThanks.\n"},{"id":"511262","messageId":"CAP8UFD31A9KuqzXrDWzjokWzBtTYsF1FGJPX8PFAWXnn_2BH_w@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqed0sxdiz.fsf@gitster.g","subject":"Re: [PATCH v3 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-01-27T13:38:58Z","receivedAt":"2025-01-27T13:39:11Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Jan 24, 2025 at 7:39 PM Junio C Hamano <gitster@pobox.com> wrote:\n\n> The only remaining issue from my point of view is if we really want\n> this as a separate and new knob with capability, or if we would be\n> better off to carry this kind of extra piece of information by\n> enhancing existing \"agent\" capability.  Given what Web Browsers do\n> in their UA strings, it does feel cumbersome for analitics tools to\n> pay attention to two separate input sources (os-version and agent).\n>\n> Has somebody brought up any downsides of cramming the OS information\n> to the existing agent thing?  I have not thought of any possible\n> downsides since I made this suggestion in a previous review of this\n> topic, but I may be missing something obvious, so...\n\nMy opinion is that it isn't a good idea to enhance the existing\n\"agent\" capability. Yeah, it goes in the same direction as what web\nbrowsers have been doing with the User-Agent header, but I think web\nbrowsers are an especially bad example that we should strive not to\nfollow.\n\nAccording to Wikipedia\n(https://en.wikipedia.org/wiki/User-Agent_header) the format for the\nUser-Agent header is now \"Mozilla/[version] ([system and browser\ninformation]) [platform] ([platform details]) [extensions]\", for\nexample \"Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us)\nAppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405\". This is\nobviously very difficult to parse for everyone including analytics\ntools and is not very flexible either. It serves as a way to pass\ninformation about available features, but leak some privacy\ninformation in the process. The fact that it's used to pass\ninformation about available features has led to a lot of user agent\nspoofing which means that analytics, statistics and debugging are\nlikely harder than they need to be.\n\nWhen Git developed capabilities and the \"agent\" capability, the doc\ntook care of saying things that it \"MUST NOT be used to\nprogrammatically assume the presence or absence of particular\nfeatures\". This was done to go in the direction of not passing more\ninformation through this \"agent\" capability but instead use separate\nones. So I think we should just avoid putting other things in the\n\"agent\"  capability to avoid what happened to the User-Agent header in\nbrowsers and to stay true to our original intent to have a different\ncapability for each advertised information or feature.\n"},{"id":"511276","messageId":"xmqq5xm0wa5l.fsf@gitster.g","threadId":"62747","inReplyTo":"CAP8UFD31A9KuqzXrDWzjokWzBtTYsF1FGJPX8PFAWXnn_2BH_w@mail.gmail.com","subject":"Re: [PATCH v3 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-27T15:26:14Z","receivedAt":"2025-01-27T15:26:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> information in the process. The fact that it's used to pass\n> information about available features has led to a lot of user agent\n> spoofing which means that analytics, statistics and debugging are\n> likely harder than they need to be.\n\nYes, that is a valid viewpoint, but ...\n\n> When Git developed capabilities and the \"agent\" capability, the doc\n> took care of saying things that it \"MUST NOT be used to\n> programmatically assume the presence or absence of particular\n> features\".\n\n... the proposed os-version thing has the same wording in its\ndocumentation, doesn't it?  What is being added is not to be used\nin a way that requires parsing and trusting the result.\n\nSo unless your point is that users (like those who parse User-Agent\nstring by browsers) will do the wrong thing and assume these strings\nare usable for feature detection anyway so we should make it easier\nto parse, I'd have to disagree.  If we are not aiming to make it\neasier to parse and assume certain things that we do not want them\nto, I do not see why we need to have the pieces of information in\ntwo separate capabilities.\n\nThanks.\n"},{"id":"511561","messageId":"CAP8UFD1+Qa2cRkYs8R_Lgr8Bkz7YqPUW6Q3ogKcrBSp+q-3qbw@mail.gmail.com","threadId":"62747","inReplyTo":"xmqq5xm0wa5l.fsf@gitster.g","subject":"Re: [PATCH v3 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2025-01-31T14:30:09Z","receivedAt":"2025-01-31T14:30:25Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Mon, Jan 27, 2025 at 4:26 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > information in the process. The fact that it's used to pass\n> > information about available features has led to a lot of user agent\n> > spoofing which means that analytics, statistics and debugging are\n> > likely harder than they need to be.\n>\n> Yes, that is a valid viewpoint, but ...\n>\n> > When Git developed capabilities and the \"agent\" capability, the doc\n> > took care of saying things that it \"MUST NOT be used to\n> > programmatically assume the presence or absence of particular\n> > features\".\n>\n> ... the proposed os-version thing has the same wording in its\n> documentation, doesn't it?\n\nYeah, we repeat it to make sure that users read it. I am fine with\nrefactoring that wording if we think that having it once is enough.\n\n> What is being added is not to be used\n> in a way that requires parsing and trusting the result.\n\nWhy not? If server people want to do OS stats on their clients, for\nexample, why shouldn't they parse and trust the result?\n\n> So unless your point is that users (like those who parse User-Agent\n> string by browsers) will do the wrong thing and assume these strings\n> are usable for feature detection anyway so we should make it easier\n> to parse, I'd have to disagree.\n\nWe should make it easy to parse because people will use this field\n(otherwise why are we adding it?), and we want to make it easy to use\nrather than hard just because we are nice with our users.\n\nI think we should not assume that they will do the wrong thing,\nespecially if our docs are clear about how it shouldn't be used.\n\n>  If we are not aiming to make it\n> easier to parse and assume certain things that we do not want them\n> to, I do not see why we need to have the pieces of information in\n> two separate capabilities.\n\nI think it's just the right thing to make it easy to parse. Doing OS\nstats on the server side doesn't need to be unnecessarily hard.\n\nBy the way, if we put the OS information in the \"agent\" capability,\nhow do we separate it from the existing \"package/version\" content and\nmake it easy to parse? I don't see a good solution because\nGIT_USER_AGENT could be used, and the config option to not show the OS\nname could be used too.\n\nAlso we don't know what could be in the \"version\" part. The doc says\nthat the agent part is typically of the form \"package/version\" but\ndoesn't require it.\n\nThanks.\n"},{"id":"511579","messageId":"xmqqzfj77xd7.fsf@gitster.g","threadId":"62747","inReplyTo":"CAP8UFD1+Qa2cRkYs8R_Lgr8Bkz7YqPUW6Q3ogKcrBSp+q-3qbw@mail.gmail.com","subject":"Re: [PATCH v3 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-31T16:37:40Z","receivedAt":"2025-01-31T16:37:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> By the way, if we put the OS information in the \"agent\" capability,\n> how do we separate it from the existing \"package/version\" content and\n> make it easy to parse?\n\nDo NOT parse, period.\n\nIf three \"things\" that talk the Git protocol on the other end of the\nconnection gives \"Linux git/2.48.0\", and \"macOS libgit2/1.9.0\", and\n\"Windows git/2.47.1\" as their (enhanced) \"agent\" strings, there is\nno \"ah, this one is 1.9.0 which way older than 2.47.1 so it must be\nmissing features X and Y\" the users of the information are allowed\nto infer.\n\nJust take it as a single opaque string, and group identical ones.\n\nIn the above scenario, we found three different kinds now.  Maybe\nwe'll accumulate the counts and notice that there are N times as\nmany connections whose agent string begins with \"Windows\" as \"Linux\"\nand \"macOS\" combined or something.  That would be an offline\nanalysis, and forcing users to do the stats offline would reduce the\ntemptation to use it for purposes other than its intended one.\n\nYou may find \"ImNotTellingYou\" and may wonder what OS the user is\nreally using, but they do not want to tell you, so you honor their\nwish.\n\n> I don't see a good solution because\n> GIT_USER_AGENT could be used, and the config option to not show the OS\n> name could be used too.\n\nThat is a good privacy measure.\n\n> Also we don't know what could be in the \"version\" part. The doc says\n> that the agent part is typically of the form \"package/version\" but\n> doesn't require it.\n\nExactly.  I would think it is a feature, and the way to treat the\nstring in line with the philosophy behind that feature is to take it\nas a single opaque thing.\n\n\n"},{"id":"511587","messageId":"CAPSxiM9xO+GPNrScQS0C02qNJM8M+hOpEfkr0Vp1Tz3i_jkCjA@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqzfj77xd7.fsf@gitster.g","subject":"Re: [PATCH v3 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-31T19:42:34Z","receivedAt":"2025-01-31T19:42:45Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Fri, Jan 31, 2025 at 10:07 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > By the way, if we put the OS information in the \"agent\" capability,\n> > how do we separate it from the existing \"package/version\" content and\n> > make it easy to parse?\n>\n> Do NOT parse, period.\n>\n> If three \"things\" that talk the Git protocol on the other end of the\n> connection gives \"Linux git/2.48.0\", and \"macOS libgit2/1.9.0\", and\n> \"Windows git/2.47.1\" as their (enhanced) \"agent\" strings, there is\n> no \"ah, this one is 1.9.0 which way older than 2.47.1 so it must be\n> missing features X and Y\" the users of the information are allowed\n> to infer.\nHi Junio,\n\nDo you have any concerns \"git/2.47.1 Windows\" instead of\n\"Windows git/2.47.1\" ?\n\nThank you.\n>\n> Just take it as a single opaque string, and group identical ones.\n>\n> In the above scenario, we found three different kinds now.  Maybe\n> we'll accumulate the counts and notice that there are N times as\n> many connections whose agent string begins with \"Windows\" as \"Linux\"\n> and \"macOS\" combined or something.  That would be an offline\n> analysis, and forcing users to do the stats offline would reduce the\n> temptation to use it for purposes other than its intended one.\n>\n> You may find \"ImNotTellingYou\" and may wonder what OS the user is\n> really using, but they do not want to tell you, so you honor their\n> wish.\n>\n> > I don't see a good solution because\n> > GIT_USER_AGENT could be used, and the config option to not show the OS\n> > name could be used too.\n>\n> That is a good privacy measure.\n>\n> > Also we don't know what could be in the \"version\" part. The doc says\n> > that the agent part is typically of the form \"package/version\" but\n> > doesn't require it.\n>\n> Exactly.  I would think it is a feature, and the way to treat the\n> string in line with the philosophy behind that feature is to take it\n> as a single opaque thing.\n>\n>\n"},{"id":"511588","messageId":"CAPSxiM_eob5ygBoND9QR-bS073X-Jzqfg27Ec0LOFmjEesYDhQ@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqzfj77xd7.fsf@gitster.g","subject":"Re: [PATCH v3 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-01-31T19:46:04Z","receivedAt":"2025-01-31T19:46:16Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Fri, Jan 31, 2025 at 10:07 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n> > By the way, if we put the OS information in the \"agent\" capability,\n> > how do we separate it from the existing \"package/version\" content and\n> > make it easy to parse?\n>\n> Do NOT parse, period.\n>\n> If three \"things\" that talk the Git protocol on the other end of the\n> connection gives \"Linux git/2.48.0\", and \"macOS libgit2/1.9.0\", and\n> \"Windows git/2.47.1\" as their (enhanced) \"agent\" strings, there is\n> no \"ah, this one is 1.9.0 which way older than 2.47.1 so it must be\n> missing features X and Y\" the users of the information are allowed\n> to infer.\n>\n> Just take it as a single opaque string, and group identical ones.\n>\n> In the above scenario, we found three different kinds now.  Maybe\n> we'll accumulate the counts and notice that there are N times as\n> many connections whose agent string begins with \"Windows\" as \"Linux\"\n> and \"macOS\" combined or something.  That would be an offline\n> analysis, and forcing users to do the stats offline would reduce the\n> temptation to use it for purposes other than its intended one.\n>\n> You may find \"ImNotTellingYou\" and may wonder what OS the user is\n> really using, but they do not want to tell you, so you honor their\n> wish.\nWhile the current implementation allows user to specify this form of string\n i.e \"ImNotTellingYou\", for agent value, it is not mentioned in the docs,\nI will add in the next iteration.\n>\n> > I don't see a good solution because\n> > GIT_USER_AGENT could be used, and the config option to not show the OS\n> > name could be used too.\n>\n> That is a good privacy measure.\n>\n> > Also we don't know what could be in the \"version\" part. The doc says\n> > that the agent part is typically of the form \"package/version\" but\n> > doesn't require it.\n>\n> Exactly.  I would think it is a feature, and the way to treat the\n> string in line with the philosophy behind that feature is to take it\n> as a single opaque thing.\n>\n>\n"},{"id":"511592","messageId":"xmqqh65e7na1.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM9xO+GPNrScQS0C02qNJM8M+hOpEfkr0Vp1Tz3i_jkCjA@mail.gmail.com","subject":"Re: [PATCH v3 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-31T20:15:34Z","receivedAt":"2025-01-31T20:15:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Do you have any concerns \"git/2.47.1 Windows\" instead of\n> \"Windows git/2.47.1\" ?\n\nEither is fine.  I expect that\n\n (1) Implementors on _our_ side will do the sensible thing and\n     reviewers help them to make sure, where the definition of \"the\n     sensible thing\" will be that whatever order we pick, we\n     consistently use that same order.  If \"git/2.47.1 Windows\" is\n     how GfW identifies itself, \"git/2.48.1 Linux\" or \"git/2.49.0\n     macOS\" would be its contemporary counterparts, and _our_\n     binaries would not identify themselves as \"Linux git/2.49.0\".\n\n (2) Implementors of third-party reimplementations of Git will just\n     mimick what we will do, as long as we tell them our intention\n     (i.e. this is a single opaque unparsable string to be collected\n     for statistics, nothing more) clearly enough.\n\n (3) Most users are lazy and/or trusting enough that only a very few\n     minority privacy conscious folks would configure it away,\n     making their \"IamNotTellingYou\" agent string merely an\n     insignificant noise in the statistics.\n\n"},{"id":"511593","messageId":"xmqqcyg27n7j.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM_eob5ygBoND9QR-bS073X-Jzqfg27Ec0LOFmjEesYDhQ@mail.gmail.com","subject":"Re: [PATCH v3 0/6][Outreachy] Introduce os-version Capability with Configurable Options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-31T20:17:04Z","receivedAt":"2025-01-31T20:17:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n>> You may find \"ImNotTellingYou\" and may wonder what OS the user is\n>> really using, but they do not want to tell you, so you honor their\n>> wish.\n> While the current implementation allows user to specify this form of string\n>  i.e \"ImNotTellingYou\", for agent value, it is not mentioned in the docs,\n> I will add in the next iteration.\n\nOK.  You may want to wait before hearing other's opinions, though,\nfor at least the time it takes for the earth to rotate once.\n\nThanks.\n"},{"id":"511898","messageId":"20250205185246.111447-1-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250124122217.250925-7-usmanakinyemi202@gmail.com","subject":"[PATCH v4 0/6][Outreachy] extend agent capability to include OS name","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-05T18:52:30Z","receivedAt":"2025-02-05T18:52:53Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"For debugging, statistical analysis, and security purposes, it can\nbe valuable for Git servers to know the operating system the clients\nare using.\n\nFor example:\n- A server noticing that a client is using an old Git version with\nsecurity issues on one platform, like macOS, could verify if the\nuser is indeed running macOS before sending a message to upgrade.\"\n- Similarly, a server identifying a client that could benefit from\nan upgrade (e.g., for performance reasons) could better customize the\nmessage it sends to nudge the client to upgrade.\n\nOur current agent capability is in the form of \"package/version\" (e.g.,\n\"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\ni.e in the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\").\nThe operating system name is retrieved using the 'sysname' field of \nhe `uname(2)` system call or its equivalent.\n\nIncluding OS details in the agent capability simplifies implementation,\nmaintains backward compatibility, avoids introducing a new capability,\nencourages adoption across Git-compatible software, and enhances\ndebugging by providing complete environment information without affecting\nfunctionality.\n\nDue to privacy issues and concerns, let's add the `transfer.advertiseOSVersion`\nconfig option. It defaults to `true` and can be changed to `false`. When `true`,\nboth the client and server independently append their operating system name(os)\nto the `agent` capability value. The `agent` capability will now be in form of\n\"package/version os\" (e.g., \"git/1.8.3.1 Linux\"). When `false`, the `agent`\ncapability will be in the form of \"package/version\" e.g \"git/1.8.3.1\". The server's\nconfiguration is independent of the client's. Defaults to `true`. \n\nNote that, due to differences between `uname(1)` (command-line\nutility) and `uname(2)` (system call) outputs on Windows,\n`transfer.advertiseOSVersion` is set to false on Windows during\ntesting. See the message part of patch 5/6 for more details.\n\nMy mentor, Christian Couder, sent a previous patch series about this\nbefore. You can find it here\nhttps://lore.kernel.org/git/20240619125708.3719150-1-christian.couder@gmail.com/\n\nChanges since v3\n================\n - Dropped the last patch which introduced `os-version` capability. This\n   was as a result of discussion on the mailing list on why adding the\n   operating system name to the existing agent capability might be better.\n   I stated the reasons above and you can also check the discussion\n   here.\n   https://public-inbox.org/git/xmqqed0sxdiz.fsf@gitster.g/\n - Extend the agent capability to include the operating system name.\n\nUsman Akinyemi (6):\n  version: replace manual ASCII checks with isprint() for clarity\n  version: refactor redact_non_printables()\n  version: refactor get_uname_info()\n  version: extend get_uname_info() to hide system details\n  t5701: add setup test to remove side-effect dependency\n  agent: advertise OS name via agent capability\n\n Documentation/config/transfer.txt |  8 ++++\n Documentation/gitprotocol-v2.txt  | 15 ++++--\n builtin/bugreport.c               | 13 +----\n t/t5555-http-smart-common.sh      | 10 +++-\n t/t5701-git-serve.sh              | 19 ++++++--\n t/test-lib-functions.sh           |  8 ++++\n version.c                         | 79 +++++++++++++++++++++++++++++--\n version.h                         | 22 +++++++++\n 8 files changed, 149 insertions(+), 25 deletions(-)\n\nRange-diff versus v3:\n\n1:  82b62c5e66 = 1:  82b62c5e66 version: replace manual ASCII checks with isprint() for clarity\n2:  0a7d7ce871 = 2:  0a7d7ce871 version: refactor redact_non_printables()\n3:  0187db59a4 = 3:  0187db59a4 version: refactor get_uname_info()\n4:  d3a3573594 = 4:  d3a3573594 version: extend get_uname_info() to hide system details\n5:  d9edd2ffc8 ! 5:  3e0e98f23d t5701: add setup test to remove side-effect dependency\n    @@ t/t5701-git-serve.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n      \n     -test_expect_success 'test capability advertisement' '\n     +test_expect_success 'setup to generate files with expected content' '\n    -+\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_and_osversion &&\n    ++\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n     +\n      \ttest_oid_cache <<-EOF &&\n      \twrong_algo sha1:sha256\n    @@ t/t5701-git-serve.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n      \tcat >expect.base <<-EOF &&\n      \tversion 2\n     -\tagent=git/$(git version | cut -d\" \" -f3)\n    -+\t$(cat agent_and_osversion)\n    ++\t$(cat agent_capability)\n      \tls-refs=unborn\n      \tfetch=shallow wait-for-done\n      \tserver-option\n6:  351d1eeddb < -:  ---------- connect: advertise OS version\n-:  ---------- > 6:  67a2767026 agent: advertise OS name via agent capability\n\n-- \n2.48.1\n\n"},{"id":"511899","messageId":"20250205185246.111447-2-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250205185246.111447-1-usmanakinyemi202@gmail.com","subject":"[PATCH v4 1/6] version: replace manual ASCII checks with isprint() for clarity","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-05T18:52:31Z","receivedAt":"2025-02-05T18:52:57Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Since the isprint() function checks for printable characters, let's\nreplace the existing hardcoded ASCII checks with it. However, since\nthe original checks also handled spaces, we need to account for spaces\nexplicitly in the new check.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n version.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/version.c b/version.c\nindex 4d763ab48d..6cfbb8ca56 100644\n--- a/version.c\n+++ b/version.c\n@@ -2,6 +2,7 @@\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n+#include \"sane-ctype.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -29,7 +30,7 @@ const char *git_user_agent_sanitized(void)\n \t\tstrbuf_addstr(&buf, git_user_agent());\n \t\tstrbuf_trim(&buf);\n \t\tfor (size_t i = 0; i < buf.len; i++) {\n-\t\t\tif (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n+\t\t\tif (!isprint(buf.buf[i]) || buf.buf[i] == ' ')\n \t\t\t\tbuf.buf[i] = '.';\n \t\t}\n \t\tagent = buf.buf;\n-- \n2.48.1\n\n"},{"id":"511900","messageId":"20250205185246.111447-3-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250205185246.111447-1-usmanakinyemi202@gmail.com","subject":"[PATCH v4 2/6] version: refactor redact_non_printables()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-05T18:52:32Z","receivedAt":"2025-02-05T18:53:01Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"The git_user_agent_sanitized() function performs some sanitizing to\navoid special characters being sent over the line and possibly messing\nup with the protocol or with the parsing on the other side.\n\nLet's extract this sanitizing into a new redact_non_printables() function,\nas we will want to reuse it in a following patch.\n\nFor now the new redact_non_printables() function is still static as\nit's only needed locally.\n\nWhile at it, let's use strbuf_detach() to explicitly detach the string\ncontained by the 'buf' strbuf.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n version.c | 21 +++++++++++++++------\n 1 file changed, 15 insertions(+), 6 deletions(-)\n\ndiff --git a/version.c b/version.c\nindex 6cfbb8ca56..60df71fd0e 100644\n--- a/version.c\n+++ b/version.c\n@@ -7,6 +7,19 @@\n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n \n+/*\n+ * Trim and replace each character with ascii code below 32 or above\n+ * 127 (included) using a dot '.' character.\n+ */\n+static void redact_non_printables(struct strbuf *buf)\n+{\n+\tstrbuf_trim(buf);\n+\tfor (size_t i = 0; i < buf->len; i++) {\n+\t\tif (!isprint(buf->buf[i]) || buf->buf[i] == ' ')\n+\t\t\tbuf->buf[i] = '.';\n+\t}\n+}\n+\n const char *git_user_agent(void)\n {\n \tstatic const char *agent = NULL;\n@@ -28,12 +41,8 @@ const char *git_user_agent_sanitized(void)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&buf, git_user_agent());\n-\t\tstrbuf_trim(&buf);\n-\t\tfor (size_t i = 0; i < buf.len; i++) {\n-\t\t\tif (!isprint(buf.buf[i]) || buf.buf[i] == ' ')\n-\t\t\t\tbuf.buf[i] = '.';\n-\t\t}\n-\t\tagent = buf.buf;\n+\t\tredact_non_printables(&buf);\n+\t\tagent = strbuf_detach(&buf, NULL);\n \t}\n \n \treturn agent;\n-- \n2.48.1\n\n"},{"id":"511901","messageId":"20250205185246.111447-4-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250205185246.111447-1-usmanakinyemi202@gmail.com","subject":"[PATCH v4 3/6] version: refactor get_uname_info()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-05T18:52:33Z","receivedAt":"2025-02-05T18:53:06Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Some code from \"builtin/bugreport.c\" uses uname(2) to get system\ninformation.\n\nLet's refactor this code into a new get_uname_info() function, so\nthat we can reuse it in a following commit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c | 13 ++-----------\n version.c           | 20 ++++++++++++++++++++\n version.h           |  7 +++++++\n 3 files changed, 29 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 7c2df035c9..5e13d532a8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -12,10 +12,10 @@\n #include \"diagnose.h\"\n #include \"object-file.h\"\n #include \"setup.h\"\n+#include \"version.h\"\n \n static void get_system_info(struct strbuf *sys_info)\n {\n-\tstruct utsname uname_info;\n \tchar *shell = NULL;\n \n \t/* get git version from native cmd */\n@@ -24,16 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tif (uname(&uname_info))\n-\t\tstrbuf_addf(sys_info, _(\"uname() failed with error '%s' (%d)\\n\"),\n-\t\t\t    strerror(errno),\n-\t\t\t    errno);\n-\telse\n-\t\tstrbuf_addf(sys_info, \"%s %s %s %s\\n\",\n-\t\t\t    uname_info.sysname,\n-\t\t\t    uname_info.release,\n-\t\t\t    uname_info.version,\n-\t\t\t    uname_info.machine);\n+\tget_uname_info(sys_info);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 60df71fd0e..3ec8b8243d 100644\n--- a/version.c\n+++ b/version.c\n@@ -3,6 +3,7 @@\n #include \"version-def.h\"\n #include \"strbuf.h\"\n #include \"sane-ctype.h\"\n+#include \"gettext.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -47,3 +48,22 @@ const char *git_user_agent_sanitized(void)\n \n \treturn agent;\n }\n+\n+int get_uname_info(struct strbuf *buf)\n+{\n+\tstruct utsname uname_info;\n+\n+\tif (uname(&uname_info)) {\n+\t\tstrbuf_addf(buf, _(\"uname() failed with error '%s' (%d)\\n\"),\n+\t\t\t    strerror(errno),\n+\t\t\t    errno);\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t    uname_info.sysname,\n+\t\t    uname_info.release,\n+\t\t    uname_info.version,\n+\t\t    uname_info.machine);\n+\treturn 0;\n+}\ndiff --git a/version.h b/version.h\nindex 7c62e80577..afe3dbbab7 100644\n--- a/version.h\n+++ b/version.h\n@@ -7,4 +7,11 @@ extern const char git_built_from_commit_string[];\n const char *git_user_agent(void);\n const char *git_user_agent_sanitized(void);\n \n+/*\n+  Try to get information about the system using uname(2).\n+  Return -1 and put an error message into 'buf' in case of uname()\n+  error. Return 0 and put uname info into 'buf' otherwise.\n+*/\n+int get_uname_info(struct strbuf *buf);\n+\n #endif /* VERSION_H */\n-- \n2.48.1\n\n"},{"id":"511902","messageId":"20250205185246.111447-5-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250205185246.111447-1-usmanakinyemi202@gmail.com","subject":"[PATCH v4 4/6] version: extend get_uname_info() to hide system details","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-05T18:52:34Z","receivedAt":"2025-02-05T18:53:10Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently, get_uname_info() function provides the full OS information.\nIn a following commit, we will need it to provide only the OS name.\n\nLet's extend it to accept a \"full\" flag that makes it switch between\nproviding full OS information and providing only the OS name.\n\nWe may need to refactor this function in the future if an\n`osVersion.format` is added.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c |  2 +-\n version.c           | 16 +++++++++-------\n version.h           |  2 +-\n 3 files changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 5e13d532a8..e3288a86c8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -24,7 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tget_uname_info(sys_info);\n+\tget_uname_info(sys_info, 1);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 3ec8b8243d..d95221a72a 100644\n--- a/version.c\n+++ b/version.c\n@@ -49,7 +49,7 @@ const char *git_user_agent_sanitized(void)\n \treturn agent;\n }\n \n-int get_uname_info(struct strbuf *buf)\n+int get_uname_info(struct strbuf *buf, unsigned int full)\n {\n \tstruct utsname uname_info;\n \n@@ -59,11 +59,13 @@ int get_uname_info(struct strbuf *buf)\n \t\t\t    errno);\n \t\treturn -1;\n \t}\n-\n-\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n-\t\t    uname_info.sysname,\n-\t\t    uname_info.release,\n-\t\t    uname_info.version,\n-\t\t    uname_info.machine);\n+\tif (full)\n+\t\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t\t    uname_info.sysname,\n+\t\t\t    uname_info.release,\n+\t\t\t    uname_info.version,\n+\t\t\t    uname_info.machine);\n+\telse\n+\t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n \treturn 0;\n }\ndiff --git a/version.h b/version.h\nindex afe3dbbab7..5eb586c0bd 100644\n--- a/version.h\n+++ b/version.h\n@@ -12,6 +12,6 @@ const char *git_user_agent_sanitized(void);\n   Return -1 and put an error message into 'buf' in case of uname()\n   error. Return 0 and put uname info into 'buf' otherwise.\n */\n-int get_uname_info(struct strbuf *buf);\n+int get_uname_info(struct strbuf *buf, unsigned int full);\n \n #endif /* VERSION_H */\n-- \n2.48.1\n\n"},{"id":"511903","messageId":"20250205185246.111447-7-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250205185246.111447-1-usmanakinyemi202@gmail.com","subject":"[PATCH v4 6/6] agent: advertise OS name via agent capability","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-05T18:52:36Z","receivedAt":"2025-02-05T18:53:18Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"As some issues that can happen with a Git client can be operating system\nspecific, it can be useful for a server to know which OS a client is\nusing. In the same way it can be useful for a client to know which OS\na server is using.\n\nOur current agent capability is in the form of \"package/version\" (e.g.,\n\"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\ni.e in the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\").\n\nIncluding OS details in the agent capability simplifies implementation,\nmaintains backward compatibility, avoids introducing a new capability,\nencourages adoption across Git-compatible software, and enhances\ndebugging by providing complete environment information without affecting\nfunctionality.\n\nAdd the `transfer.advertiseOSInfo` config option to address privacy\nconcerns. It defaults to `true` and can be changed to `false`.\nWhen `true`, both the client and server independently append their\noperating system name(os) to the `agent` capability value. The `agent`\ncapability will now be in form of \"package/version os\" (e.g.,\n\"git/1.8.3.1 Linux\"). When `false`, the `agent` capability will be\nin the form of \"package/version\" e.g \"git/1.8.3.1\". The server's\nconfiguration is independent of the client's. Defaults to `true`.\nThe operating system name is retrieved using the 'sysname' field of\nthe `uname(2)` system call or its equivalent.\n\nHowever, there are differences between `uname(1)` (command-line utility)\nand `uname(2)` (system call) outputs on Windows. These discrepancies\ncomplicate testing on Windows platforms. For example:\n  - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n  .2024-02-14.20:17.UTC.x86_64\n  - `uname(2)` output: Windows.10.0.20348\n\nOn Windows, uname(2) is not actually system-supplied but is instead\nalready faked up by Git itself. We could have overcome the test issue\non Windows by implementing a new `uname` subcommand in `test-tool`\nusing uname(2), but except uname(2), which would be tested against\nitself, there would be nothing platform specific, so it's just simpler\nto disable the tests on Windows.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n Documentation/config/transfer.txt |  8 +++++++\n Documentation/gitprotocol-v2.txt  | 15 ++++++++-----\n t/t5555-http-smart-common.sh      | 10 ++++++++-\n t/t5701-git-serve.sh              |  9 +++++++-\n t/test-lib-functions.sh           |  8 +++++++\n version.c                         | 37 +++++++++++++++++++++++++++++++\n version.h                         | 15 +++++++++++++\n 7 files changed, 95 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt\nindex f1ce50f4a6..1e1dc849ef 100644\n--- a/Documentation/config/transfer.txt\n+++ b/Documentation/config/transfer.txt\n@@ -125,3 +125,11 @@ transfer.bundleURI::\n transfer.advertiseObjectInfo::\n \tWhen `true`, the `object-info` capability is advertised by\n \tservers. Defaults to false.\n+\n+transfer.advertiseOSInfo::\n+\tWhen `true`, both the client and server independently append their\n+\toperating system name (os) to the `agent` capability value. The `agent`\n+\tcapability will now be in form of \"package/version os\" (e.g.,\n+\t\"git/1.8.3.1 Linux\"). When `false`, the `agent` capability will be\n+\tin the form of \"package/version\" e.g \"git/1.8.3.1\". The server's\n+\tconfiguration is independent of the client's. Defaults to `true`.\ndiff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\nindex 1652fef3ae..8fab7d7d52 100644\n--- a/Documentation/gitprotocol-v2.txt\n+++ b/Documentation/gitprotocol-v2.txt\n@@ -184,11 +184,16 @@ form `agent=X`) to notify the client that the server is running version\n the `agent` capability with a value `Y` (in the form `agent=Y`) in its\n request to the server (but it MUST NOT do so if the server did not\n advertise the agent capability). The `X` and `Y` strings may contain any\n-printable ASCII characters except space (i.e., the byte range 32 < x <\n-127), and are typically of the form \"package/version\" (e.g.,\n-\"git/1.8.3.1\"). The agent strings are purely informative for statistics\n-and debugging purposes, and MUST NOT be used to programmatically assume\n-the presence or absence of particular features.\n+printable ASCII characters (i.e., the byte range 32 < x < 127), and are\n+typically of the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\")\n+where `os` is the operating system name (e.g., \"Linux\"). `X` and `Y` can\n+be configured using the GIT_USER_AGENT environment variable and it takes\n+priority. If `transfer.advertiseOSInfo` is `false` on the server, the server\n+omits the `os` from X. If it is `false` on the client, the client omits the\n+`os` from `Y`. The `os` is retrieved using the 'sysname' field of the `uname(2)`\n+system call or its equivalent. The agent strings are purely informative for\n+statistics and debugging purposes, and MUST NOT be used to programmatically\n+assume the presence or absence of particular features.\n \n ls-refs\n ~~~~~~~\ndiff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh\nindex e47ea1ad10..140a7f0ffb 100755\n--- a/t/t5555-http-smart-common.sh\n+++ b/t/t5555-http-smart-common.sh\n@@ -123,9 +123,17 @@ test_expect_success 'git receive-pack --advertise-refs: v1' '\n '\n \n test_expect_success 'git upload-pack --advertise-refs: v2' '\n+\tprintf \"agent=FAKE\" >agent_capability &&\n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tprintf \"\\n\" >>agent_capability &&\n+\t\tgit config transfer.advertiseOSInfo false\n+\telse\n+\t\tprintf \" %s\\n\" $(uname -s | test_redact_non_printables) >>agent_capability\n+\tfi &&\n \tcat >expect <<-EOF &&\n \tversion 2\n-\tagent=FAKE\n+\t$(cat agent_capability)\n \tls-refs=unborn\n \tfetch=shallow wait-for-done\n \tserver-option\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 4c24a188b9..a4c12372f8 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -8,13 +8,20 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n . ./test-lib.sh\n \n test_expect_success 'setup to generate files with expected content' '\n-\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n+\tprintf \"agent=git/%s\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n \n \ttest_oid_cache <<-EOF &&\n \twrong_algo sha1:sha256\n \twrong_algo sha256:sha1\n \tEOF\n \n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tprintf \"\\n\" >>agent_capability &&\n+\t\tgit config transfer.advertiseOSInfo false\n+\telse\n+\t\tprintf \" %s\\n\" $(uname -s | test_redact_non_printables) >>agent_capability\n+\tfi &&\n \tcat >expect.base <<-EOF &&\n \tversion 2\n \t$(cat agent_capability)\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 78e054ab50..3465904323 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -2007,3 +2007,11 @@ test_trailing_hash () {\n \t\ttest-tool hexdump |\n \t\tsed \"s/ //g\"\n }\n+\n+# Trim and replace each character with ascii code below 32 or above\n+# 127 (included) using a dot '.' character.\n+# Octal intervals \\001-\\040 and \\177-\\377\n+# correspond to decimal intervals 1-32 and 127-255\n+test_redact_non_printables () {\n+    tr -d \"\\n\\r\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n+}\ndiff --git a/version.c b/version.c\nindex d95221a72a..f0f936a75e 100644\n--- a/version.c\n+++ b/version.c\n@@ -1,9 +1,12 @@\n+#define USE_THE_REPOSITORY_VARIABLE\n+\n #include \"git-compat-util.h\"\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n #include \"sane-ctype.h\"\n #include \"gettext.h\"\n+#include \"config.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -43,6 +46,12 @@ const char *git_user_agent_sanitized(void)\n \n \t\tstrbuf_addstr(&buf, git_user_agent());\n \t\tredact_non_printables(&buf);\n+\t\t/* Add os name if the transfer.advertiseosinfo config is true */\n+\t\tif (advertise_os_info()) {\n+\t\t\t/* Add space to space character after git version string */\n+\t\t\tstrbuf_addch(&buf, ' ');\n+\t\t\tstrbuf_addstr(&buf, os_info_sanitized());\n+\t\t}\n \t\tagent = strbuf_detach(&buf, NULL);\n \t}\n \n@@ -69,3 +78,31 @@ int get_uname_info(struct strbuf *buf, unsigned int full)\n \t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n \treturn 0;\n }\n+\n+const char *os_info_sanitized(void)\n+{\n+\tstatic const char *os = NULL;\n+\n+\tif (!os) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tget_uname_info(&buf, 0);\n+\t\t/* Sanitize the os information immediately */\n+\t\tredact_non_printables(&buf);\n+\t\tos = strbuf_detach(&buf, NULL);\n+\t}\n+\n+\treturn os;\n+}\n+\n+int advertise_os_info(void)\n+{\n+\tstatic int transfer_advertise_os_info= -1;\n+\n+\tif (transfer_advertise_os_info == -1) {\n+\t\trepo_config_get_bool(the_repository, \"transfer.advertiseosinfo\", &transfer_advertise_os_info);\n+\t\t/* enabled by default */\n+\t\ttransfer_advertise_os_info = !!transfer_advertise_os_info;\n+\t}\n+\treturn transfer_advertise_os_info;\n+}\ndiff --git a/version.h b/version.h\nindex 5eb586c0bd..b2325865d7 100644\n--- a/version.h\n+++ b/version.h\n@@ -1,6 +1,8 @@\n #ifndef VERSION_H\n #define VERSION_H\n \n+struct repository;\n+\n extern const char git_version_string[];\n extern const char git_built_from_commit_string[];\n \n@@ -14,4 +16,17 @@ const char *git_user_agent_sanitized(void);\n */\n int get_uname_info(struct strbuf *buf, unsigned int full);\n \n+/*\n+  Retrieve, sanitize and cache operating system info for subsequent\n+  calls. Return a pointer to the sanitized operating system info\n+  string.\n+*/\n+const char *os_info_sanitized(void);\n+\n+/*\n+  Retrieve and cache transfer.advertiseosinfo config value. Return 1\n+  if true, 0 if false.\n+*/\n+int advertise_os_info(void);\n+\n #endif /* VERSION_H */\n-- \n2.48.1\n\n"},{"id":"511904","messageId":"20250205185246.111447-6-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250205185246.111447-1-usmanakinyemi202@gmail.com","subject":"[PATCH v4 5/6] t5701: add setup test to remove side-effect dependency","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-05T18:52:35Z","receivedAt":"2025-02-05T18:53:20Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently, the \"test capability advertisement\" test creates some files\nwith expected content which are used by other tests below it.\n\nTo remove that side-effect from this test, let's split up part of\nit into a \"setup\"-type test which creates the files with expected content\nwhich gets reused by multiple tests. This will be useful in a following\ncommit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n t/t5701-git-serve.sh | 12 +++++++++---\n 1 file changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex de904c1655..4c24a188b9 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -7,22 +7,28 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n \n-test_expect_success 'test capability advertisement' '\n+test_expect_success 'setup to generate files with expected content' '\n+\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n+\n \ttest_oid_cache <<-EOF &&\n \twrong_algo sha1:sha256\n \twrong_algo sha256:sha1\n \tEOF\n+\n \tcat >expect.base <<-EOF &&\n \tversion 2\n-\tagent=git/$(git version | cut -d\" \" -f3)\n+\t$(cat agent_capability)\n \tls-refs=unborn\n \tfetch=shallow wait-for-done\n \tserver-option\n \tobject-format=$(test_oid algo)\n \tEOF\n-\tcat >expect.trailer <<-EOF &&\n+\tcat >expect.trailer <<-EOF\n \t0000\n \tEOF\n+'\n+\n+test_expect_success 'test capability advertisement' '\n \tcat expect.base expect.trailer >expect &&\n \n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n-- \n2.48.1\n\n"},{"id":"511911","messageId":"xmqqy0ykkqqc.fsf@gitster.g","threadId":"62747","inReplyTo":"20250205185246.111447-7-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v4 6/6] agent: advertise OS name via agent capability","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-05T21:48:27Z","receivedAt":"2025-02-05T21:48:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> As some issues that can happen with a Git client can be operating system\n> specific, it can be useful for a server to know which OS a client is\n> using. In the same way it can be useful for a client to know which OS\n> a server is using.\n>\n> Our current agent capability is in the form of \"package/version\" (e.g.,\n> \"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\n> i.e in the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\").\n>\n> Including OS details in the agent capability simplifies implementation,\n> maintains backward compatibility, avoids introducing a new capability,\n> encourages adoption across Git-compatible software, and enhances\n> debugging by providing complete environment information without affecting\n> functionality.\n\nI obviously agree with the benefits enumerated in the above\nparagraph.  The simpler, the better.\n\nI however wonder ...\n\n> Add the `transfer.advertiseOSInfo` config option to address privacy\n> concerns. It defaults to `true` and can be changed to `false`.\n\n... if this configuration knob is at the right granularity.\n\nFor privacy concious folks, I would imagine that the distinction\nbetween \"git/1.8.3.1\" vs \"git/2.48.1\" would be something they do not\nwant to reveal equally as, if not more than, which Operating System\nthey are on.  Such a privacy concious user may already be using\nGIT_USER_AGENT environment variable to squelch it already, anyway.\n\nIf we were to give them an improvement in the area for privacy\nfeatures, I would think it would be to add a configuration variable\nto turn the agent off, instead of having to leave GIT_USER_AGENT\nenvironment variable set in the environment of their processes.\n\nOn the other hand, for the rest of us who think \"git/1.8.3.1 Linux\"\nis not too much of a secret, we do not need a knob to configure it\nbetween \"git/1.8.3.1\" and \"git/1.8.3.1 Linux\".\n\nSo, while I view some parts of the series would have been a good\nexercise to use various features (like config subsystem) from our\nAPI, I prefer if we kept the end-user interface not overly\ncustomizable (iow, without a config-knob, we do not need to add a\ncode to inspect the new configuration variable).\n\nAfter all, GIT_USER_AGENT let's you hide not just the OS part but\nany other things from the user-agent string already.\n\nI notice that unlike user_agent() vs user_agent_sanitized(), you\nonly have a single function for os_info(), which I think is a good\ndesign.  But if we were to go that route, shouldn't we call the\nfunction os_info(), not os_info_sanitized()?  The idea behind a\nsingle function is that you cannot obtain unsanitized version of\nos_info() out of the system at all, so what _sanitized() returns\nwould be what os_info() without _sanitized suffix would return to\nthe caller anyway.\n\nThanks.\n"},{"id":"511944","messageId":"CAPSxiM9Yejt+Cgu_ekuQwhduf=JEmS1s+T+nc--SvNQqkQE82g@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqy0ykkqqc.fsf@gitster.g","subject":"Re: [PATCH v4 6/6] agent: advertise OS name via agent capability","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-06T06:37:20Z","receivedAt":"2025-02-06T06:37:32Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Thu, Feb 6, 2025 at 3:18 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > As some issues that can happen with a Git client can be operating system\n> > specific, it can be useful for a server to know which OS a client is\n> > using. In the same way it can be useful for a client to know which OS\n> > a server is using.\n> >\n> > Our current agent capability is in the form of \"package/version\" (e.g.,\n> > \"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\n> > i.e in the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\").\n> >\n> > Including OS details in the agent capability simplifies implementation,\n> > maintains backward compatibility, avoids introducing a new capability,\n> > encourages adoption across Git-compatible software, and enhances\n> > debugging by providing complete environment information without affecting\n> > functionality.\n>\n> I obviously agree with the benefits enumerated in the above\n> paragraph.  The simpler, the better.\n>\n> I however wonder ...\n>\n> > Add the `transfer.advertiseOSInfo` config option to address privacy\n> > concerns. It defaults to `true` and can be changed to `false`.\n>\n> ... if this configuration knob is at the right granularity.\n>\n> For privacy concious folks, I would imagine that the distinction\n> between \"git/1.8.3.1\" vs \"git/2.48.1\" would be something they do not\n> want to reveal equally as, if not more than, which Operating System\n> they are on.  Such a privacy concious user may already be using\n> GIT_USER_AGENT environment variable to squelch it already, anyway.\n>\n> If we were to give them an improvement in the area for privacy\n> features, I would think it would be to add a configuration variable\n> to turn the agent off, instead of having to leave GIT_USER_AGENT\n> environment variable set in the environment of their processes.\n>\n> On the other hand, for the rest of us who think \"git/1.8.3.1 Linux\"\n> is not too much of a secret, we do not need a knob to configure it\n> between \"git/1.8.3.1\" and \"git/1.8.3.1 Linux\".\n>\n> So, while I view some parts of the series would have been a good\n> exercise to use various features (like config subsystem) from our\n> API, I prefer if we kept the end-user interface not overly\n> customizable (iow, without a config-knob, we do not need to add a\n> code to inspect the new configuration variable).\n>\n> After all, GIT_USER_AGENT let's you hide not just the OS part but\n> any other things from the user-agent string already.\nHi Junio,\n\nThe conclusion now is that we should not add any config option since\nthe GIT_USER_AGENT could actually allow the user to hide whatever\ninfo they do not want to share ?\n>\n> I notice that unlike user_agent() vs user_agent_sanitized(), you\n> only have a single function for os_info(), which I think is a good\n> design.  But if we were to go that route, shouldn't we call the\n> function os_info(), not os_info_sanitized()?  The idea behind a\n> single function is that you cannot obtain unsanitized version of\n> os_info() out of the system at all, so what _sanitized() returns\n> would be what os_info() without _sanitized suffix would return to\n> the caller anyway.\nYeah, we can change it to os_info, if in the future someone needs\nthe os information in some way, they could use the get_uname_info.\n\nThanks.\n>\n> Thanks.\n"},{"id":"512003","messageId":"xmqqo6zfjec5.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM9Yejt+Cgu_ekuQwhduf=JEmS1s+T+nc--SvNQqkQE82g@mail.gmail.com","subject":"Re: [PATCH v4 6/6] agent: advertise OS name via agent capability","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-06T15:13:46Z","receivedAt":"2025-02-06T15:13:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n>> I obviously agree with the benefits enumerated in the above\n>> paragraph.  The simpler, the better.\n>>\n>> I however wonder ...\n>>\n>> > Add the `transfer.advertiseOSInfo` config option to address privacy\n>> > concerns. It defaults to `true` and can be changed to `false`.\n>>\n>> ... if this configuration knob is at the right granularity.\n>\n> The conclusion now is that we should not add any config option since\n> the GIT_USER_AGENT could actually allow the user to hide whatever\n> info they do not want to share ?\n\nI wouldn't call that a conclusion (as you and I are the only people\nwho expressed their opinion on this so far), but that is my take on\nit---tweaking only the (os) part in the agent string with a config\nsmells like the tweakability is at a wrong level.\n\n"},{"id":"512108","messageId":"CAPSxiM_y6gUQVNt8kZXfrn2dxpM7RNNg5LZz0dZy+9nYy=hcgg@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqo6zfjec5.fsf@gitster.g","subject":"Re: [PATCH v4 6/6] agent: advertise OS name via agent capability","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-07T17:27:19Z","receivedAt":"2025-02-07T17:27:32Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Thu, Feb 6, 2025 at 8:43 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> >> I obviously agree with the benefits enumerated in the above\n> >> paragraph.  The simpler, the better.\n> >>\n> >> I however wonder ...\n> >>\n> >> > Add the `transfer.advertiseOSInfo` config option to address privacy\n> >> > concerns. It defaults to `true` and can be changed to `false`.\n> >>\n> >> ... if this configuration knob is at the right granularity.\n> >\n> > The conclusion now is that we should not add any config option since\n> > the GIT_USER_AGENT could actually allow the user to hide whatever\n> > info they do not want to share ?\n>\n> I wouldn't call that a conclusion (as you and I are the only people\n> who expressed their opinion on this so far), but that is my take on\n> it---tweaking only the (os) part in the agent string with a config\n> smells like the tweakability is at a wrong level.\n>\nHi Junio,\n\nI was actually thinking about this inside the bathroom when it\noccurred to me that,\naccording to the current implementation, GIT_USER_AGENT will not allow the user\nto specify an empty string at all. It is either you specify some value\nor we decide for\nyou. I think we can add the config at a level that can disable the\nagent capability completely\ninstead of only tweaking the (os) part.\n\nWith this, the user can disable the agent  capability completely,\nshare whatever string they want using the GIT_USER_AGENT.\n\nWhat do you think ?\n\nThanks.\n"},{"id":"512112","messageId":"xmqqy0yhfxjj.fsf@gitster.g","threadId":"62747","inReplyTo":"CAPSxiM_y6gUQVNt8kZXfrn2dxpM7RNNg5LZz0dZy+9nYy=hcgg@mail.gmail.com","subject":"Re: [PATCH v4 6/6] agent: advertise OS name via agent capability","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-07T17:57:04Z","receivedAt":"2025-02-07T17:57:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> I was actually thinking about this inside the bathroom when it\n> occurred to me that,\n> according to the current implementation, GIT_USER_AGENT will not allow the user\n> to specify an empty string at all. It is either you specify some value\n> or we decide for\n> you.\n\nYes.  GIT_USER_AGENT=ImNotTellingYou would work just fine for\nprivacy concious folks.\n\n> I think we can add the config at a level that can disable the\n> agent capability completely\n> instead of only tweaking the (os) part.\n\nYes, go back to a few messages you received from me earlier; it is\nalready there ;-)\n\n    If we were to give them an improvement in the area for privacy\n    features, I would think it would be to add a configuration variable\n    to turn the agent off, instead of having to leave GIT_USER_AGENT\n    environment variable set in the environment of their processes.\n\n"},{"id":"512115","messageId":"CAPSxiM_TUyZqrKEaDBz6AXBg3hamPOaVvY5RryUUMUE7RDU1cg@mail.gmail.com","threadId":"62747","inReplyTo":"xmqqy0ykkqqc.fsf@gitster.g","subject":"Re: [PATCH v4 6/6] agent: advertise OS name via agent capability","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-07T19:25:57Z","receivedAt":"2025-02-07T19:26:09Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Thu, Feb 6, 2025 at 3:18 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n>\n> > As some issues that can happen with a Git client can be operating system\n> > specific, it can be useful for a server to know which OS a client is\n> > using. In the same way it can be useful for a client to know which OS\n> > a server is using.\n> >\n> > Our current agent capability is in the form of \"package/version\" (e.g.,\n> > \"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\n> > i.e in the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\").\n> >\n> > Including OS details in the agent capability simplifies implementation,\n> > maintains backward compatibility, avoids introducing a new capability,\n> > encourages adoption across Git-compatible software, and enhances\n> > debugging by providing complete environment information without affecting\n> > functionality.\n>\n> I obviously agree with the benefits enumerated in the above\n> paragraph.  The simpler, the better.\n>\n> I however wonder ...\n>\n> > Add the `transfer.advertiseOSInfo` config option to address privacy\n> > concerns. It defaults to `true` and can be changed to `false`.\n>\n> ... if this configuration knob is at the right granularity.\n>\n> For privacy concious folks, I would imagine that the distinction\n> between \"git/1.8.3.1\" vs \"git/2.48.1\" would be something they do not\n> want to reveal equally as, if not more than, which Operating System\n> they are on.  Such a privacy concious user may already be using\n> GIT_USER_AGENT environment variable to squelch it already, anyway.\n>\n> If we were to give them an improvement in the area for privacy\n> features, I would think it would be to add a configuration variable\n> to turn the agent off, instead of having to leave GIT_USER_AGENT\n> environment variable set in the environment of their processes.\n>\n> On the other hand, for the rest of us who think \"git/1.8.3.1 Linux\"\n> is not too much of a secret, we do not need a knob to configure it\n> between \"git/1.8.3.1\" and \"git/1.8.3.1 Linux\".\n>\n> So, while I view some parts of the series would have been a good\n> exercise to use various features (like config subsystem) from our\n> API, I prefer if we kept the end-user interface not overly\n> customizable (iow, without a config-knob, we do not need to add a\n> code to inspect the new configuration variable).\nHi Junio,\n\nYeah, I believe appending the (os) to the agent might attract the attention\nof some set of privacy conscious users who might not really be worried\nabout the agent when it was just a string like \"git/1.8.3.1\". While\nGIT_USER_AGENT\ncan be used to suppress it, I believe having a dedicated config option to\ncompletely disable the agent is a more user-friendly and persistent approach\nthan relying solely on environment variables.\nRequiring users to manually set GIT_USER_AGENT (since it cannot be empty)\ncan feel cumbersome, making a config option a cleaner and more\nintuitive alternative.\nAdditionally, having a config option would provide a consistent\nmechanism in case\nsimilar privacy-related features are introduced in the future.\n\nThis is me convincing you that having a config option to disable the\nagent is more user friendly than having only the environment variable for\nusers who do not want to share anything at all.\n\nWhat do you think ? Or maybe there is strong reason for having the\nGIT_USER_AGENT in the first place and not having a config to disable\nthe agent capability?\n\nWe could also wait for input from other community members.\n\nThanks,\nUsman\n\n>\n> After all, GIT_USER_AGENT let's you hide not just the OS part but\n> any other things from the user-agent string already.\n>\n> I notice that unlike user_agent() vs user_agent_sanitized(), you\n> only have a single function for os_info(), which I think is a good\n> design.  But if we were to go that route, shouldn't we call the\n> function os_info(), not os_info_sanitized()?  The idea behind a\n> single function is that you cannot obtain unsanitized version of\n> os_info() out of the system at all, so what _sanitized() returns\n> would be what os_info() without _sanitized suffix would return to\n> the caller anyway.\n>\n> Thanks.\n"},{"id":"512418","messageId":"20250214123734.1403120-1-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250205185246.111447-1-usmanakinyemi202@gmail.com","subject":"[PATCH v5 0/6][Outreachy] extend agent capability to include OS name","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-14T12:36:10Z","receivedAt":"2025-02-14T12:37:41Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"For debugging, statistical analysis, and security purposes, it can\nbe valuable for Git servers to know the operating system the clients\nare using.\n\nFor example:\n- A server noticing that a client is using an old Git version with\nsecurity issues on one platform, like macOS, could verify if the\nuser is indeed running macOS before sending a message to upgrade.\"\n- Similarly, a server identifying a client that could benefit from\nan upgrade (e.g., for performance reasons) could better customize the\nmessage it sends to nudge the client to upgrade.\n\nOur current agent capability is in the form of \"package/version\" (e.g.,\n\"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\ni.e in the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\").\nThe operating system name is retrieved using the 'sysname' field of \nhe `uname(2)` system call or its equivalent.\n\nIncluding OS details in the agent capability simplifies implementation,\nmaintains backward compatibility, avoids introducing a new capability,\nencourages adoption across Git-compatible software, and enhances\ndebugging by providing complete environment information without affecting\nfunctionality.\n\nNote that, due to differences between `uname(1)` (command-line\nutility) and `uname(2)` (system call) outputs on Windows,\n`transfer.advertiseOSVersion` is set to false on Windows during\ntesting. See the message part of patch 5/6 for more details.\n\nMy mentor, Christian Couder, sent a previous patch series about this\nbefore. You can find it here\nhttps://lore.kernel.org/git/20240619125708.3719150-1-christian.couder@gmail.com/\n\nChanges since v4\n================\n - Remove the implementation of transfer.advertiseOSInfo config. \n - Update the documentation.\n - Move the `os_info()` function into \"version.c\" file.\n\nUsman Akinyemi (6):\n  version: replace manual ASCII checks with isprint() for clarity\n  version: refactor redact_non_printables()\n  version: refactor get_uname_info()\n  version: extend get_uname_info() to hide system details\n  t5701: add setup test to remove side-effect dependency\n  agent: advertise OS name via agent capability\n\n Documentation/gitprotocol-v2.txt | 13 +++---\n builtin/bugreport.c              | 13 +-----\n t/t5701-git-serve.sh             | 26 ++++++++++--\n t/test-lib-functions.sh          |  8 ++++\n version.c                        | 69 +++++++++++++++++++++++++++++---\n version.h                        | 10 +++++\n 6 files changed, 115 insertions(+), 24 deletions(-)\n\nRange-diff versus v4:\n\n1:  82b62c5e66 = 1:  82b62c5e66 version: replace manual ASCII checks with isprint() for clarity\n2:  0a7d7ce871 = 2:  0a7d7ce871 version: refactor redact_non_printables()\n3:  0187db59a4 = 3:  0187db59a4 version: refactor get_uname_info()\n4:  d3a3573594 = 4:  d3a3573594 version: extend get_uname_info() to hide system details\n5:  3e0e98f23d = 5:  3e0e98f23d t5701: add setup test to remove side-effect dependency\n6:  67a2767026 ! 6:  bcd1130aa1 agent: advertise OS name via agent capability\n    @@ Commit message\n         maintains backward compatibility, avoids introducing a new capability,\n         encourages adoption across Git-compatible software, and enhances\n         debugging by providing complete environment information without affecting\n    -    functionality.\n    -\n    -    Add the `transfer.advertiseOSInfo` config option to address privacy\n    -    concerns. It defaults to `true` and can be changed to `false`.\n    -    When `true`, both the client and server independently append their\n    -    operating system name(os) to the `agent` capability value. The `agent`\n    -    capability will now be in form of \"package/version os\" (e.g.,\n    -    \"git/1.8.3.1 Linux\"). When `false`, the `agent` capability will be\n    -    in the form of \"package/version\" e.g \"git/1.8.3.1\". The server's\n    -    configuration is independent of the client's. Defaults to `true`.\n    -    The operating system name is retrieved using the 'sysname' field of\n    -    the `uname(2)` system call or its equivalent.\n    +    functionality. The operating system name is retrieved using the 'sysname'\n    +    field of the `uname(2)` system call or its equivalent.\n     \n         However, there are differences between `uname(1)` (command-line utility)\n         and `uname(2)` (system call) outputs on Windows. These discrepancies\n    @@ Commit message\n         Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n         Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n     \n    - ## Documentation/config/transfer.txt ##\n    -@@ Documentation/config/transfer.txt: transfer.bundleURI::\n    - transfer.advertiseObjectInfo::\n    - \tWhen `true`, the `object-info` capability is advertised by\n    - \tservers. Defaults to false.\n    -+\n    -+transfer.advertiseOSInfo::\n    -+\tWhen `true`, both the client and server independently append their\n    -+\toperating system name (os) to the `agent` capability value. The `agent`\n    -+\tcapability will now be in form of \"package/version os\" (e.g.,\n    -+\t\"git/1.8.3.1 Linux\"). When `false`, the `agent` capability will be\n    -+\tin the form of \"package/version\" e.g \"git/1.8.3.1\". The server's\n    -+\tconfiguration is independent of the client's. Defaults to `true`.\n    -\n      ## Documentation/gitprotocol-v2.txt ##\n     @@ Documentation/gitprotocol-v2.txt: form `agent=X`) to notify the client that the server is running version\n      the `agent` capability with a value `Y` (in the form `agent=Y`) in its\n    @@ Documentation/gitprotocol-v2.txt: form `agent=X`) to notify the client that the\n     -\"git/1.8.3.1\"). The agent strings are purely informative for statistics\n     -and debugging purposes, and MUST NOT be used to programmatically assume\n     -the presence or absence of particular features.\n    -+printable ASCII characters (i.e., the byte range 32 < x < 127), and are\n    ++printable ASCII characters (i.e., the byte range 31 < x < 127), and are\n     +typically of the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\")\n     +where `os` is the operating system name (e.g., \"Linux\"). `X` and `Y` can\n     +be configured using the GIT_USER_AGENT environment variable and it takes\n    -+priority. If `transfer.advertiseOSInfo` is `false` on the server, the server\n    -+omits the `os` from X. If it is `false` on the client, the client omits the\n    -+`os` from `Y`. The `os` is retrieved using the 'sysname' field of the `uname(2)`\n    ++priority. The `os` is retrieved using the 'sysname' field of the `uname(2)`\n     +system call or its equivalent. The agent strings are purely informative for\n     +statistics and debugging purposes, and MUST NOT be used to programmatically\n     +assume the presence or absence of particular features.\n    @@ Documentation/gitprotocol-v2.txt: form `agent=X`) to notify the client that the\n      ls-refs\n      ~~~~~~~\n     \n    - ## t/t5555-http-smart-common.sh ##\n    -@@ t/t5555-http-smart-common.sh: test_expect_success 'git receive-pack --advertise-refs: v1' '\n    - '\n    - \n    - test_expect_success 'git upload-pack --advertise-refs: v2' '\n    -+\tprintf \"agent=FAKE\" >agent_capability &&\n    -+\tif test_have_prereq WINDOWS\n    -+\tthen\n    -+\t\tprintf \"\\n\" >>agent_capability &&\n    -+\t\tgit config transfer.advertiseOSInfo false\n    -+\telse\n    -+\t\tprintf \" %s\\n\" $(uname -s | test_redact_non_printables) >>agent_capability\n    -+\tfi &&\n    - \tcat >expect <<-EOF &&\n    - \tversion 2\n    --\tagent=FAKE\n    -+\t$(cat agent_capability)\n    - \tls-refs=unborn\n    - \tfetch=shallow wait-for-done\n    - \tserver-option\n    -\n      ## t/t5701-git-serve.sh ##\n     @@ t/t5701-git-serve.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n      . ./test-lib.sh\n    @@ t/t5701-git-serve.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n      \n     +\tif test_have_prereq WINDOWS\n     +\tthen\n    -+\t\tprintf \"\\n\" >>agent_capability &&\n    -+\t\tgit config transfer.advertiseOSInfo false\n    ++\t\tprintf \"agent=FAKE\\n\" >agent_capability\n     +\telse\n     +\t\tprintf \" %s\\n\" $(uname -s | test_redact_non_printables) >>agent_capability\n     +\tfi &&\n      \tcat >expect.base <<-EOF &&\n      \tversion 2\n      \t$(cat agent_capability)\n    +@@ t/t5701-git-serve.sh: test_expect_success 'setup to generate files with expected content' '\n    + test_expect_success 'test capability advertisement' '\n    + \tcat expect.base expect.trailer >expect &&\n    + \n    ++\tif test_have_prereq WINDOWS\n    ++\tthen\n    ++\t\tGIT_USER_AGENT=FAKE && export GIT_USER_AGENT\n    ++\tfi &&\n    + \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n    + \t\t--advertise-capabilities >out &&\n    + \ttest-tool pkt-line unpack <out >actual &&\n    +@@ t/t5701-git-serve.sh: test_expect_success 'test capability advertisement with uploadpack.advertiseBund\n    + \t    expect.extra \\\n    + \t    expect.trailer >expect &&\n    + \n    ++\tif test_have_prereq WINDOWS\n    ++\tthen\n    ++\t\tGIT_USER_AGENT=FAKE && export GIT_USER_AGENT\n    ++\tfi &&\n    + \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n    + \t\t--advertise-capabilities >out &&\n    + \ttest-tool pkt-line unpack <out >actual &&\n     \n      ## t/test-lib-functions.sh ##\n     @@ t/test-lib-functions.sh: test_trailing_hash () {\n    @@ version.c\n      #include \"version.h\"\n      #include \"version-def.h\"\n      #include \"strbuf.h\"\n    - #include \"sane-ctype.h\"\n    +-#include \"sane-ctype.h\"\n      #include \"gettext.h\"\n    -+#include \"config.h\"\n      \n      const char git_version_string[] = GIT_VERSION;\n    - const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n    -@@ version.c: const char *git_user_agent_sanitized(void)\n    - \n    - \t\tstrbuf_addstr(&buf, git_user_agent());\n    - \t\tredact_non_printables(&buf);\n    -+\t\t/* Add os name if the transfer.advertiseosinfo config is true */\n    -+\t\tif (advertise_os_info()) {\n    -+\t\t\t/* Add space to space character after git version string */\n    -+\t\t\tstrbuf_addch(&buf, ' ');\n    -+\t\t\tstrbuf_addstr(&buf, os_info_sanitized());\n    -+\t\t}\n    - \t\tagent = strbuf_detach(&buf, NULL);\n    - \t}\n    - \n    -@@ version.c: int get_uname_info(struct strbuf *buf, unsigned int full)\n    - \t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n    - \treturn 0;\n    +@@ version.c: const char *git_user_agent(void)\n    + \treturn agent;\n      }\n    -+\n    -+const char *os_info_sanitized(void)\n    + \n    ++/*\n    ++  Retrieve, sanitize and cache operating system info for subsequent\n    ++  calls. Return a pointer to the sanitized operating system info\n    ++  string.\n    ++*/\n    ++static const char *os_info(void)\n     +{\n     +\tstatic const char *os = NULL;\n     +\n    @@ version.c: int get_uname_info(struct strbuf *buf, unsigned int full)\n     +\treturn os;\n     +}\n     +\n    -+int advertise_os_info(void)\n    -+{\n    -+\tstatic int transfer_advertise_os_info= -1;\n    + const char *git_user_agent_sanitized(void)\n    + {\n    + \tstatic const char *agent = NULL;\n    +@@ version.c: const char *git_user_agent_sanitized(void)\n    + \n    + \t\tstrbuf_addstr(&buf, git_user_agent());\n    + \t\tredact_non_printables(&buf);\n     +\n    -+\tif (transfer_advertise_os_info == -1) {\n    -+\t\trepo_config_get_bool(the_repository, \"transfer.advertiseosinfo\", &transfer_advertise_os_info);\n    -+\t\t/* enabled by default */\n    -+\t\ttransfer_advertise_os_info = !!transfer_advertise_os_info;\n    -+\t}\n    -+\treturn transfer_advertise_os_info;\n    -+}\n    ++\t\tif (!getenv(\"GIT_USER_AGENT\")) {\n    ++\t\t\tstrbuf_addch(&buf, ' ');\n    ++\t\t\tstrbuf_addstr(&buf, os_info());\n    ++\t\t}\n    + \t\tagent = strbuf_detach(&buf, NULL);\n    + \t}\n    + \n     \n      ## version.h ##\n     @@\n    @@ version.h: const char *git_user_agent_sanitized(void);\n      */\n      int get_uname_info(struct strbuf *buf, unsigned int full);\n      \n    -+/*\n    -+  Retrieve, sanitize and cache operating system info for subsequent\n    -+  calls. Return a pointer to the sanitized operating system info\n    -+  string.\n    -+*/\n    -+const char *os_info_sanitized(void);\n    -+\n    -+/*\n    -+  Retrieve and cache transfer.advertiseosinfo config value. Return 1\n    -+  if true, 0 if false.\n    -+*/\n    -+int advertise_os_info(void);\n     +\n      #endif /* VERSION_H */\n\n-- \n2.48.1\n\n"},{"id":"512419","messageId":"20250214123734.1403120-2-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250214123734.1403120-1-usmanakinyemi202@gmail.com","subject":"[PATCH v5 1/6] version: replace manual ASCII checks with isprint() for clarity","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-14T12:36:11Z","receivedAt":"2025-02-14T12:37:45Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Since the isprint() function checks for printable characters, let's\nreplace the existing hardcoded ASCII checks with it. However, since\nthe original checks also handled spaces, we need to account for spaces\nexplicitly in the new check.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n version.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/version.c b/version.c\nindex 4d763ab48d..6cfbb8ca56 100644\n--- a/version.c\n+++ b/version.c\n@@ -2,6 +2,7 @@\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n+#include \"sane-ctype.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -29,7 +30,7 @@ const char *git_user_agent_sanitized(void)\n \t\tstrbuf_addstr(&buf, git_user_agent());\n \t\tstrbuf_trim(&buf);\n \t\tfor (size_t i = 0; i < buf.len; i++) {\n-\t\t\tif (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n+\t\t\tif (!isprint(buf.buf[i]) || buf.buf[i] == ' ')\n \t\t\t\tbuf.buf[i] = '.';\n \t\t}\n \t\tagent = buf.buf;\n-- \n2.48.1\n\n"},{"id":"512420","messageId":"20250214123734.1403120-3-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250214123734.1403120-1-usmanakinyemi202@gmail.com","subject":"[PATCH v5 2/6] version: refactor redact_non_printables()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-14T12:36:12Z","receivedAt":"2025-02-14T12:37:49Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"The git_user_agent_sanitized() function performs some sanitizing to\navoid special characters being sent over the line and possibly messing\nup with the protocol or with the parsing on the other side.\n\nLet's extract this sanitizing into a new redact_non_printables() function,\nas we will want to reuse it in a following patch.\n\nFor now the new redact_non_printables() function is still static as\nit's only needed locally.\n\nWhile at it, let's use strbuf_detach() to explicitly detach the string\ncontained by the 'buf' strbuf.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n version.c | 21 +++++++++++++++------\n 1 file changed, 15 insertions(+), 6 deletions(-)\n\ndiff --git a/version.c b/version.c\nindex 6cfbb8ca56..60df71fd0e 100644\n--- a/version.c\n+++ b/version.c\n@@ -7,6 +7,19 @@\n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n \n+/*\n+ * Trim and replace each character with ascii code below 32 or above\n+ * 127 (included) using a dot '.' character.\n+ */\n+static void redact_non_printables(struct strbuf *buf)\n+{\n+\tstrbuf_trim(buf);\n+\tfor (size_t i = 0; i < buf->len; i++) {\n+\t\tif (!isprint(buf->buf[i]) || buf->buf[i] == ' ')\n+\t\t\tbuf->buf[i] = '.';\n+\t}\n+}\n+\n const char *git_user_agent(void)\n {\n \tstatic const char *agent = NULL;\n@@ -28,12 +41,8 @@ const char *git_user_agent_sanitized(void)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&buf, git_user_agent());\n-\t\tstrbuf_trim(&buf);\n-\t\tfor (size_t i = 0; i < buf.len; i++) {\n-\t\t\tif (!isprint(buf.buf[i]) || buf.buf[i] == ' ')\n-\t\t\t\tbuf.buf[i] = '.';\n-\t\t}\n-\t\tagent = buf.buf;\n+\t\tredact_non_printables(&buf);\n+\t\tagent = strbuf_detach(&buf, NULL);\n \t}\n \n \treturn agent;\n-- \n2.48.1\n\n"},{"id":"512421","messageId":"20250214123734.1403120-4-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250214123734.1403120-1-usmanakinyemi202@gmail.com","subject":"[PATCH v5 3/6] version: refactor get_uname_info()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-14T12:36:13Z","receivedAt":"2025-02-14T12:37:53Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Some code from \"builtin/bugreport.c\" uses uname(2) to get system\ninformation.\n\nLet's refactor this code into a new get_uname_info() function, so\nthat we can reuse it in a following commit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c | 13 ++-----------\n version.c           | 20 ++++++++++++++++++++\n version.h           |  7 +++++++\n 3 files changed, 29 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 7c2df035c9..5e13d532a8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -12,10 +12,10 @@\n #include \"diagnose.h\"\n #include \"object-file.h\"\n #include \"setup.h\"\n+#include \"version.h\"\n \n static void get_system_info(struct strbuf *sys_info)\n {\n-\tstruct utsname uname_info;\n \tchar *shell = NULL;\n \n \t/* get git version from native cmd */\n@@ -24,16 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tif (uname(&uname_info))\n-\t\tstrbuf_addf(sys_info, _(\"uname() failed with error '%s' (%d)\\n\"),\n-\t\t\t    strerror(errno),\n-\t\t\t    errno);\n-\telse\n-\t\tstrbuf_addf(sys_info, \"%s %s %s %s\\n\",\n-\t\t\t    uname_info.sysname,\n-\t\t\t    uname_info.release,\n-\t\t\t    uname_info.version,\n-\t\t\t    uname_info.machine);\n+\tget_uname_info(sys_info);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 60df71fd0e..3ec8b8243d 100644\n--- a/version.c\n+++ b/version.c\n@@ -3,6 +3,7 @@\n #include \"version-def.h\"\n #include \"strbuf.h\"\n #include \"sane-ctype.h\"\n+#include \"gettext.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -47,3 +48,22 @@ const char *git_user_agent_sanitized(void)\n \n \treturn agent;\n }\n+\n+int get_uname_info(struct strbuf *buf)\n+{\n+\tstruct utsname uname_info;\n+\n+\tif (uname(&uname_info)) {\n+\t\tstrbuf_addf(buf, _(\"uname() failed with error '%s' (%d)\\n\"),\n+\t\t\t    strerror(errno),\n+\t\t\t    errno);\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t    uname_info.sysname,\n+\t\t    uname_info.release,\n+\t\t    uname_info.version,\n+\t\t    uname_info.machine);\n+\treturn 0;\n+}\ndiff --git a/version.h b/version.h\nindex 7c62e80577..afe3dbbab7 100644\n--- a/version.h\n+++ b/version.h\n@@ -7,4 +7,11 @@ extern const char git_built_from_commit_string[];\n const char *git_user_agent(void);\n const char *git_user_agent_sanitized(void);\n \n+/*\n+  Try to get information about the system using uname(2).\n+  Return -1 and put an error message into 'buf' in case of uname()\n+  error. Return 0 and put uname info into 'buf' otherwise.\n+*/\n+int get_uname_info(struct strbuf *buf);\n+\n #endif /* VERSION_H */\n-- \n2.48.1\n\n"},{"id":"512422","messageId":"20250214123734.1403120-5-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250214123734.1403120-1-usmanakinyemi202@gmail.com","subject":"[PATCH v5 4/6] version: extend get_uname_info() to hide system details","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-14T12:36:14Z","receivedAt":"2025-02-14T12:37:58Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently, get_uname_info() function provides the full OS information.\nIn a following commit, we will need it to provide only the OS name.\n\nLet's extend it to accept a \"full\" flag that makes it switch between\nproviding full OS information and providing only the OS name.\n\nWe may need to refactor this function in the future if an\n`osVersion.format` is added.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c |  2 +-\n version.c           | 16 +++++++++-------\n version.h           |  2 +-\n 3 files changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 5e13d532a8..e3288a86c8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -24,7 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tget_uname_info(sys_info);\n+\tget_uname_info(sys_info, 1);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 3ec8b8243d..d95221a72a 100644\n--- a/version.c\n+++ b/version.c\n@@ -49,7 +49,7 @@ const char *git_user_agent_sanitized(void)\n \treturn agent;\n }\n \n-int get_uname_info(struct strbuf *buf)\n+int get_uname_info(struct strbuf *buf, unsigned int full)\n {\n \tstruct utsname uname_info;\n \n@@ -59,11 +59,13 @@ int get_uname_info(struct strbuf *buf)\n \t\t\t    errno);\n \t\treturn -1;\n \t}\n-\n-\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n-\t\t    uname_info.sysname,\n-\t\t    uname_info.release,\n-\t\t    uname_info.version,\n-\t\t    uname_info.machine);\n+\tif (full)\n+\t\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t\t    uname_info.sysname,\n+\t\t\t    uname_info.release,\n+\t\t\t    uname_info.version,\n+\t\t\t    uname_info.machine);\n+\telse\n+\t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n \treturn 0;\n }\ndiff --git a/version.h b/version.h\nindex afe3dbbab7..5eb586c0bd 100644\n--- a/version.h\n+++ b/version.h\n@@ -12,6 +12,6 @@ const char *git_user_agent_sanitized(void);\n   Return -1 and put an error message into 'buf' in case of uname()\n   error. Return 0 and put uname info into 'buf' otherwise.\n */\n-int get_uname_info(struct strbuf *buf);\n+int get_uname_info(struct strbuf *buf, unsigned int full);\n \n #endif /* VERSION_H */\n-- \n2.48.1\n\n"},{"id":"512423","messageId":"20250214123734.1403120-6-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250214123734.1403120-1-usmanakinyemi202@gmail.com","subject":"[PATCH v5 5/6] t5701: add setup test to remove side-effect dependency","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-14T12:36:15Z","receivedAt":"2025-02-14T12:38:02Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently, the \"test capability advertisement\" test creates some files\nwith expected content which are used by other tests below it.\n\nTo remove that side-effect from this test, let's split up part of\nit into a \"setup\"-type test which creates the files with expected content\nwhich gets reused by multiple tests. This will be useful in a following\ncommit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n t/t5701-git-serve.sh | 12 +++++++++---\n 1 file changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex de904c1655..4c24a188b9 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -7,22 +7,28 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n \n-test_expect_success 'test capability advertisement' '\n+test_expect_success 'setup to generate files with expected content' '\n+\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n+\n \ttest_oid_cache <<-EOF &&\n \twrong_algo sha1:sha256\n \twrong_algo sha256:sha1\n \tEOF\n+\n \tcat >expect.base <<-EOF &&\n \tversion 2\n-\tagent=git/$(git version | cut -d\" \" -f3)\n+\t$(cat agent_capability)\n \tls-refs=unborn\n \tfetch=shallow wait-for-done\n \tserver-option\n \tobject-format=$(test_oid algo)\n \tEOF\n-\tcat >expect.trailer <<-EOF &&\n+\tcat >expect.trailer <<-EOF\n \t0000\n \tEOF\n+'\n+\n+test_expect_success 'test capability advertisement' '\n \tcat expect.base expect.trailer >expect &&\n \n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n-- \n2.48.1\n\n"},{"id":"512424","messageId":"20250214123734.1403120-7-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250214123734.1403120-1-usmanakinyemi202@gmail.com","subject":"[PATCH v5 6/6] agent: advertise OS name via agent capability","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-14T12:36:16Z","receivedAt":"2025-02-14T12:38:06Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"As some issues that can happen with a Git client can be operating system\nspecific, it can be useful for a server to know which OS a client is\nusing. In the same way it can be useful for a client to know which OS\na server is using.\n\nOur current agent capability is in the form of \"package/version\" (e.g.,\n\"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\ni.e in the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\").\n\nIncluding OS details in the agent capability simplifies implementation,\nmaintains backward compatibility, avoids introducing a new capability,\nencourages adoption across Git-compatible software, and enhances\ndebugging by providing complete environment information without affecting\nfunctionality. The operating system name is retrieved using the 'sysname'\nfield of the `uname(2)` system call or its equivalent.\n\nHowever, there are differences between `uname(1)` (command-line utility)\nand `uname(2)` (system call) outputs on Windows. These discrepancies\ncomplicate testing on Windows platforms. For example:\n  - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n  .2024-02-14.20:17.UTC.x86_64\n  - `uname(2)` output: Windows.10.0.20348\n\nOn Windows, uname(2) is not actually system-supplied but is instead\nalready faked up by Git itself. We could have overcome the test issue\non Windows by implementing a new `uname` subcommand in `test-tool`\nusing uname(2), but except uname(2), which would be tested against\nitself, there would be nothing platform specific, so it's just simpler\nto disable the tests on Windows.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n Documentation/gitprotocol-v2.txt | 13 ++++++++-----\n t/t5701-git-serve.sh             | 16 +++++++++++++++-\n t/test-lib-functions.sh          |  8 ++++++++\n version.c                        | 29 ++++++++++++++++++++++++++++-\n version.h                        |  3 +++\n 5 files changed, 62 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\nindex 1652fef3ae..f4831a8787 100644\n--- a/Documentation/gitprotocol-v2.txt\n+++ b/Documentation/gitprotocol-v2.txt\n@@ -184,11 +184,14 @@ form `agent=X`) to notify the client that the server is running version\n the `agent` capability with a value `Y` (in the form `agent=Y`) in its\n request to the server (but it MUST NOT do so if the server did not\n advertise the agent capability). The `X` and `Y` strings may contain any\n-printable ASCII characters except space (i.e., the byte range 32 < x <\n-127), and are typically of the form \"package/version\" (e.g.,\n-\"git/1.8.3.1\"). The agent strings are purely informative for statistics\n-and debugging purposes, and MUST NOT be used to programmatically assume\n-the presence or absence of particular features.\n+printable ASCII characters (i.e., the byte range 31 < x < 127), and are\n+typically of the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\")\n+where `os` is the operating system name (e.g., \"Linux\"). `X` and `Y` can\n+be configured using the GIT_USER_AGENT environment variable and it takes\n+priority. The `os` is retrieved using the 'sysname' field of the `uname(2)`\n+system call or its equivalent. The agent strings are purely informative for\n+statistics and debugging purposes, and MUST NOT be used to programmatically\n+assume the presence or absence of particular features.\n \n ls-refs\n ~~~~~~~\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 4c24a188b9..4f0b053c4a 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -8,13 +8,19 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n . ./test-lib.sh\n \n test_expect_success 'setup to generate files with expected content' '\n-\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n+\tprintf \"agent=git/%s\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n \n \ttest_oid_cache <<-EOF &&\n \twrong_algo sha1:sha256\n \twrong_algo sha256:sha1\n \tEOF\n \n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tprintf \"agent=FAKE\\n\" >agent_capability\n+\telse\n+\t\tprintf \" %s\\n\" $(uname -s | test_redact_non_printables) >>agent_capability\n+\tfi &&\n \tcat >expect.base <<-EOF &&\n \tversion 2\n \t$(cat agent_capability)\n@@ -31,6 +37,10 @@ test_expect_success 'setup to generate files with expected content' '\n test_expect_success 'test capability advertisement' '\n \tcat expect.base expect.trailer >expect &&\n \n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tGIT_USER_AGENT=FAKE && export GIT_USER_AGENT\n+\tfi &&\n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n \t\t--advertise-capabilities >out &&\n \ttest-tool pkt-line unpack <out >actual &&\n@@ -361,6 +371,10 @@ test_expect_success 'test capability advertisement with uploadpack.advertiseBund\n \t    expect.extra \\\n \t    expect.trailer >expect &&\n \n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tGIT_USER_AGENT=FAKE && export GIT_USER_AGENT\n+\tfi &&\n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n \t\t--advertise-capabilities >out &&\n \ttest-tool pkt-line unpack <out >actual &&\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 78e054ab50..3465904323 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -2007,3 +2007,11 @@ test_trailing_hash () {\n \t\ttest-tool hexdump |\n \t\tsed \"s/ //g\"\n }\n+\n+# Trim and replace each character with ascii code below 32 or above\n+# 127 (included) using a dot '.' character.\n+# Octal intervals \\001-\\040 and \\177-\\377\n+# correspond to decimal intervals 1-32 and 127-255\n+test_redact_non_printables () {\n+    tr -d \"\\n\\r\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n+}\ndiff --git a/version.c b/version.c\nindex d95221a72a..027ebc82b4 100644\n--- a/version.c\n+++ b/version.c\n@@ -1,8 +1,9 @@\n+#define USE_THE_REPOSITORY_VARIABLE\n+\n #include \"git-compat-util.h\"\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n-#include \"sane-ctype.h\"\n #include \"gettext.h\"\n \n const char git_version_string[] = GIT_VERSION;\n@@ -34,6 +35,27 @@ const char *git_user_agent(void)\n \treturn agent;\n }\n \n+/*\n+  Retrieve, sanitize and cache operating system info for subsequent\n+  calls. Return a pointer to the sanitized operating system info\n+  string.\n+*/\n+static const char *os_info(void)\n+{\n+\tstatic const char *os = NULL;\n+\n+\tif (!os) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tget_uname_info(&buf, 0);\n+\t\t/* Sanitize the os information immediately */\n+\t\tredact_non_printables(&buf);\n+\t\tos = strbuf_detach(&buf, NULL);\n+\t}\n+\n+\treturn os;\n+}\n+\n const char *git_user_agent_sanitized(void)\n {\n \tstatic const char *agent = NULL;\n@@ -43,6 +65,11 @@ const char *git_user_agent_sanitized(void)\n \n \t\tstrbuf_addstr(&buf, git_user_agent());\n \t\tredact_non_printables(&buf);\n+\n+\t\tif (!getenv(\"GIT_USER_AGENT\")) {\n+\t\t\tstrbuf_addch(&buf, ' ');\n+\t\t\tstrbuf_addstr(&buf, os_info());\n+\t\t}\n \t\tagent = strbuf_detach(&buf, NULL);\n \t}\n \ndiff --git a/version.h b/version.h\nindex 5eb586c0bd..bbde6d371a 100644\n--- a/version.h\n+++ b/version.h\n@@ -1,6 +1,8 @@\n #ifndef VERSION_H\n #define VERSION_H\n \n+struct repository;\n+\n extern const char git_version_string[];\n extern const char git_built_from_commit_string[];\n \n@@ -14,4 +16,5 @@ const char *git_user_agent_sanitized(void);\n */\n int get_uname_info(struct strbuf *buf, unsigned int full);\n \n+\n #endif /* VERSION_H */\n-- \n2.48.1\n\n"},{"id":"512439","messageId":"xmqqa5aokxhk.fsf@gitster.g","threadId":"62747","inReplyTo":"20250214123734.1403120-6-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v5 5/6] t5701: add setup test to remove side-effect dependency","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-14T21:49:43Z","receivedAt":"2025-02-14T21:49:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Currently, the \"test capability advertisement\" test creates some files\n> with expected content which are used by other tests below it.\n>\n> To remove that side-effect from this test, let's split up part of\n> it into a \"setup\"-type test which creates the files with expected content\n> which gets reused by multiple tests. This will be useful in a following\n> commit.\n>\n> Mentored-by: Christian Couder <chriscool@tuxfamily.org>\n> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n> ---\n>  t/t5701-git-serve.sh | 12 +++++++++---\n>  1 file changed, 9 insertions(+), 3 deletions(-)\n\nUp to this step, everything looked very good.\n\n"},{"id":"512441","messageId":"xmqq34ggkwnx.fsf@gitster.g","threadId":"62747","inReplyTo":"20250214123734.1403120-7-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v5 6/6] agent: advertise OS name via agent capability","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-14T22:07:30Z","receivedAt":"2025-02-14T22:07:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> As some issues that can happen with a Git client can be operating system\n> specific, it can be useful for a server to know which OS a client is\n> using. In the same way it can be useful for a client to know which OS\n> a server is using.\n>\n> Our current agent capability is in the form of \"package/version\" (e.g.,\n> \"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\n> i.e in the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\").\n\nShouldn't this be \"git/1.8.3.1-Linux\" or something to avoid SP?  The\ncapability list in protocol v1 is on a single line that is whitespace \nseparated (cf. connect.c:parse_feature_value()) without any escape\nmechanism.\n\n\tSide note.  Does it pose a security hole, when we can set\n\tagent to any value?  I do not think so, as it controls what\n\tthis end sends to the other.  If you are attacker in control\n\tof your own agent string to be sent to the other end, and\n\tuse a string with a whitespace in it after \"agent=\" to claim\n\tthat you support a capability you actually don't, that is\n\tnot a new way to attack the other side available to you---you\n\tcan write your own Git client to talk to the other side to\n\tsend such a bogus capablity list anyway.\n\n> diff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\n> index 1652fef3ae..f4831a8787 100644\n> --- a/Documentation/gitprotocol-v2.txt\n> +++ b/Documentation/gitprotocol-v2.txt\n> @@ -184,11 +184,14 @@ form `agent=X`) to notify the client that the server is running version\n>  the `agent` capability with a value `Y` (in the form `agent=Y`) in its\n>  request to the server (but it MUST NOT do so if the server did not\n>  advertise the agent capability). The `X` and `Y` strings may contain any\n> -printable ASCII characters except space (i.e., the byte range 32 < x <\n> -127), and are typically of the form \"package/version\" (e.g.,\n> -\"git/1.8.3.1\"). The agent strings are purely informative for statistics\n> -and debugging purposes, and MUST NOT be used to programmatically assume\n> -the presence or absence of particular features.\n> +printable ASCII characters (i.e., the byte range 31 < x < 127), and are\n\nPatches 1 & 2 redacted non-printables and SP separately, because SP\nis considered printable.  With this change you are allowing SP to be\npassed without getting redacted?  I do not think it is a good idea\n(see above).\n\nWhile I'd prefer to keep the range the same as before, i.e. \"any\nprintable ASCII characters except space\", \"33 <= x <= 126\" may be\nmore readily recognisable that we are doing something unusual, as\n\"32 <= x <= 126\" is fairly easily recognisable as \"ASCII printable\".\n\n> +typically of the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\")\n\nSo, I'd suggest using something other than \" \" between \"version\" and\n\"os\".  Dot (as if the byte there were redacted) or slash or dash or\nwhatever, anything that is not whitespace.\n\n> +where `os` is the operating system name (e.g., \"Linux\"). `X` and `Y` can\n> +be configured using the GIT_USER_AGENT environment variable and it takes\n> +priority. The `os` is retrieved using the 'sysname' field of the `uname(2)`\n> +system call or its equivalent. The agent strings are purely informative for\n> +statistics and debugging purposes, and MUST NOT be used to programmatically\n> +assume the presence or absence of particular features.\n\nOther than these nits, I find the above very well done.\n\nAs to the additional implementation of git_user_agent_sanitized(),\nexcept for that same \"do we really want SP there?\" question, I see\nnothing questionable there, either.\n\nOverall very nicely done and presented.\n\nThanks.\n"},{"id":"512462","messageId":"CAPSxiM8t1TdvKwXuX1XZq_ms6WeJNBMMfhryY-x=835Z8zyHdQ@mail.gmail.com","threadId":"62747","inReplyTo":"xmqq34ggkwnx.fsf@gitster.g","subject":"Re: [PATCH v5 6/6] agent: advertise OS name via agent capability","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-15T15:29:41Z","receivedAt":"2025-02-15T15:29:54Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"On Sat, Feb 15, 2025 at 3:37 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\nHi Junio,\n>\n> > As some issues that can happen with a Git client can be operating system\n> > specific, it can be useful for a server to know which OS a client is\n> > using. In the same way it can be useful for a client to know which OS\n> > a server is using.\n> >\n> > Our current agent capability is in the form of \"package/version\" (e.g.,\n> > \"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\n> > i.e in the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\").\n>\n> Shouldn't this be \"git/1.8.3.1-Linux\" or something to avoid SP?  The\n> capability list in protocol v1 is on a single line that is whitespace\n> separated (cf. connect.c:parse_feature_value()) without any escape\n> mechanism.\nYeah, I almost missed this function. Thanks for pointing it out.\n>\n>         Side note.  Does it pose a security hole, when we can set\n>         agent to any value?  I do not think so, as it controls what\n>         this end sends to the other.  If you are attacker in control\n>         of your own agent string to be sent to the other end, and\n>         use a string with a whitespace in it after \"agent=\" to claim\n>         that you support a capability you actually don't, that is\n>         not a new way to attack the other side available to you---you\n>         can write your own Git client to talk to the other side to\n>         send such a bogus capablity list anyway.\nThanks for this explanation.\n>\n> > diff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\n> > index 1652fef3ae..f4831a8787 100644\n> > --- a/Documentation/gitprotocol-v2.txt\n> > +++ b/Documentation/gitprotocol-v2.txt\n> > @@ -184,11 +184,14 @@ form `agent=X`) to notify the client that the server is running version\n> >  the `agent` capability with a value `Y` (in the form `agent=Y`) in its\n> >  request to the server (but it MUST NOT do so if the server did not\n> >  advertise the agent capability). The `X` and `Y` strings may contain any\n> > -printable ASCII characters except space (i.e., the byte range 32 < x <\n> > -127), and are typically of the form \"package/version\" (e.g.,\n> > -\"git/1.8.3.1\"). The agent strings are purely informative for statistics\n> > -and debugging purposes, and MUST NOT be used to programmatically assume\n> > -the presence or absence of particular features.\n> > +printable ASCII characters (i.e., the byte range 31 < x < 127), and are\n>\n> Patches 1 & 2 redacted non-printables and SP separately, because SP\n> is considered printable.  With this change you are allowing SP to be\n> passed without getting redacted?  I do not think it is a good idea\n> (see above).\n>\n> While I'd prefer to keep the range the same as before, i.e. \"any\n> printable ASCII characters except space\", \"33 <= x <= 126\" may be\n> more readily recognisable that we are doing something unusual, as\n> \"32 <= x <= 126\" is fairly easily recognisable as \"ASCII printable\".\n>\n> > +typically of the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\")\n>\n> So, I'd suggest using something other than \" \" between \"version\" and\n> \"os\".  Dot (as if the byte there were redacted) or slash or dash or\n> whatever, anything that is not whitespace.\nYeah, Noted. Thanks.\n>\n> > +where `os` is the operating system name (e.g., \"Linux\"). `X` and `Y` can\n> > +be configured using the GIT_USER_AGENT environment variable and it takes\n> > +priority. The `os` is retrieved using the 'sysname' field of the `uname(2)`\n> > +system call or its equivalent. The agent strings are purely informative for\n> > +statistics and debugging purposes, and MUST NOT be used to programmatically\n> > +assume the presence or absence of particular features.\n>\n> Other than these nits, I find the above very well done.\n>\n> As to the additional implementation of git_user_agent_sanitized(),\n> except for that same \"do we really want SP there?\" question, I see\n> nothing questionable there, either.\n>\n> Overall very nicely done and presented.\nThank you.\n>\n> Thanks.\n"},{"id":"512463","messageId":"20250215155130.1756934-1-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250214123734.1403120-1-usmanakinyemi202@gmail.com","subject":"[PATCH v6 0/6][Outreachy] extend agent capability to include OS name","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-15T15:50:46Z","receivedAt":"2025-02-15T15:51:37Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"For debugging, statistical analysis, and security purposes, it can\nbe valuable for Git servers to know the operating system the clients\nare using.\n\nFor example:\n- A server noticing that a client is using an old Git version with\nsecurity issues on one platform, like macOS, could verify if the\nuser is indeed running macOS before sending a message to upgrade.\"\n- Similarly, a server identifying a client that could benefit from\nan upgrade (e.g., for performance reasons) could better customize the\nmessage it sends to nudge the client to upgrade.\n\nOur current agent capability is in the form of \"package/version\" (e.g.,\n\"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\ni.e in the form \"package/version-os\" (e.g., \"git/1.8.3.1-Linux\").\nThe operating system name is retrieved using the 'sysname' field of \nhe `uname(2)` system call or its equivalent.\n\nIncluding OS details in the agent capability simplifies implementation,\nmaintains backward compatibility, avoids introducing a new capability,\nencourages adoption across Git-compatible software, and enhances\ndebugging by providing complete environment information without affecting\nfunctionality.\n\nNote that, due to differences between `uname(1)` (command-line\nutility) and `uname(2)` (system call) outputs on Windows,\n`transfer.advertiseOSVersion` is set to false on Windows during\ntesting. See the message part of patch 5/6 for more details.\n\nMy mentor, Christian Couder, sent a previous patch series about this\nbefore. You can find it here\nhttps://lore.kernel.org/git/20240619125708.3719150-1-christian.couder@gmail.com/\n\nChanges since v5\n================\n - Used \"-\" instead of \" \" for seperating \"version\" and \"os\" in the agent string.\n\nUsman Akinyemi (6):\n  version: replace manual ASCII checks with isprint() for clarity\n  version: refactor redact_non_printables()\n  version: refactor get_uname_info()\n  version: extend get_uname_info() to hide system details\n  t5701: add setup test to remove side-effect dependency\n  agent: advertise OS name via agent capability\n\n Documentation/gitprotocol-v2.txt | 13 +++---\n builtin/bugreport.c              | 13 +-----\n connect.c                        |  2 +-\n t/t5701-git-serve.sh             | 26 ++++++++++--\n t/test-lib-functions.sh          |  8 ++++\n version.c                        | 69 +++++++++++++++++++++++++++++---\n version.h                        | 10 +++++\n 7 files changed, 116 insertions(+), 25 deletions(-)\n\nRange-diff versus v5:\n\n1:  82b62c5e66 = 1:  82b62c5e66 version: replace manual ASCII checks with isprint() for clarity\n2:  0a7d7ce871 = 2:  0a7d7ce871 version: refactor redact_non_printables()\n3:  0187db59a4 = 3:  0187db59a4 version: refactor get_uname_info()\n4:  d3a3573594 = 4:  d3a3573594 version: extend get_uname_info() to hide system details\n5:  3e0e98f23d = 5:  3e0e98f23d t5701: add setup test to remove side-effect dependency\n6:  8878e9c9ab ! 6:  48cf946f61 agent: advertise OS name via agent capability\n    @@ Commit message\n     \n         Our current agent capability is in the form of \"package/version\" (e.g.,\n         \"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\n    -    i.e in the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\").\n    +    i.e in the form \"package/version-os\" (e.g., \"git/1.8.3.1-Linux\").\n     \n         Including OS details in the agent capability simplifies implementation,\n         maintains backward compatibility, avoids introducing a new capability,\n    @@ Documentation/gitprotocol-v2.txt: form `agent=X`) to notify the client that the\n     -\"git/1.8.3.1\"). The agent strings are purely informative for statistics\n     -and debugging purposes, and MUST NOT be used to programmatically assume\n     -the presence or absence of particular features.\n    -+printable ASCII characters (i.e., the byte range 31 < x < 127), and are\n    -+typically of the form \"package/version os\" (e.g., \"git/1.8.3.1 Linux\")\n    ++printable ASCII characters (i.e., the byte range 33 <= x <= 126), and are\n    ++typically of the form \"package/version-os\" (e.g., \"git/1.8.3.1-Linux\")\n     +where `os` is the operating system name (e.g., \"Linux\"). `X` and `Y` can\n     +be configured using the GIT_USER_AGENT environment variable and it takes\n     +priority. The `os` is retrieved using the 'sysname' field of the `uname(2)`\n    @@ Documentation/gitprotocol-v2.txt: form `agent=X`) to notify the client that the\n      ls-refs\n      ~~~~~~~\n     \n    + ## connect.c ##\n    +@@ connect.c: const char *parse_feature_value(const char *feature_list, const char *feature, s\n    + \t\t\t\t\t*offset = found + len - orig_start;\n    + \t\t\t\treturn value;\n    + \t\t\t}\n    +-\t\t\t/* feature with a value (e.g., \"agent=git/1.2.3\") */\n    ++\t\t\t/* feature with a value (e.g., \"agent=git/1.2.3-Linux\") */\n    + \t\t\telse if (*value == '=') {\n    + \t\t\t\tsize_t end;\n    + \n    +\n      ## t/t5701-git-serve.sh ##\n     @@ t/t5701-git-serve.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n      . ./test-lib.sh\n    @@ t/t5701-git-serve.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n     +\tthen\n     +\t\tprintf \"agent=FAKE\\n\" >agent_capability\n     +\telse\n    -+\t\tprintf \" %s\\n\" $(uname -s | test_redact_non_printables) >>agent_capability\n    ++\t\tprintf -- \"-%s\\n\" $(uname -s | test_redact_non_printables) >>agent_capability\n     +\tfi &&\n      \tcat >expect.base <<-EOF &&\n      \tversion 2\n    @@ version.c\n      #include \"gettext.h\"\n      \n      const char git_version_string[] = GIT_VERSION;\n    -@@ version.c: const char *git_user_agent_sanitized(void)\n    - \n    - \t\tstrbuf_addstr(&buf, git_user_agent());\n    - \t\tredact_non_printables(&buf);\n    -+\n    -+\t\tif (!getenv(\"GIT_USER_AGENT\")) {\n    -+\t\t\tstrbuf_addch(&buf, ' ');\n    -+\t\t\tstrbuf_addstr(&buf, os_info());\n    -+\t\t}\n    - \t\tagent = strbuf_detach(&buf, NULL);\n    - \t}\n    - \n    -@@ version.c: int get_uname_info(struct strbuf *buf, unsigned int full)\n    - \t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n    - \treturn 0;\n    +@@ version.c: const char *git_user_agent(void)\n    + \treturn agent;\n      }\n    -+\n    -+const char *os_info(void)\n    + \n    ++/*\n    ++  Retrieve, sanitize and cache operating system info for subsequent\n    ++  calls. Return a pointer to the sanitized operating system info\n    ++  string.\n    ++*/\n    ++static const char *os_info(void)\n     +{\n     +\tstatic const char *os = NULL;\n     +\n    @@ version.c: int get_uname_info(struct strbuf *buf, unsigned int full)\n     +\n     +\treturn os;\n     +}\n    ++\n    + const char *git_user_agent_sanitized(void)\n    + {\n    + \tstatic const char *agent = NULL;\n    +@@ version.c: const char *git_user_agent_sanitized(void)\n    + \t\tstruct strbuf buf = STRBUF_INIT;\n    + \n    + \t\tstrbuf_addstr(&buf, git_user_agent());\n    ++\n    ++\t\tif (!getenv(\"GIT_USER_AGENT\")) {\n    ++\t\t\tstrbuf_addch(&buf, '-');\n    ++\t\t\tstrbuf_addstr(&buf, os_info());\n    ++\t\t}\n    + \t\tredact_non_printables(&buf);\n    + \t\tagent = strbuf_detach(&buf, NULL);\n    + \t}\n     \n      ## version.h ##\n     @@\n    @@ version.h: const char *git_user_agent_sanitized(void);\n      */\n      int get_uname_info(struct strbuf *buf, unsigned int full);\n      \n    -+/*\n    -+  Retrieve, sanitize and cache operating system info for subsequent\n    -+  calls. Return a pointer to the sanitized operating system info\n    -+  string.\n    -+*/\n    -+const char *os_info(void);\n     +\n      #endif /* VERSION_H */\n\n-- \n2.48.1\n\n"},{"id":"512464","messageId":"20250215155130.1756934-2-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250215155130.1756934-1-usmanakinyemi202@gmail.com","subject":"[PATCH v6 1/6] version: replace manual ASCII checks with isprint() for clarity","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-15T15:50:47Z","receivedAt":"2025-02-15T15:51:41Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Since the isprint() function checks for printable characters, let's\nreplace the existing hardcoded ASCII checks with it. However, since\nthe original checks also handled spaces, we need to account for spaces\nexplicitly in the new check.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n version.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/version.c b/version.c\nindex 4d763ab48d..6cfbb8ca56 100644\n--- a/version.c\n+++ b/version.c\n@@ -2,6 +2,7 @@\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n+#include \"sane-ctype.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -29,7 +30,7 @@ const char *git_user_agent_sanitized(void)\n \t\tstrbuf_addstr(&buf, git_user_agent());\n \t\tstrbuf_trim(&buf);\n \t\tfor (size_t i = 0; i < buf.len; i++) {\n-\t\t\tif (buf.buf[i] <= 32 || buf.buf[i] >= 127)\n+\t\t\tif (!isprint(buf.buf[i]) || buf.buf[i] == ' ')\n \t\t\t\tbuf.buf[i] = '.';\n \t\t}\n \t\tagent = buf.buf;\n-- \n2.48.1\n\n"},{"id":"512465","messageId":"20250215155130.1756934-3-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250215155130.1756934-1-usmanakinyemi202@gmail.com","subject":"[PATCH v6 2/6] version: refactor redact_non_printables()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-15T15:50:48Z","receivedAt":"2025-02-15T15:51:45Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"The git_user_agent_sanitized() function performs some sanitizing to\navoid special characters being sent over the line and possibly messing\nup with the protocol or with the parsing on the other side.\n\nLet's extract this sanitizing into a new redact_non_printables() function,\nas we will want to reuse it in a following patch.\n\nFor now the new redact_non_printables() function is still static as\nit's only needed locally.\n\nWhile at it, let's use strbuf_detach() to explicitly detach the string\ncontained by the 'buf' strbuf.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n version.c | 21 +++++++++++++++------\n 1 file changed, 15 insertions(+), 6 deletions(-)\n\ndiff --git a/version.c b/version.c\nindex 6cfbb8ca56..60df71fd0e 100644\n--- a/version.c\n+++ b/version.c\n@@ -7,6 +7,19 @@\n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n \n+/*\n+ * Trim and replace each character with ascii code below 32 or above\n+ * 127 (included) using a dot '.' character.\n+ */\n+static void redact_non_printables(struct strbuf *buf)\n+{\n+\tstrbuf_trim(buf);\n+\tfor (size_t i = 0; i < buf->len; i++) {\n+\t\tif (!isprint(buf->buf[i]) || buf->buf[i] == ' ')\n+\t\t\tbuf->buf[i] = '.';\n+\t}\n+}\n+\n const char *git_user_agent(void)\n {\n \tstatic const char *agent = NULL;\n@@ -28,12 +41,8 @@ const char *git_user_agent_sanitized(void)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&buf, git_user_agent());\n-\t\tstrbuf_trim(&buf);\n-\t\tfor (size_t i = 0; i < buf.len; i++) {\n-\t\t\tif (!isprint(buf.buf[i]) || buf.buf[i] == ' ')\n-\t\t\t\tbuf.buf[i] = '.';\n-\t\t}\n-\t\tagent = buf.buf;\n+\t\tredact_non_printables(&buf);\n+\t\tagent = strbuf_detach(&buf, NULL);\n \t}\n \n \treturn agent;\n-- \n2.48.1\n\n"},{"id":"512466","messageId":"20250215155130.1756934-4-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250215155130.1756934-1-usmanakinyemi202@gmail.com","subject":"[PATCH v6 3/6] version: refactor get_uname_info()","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-15T15:50:49Z","receivedAt":"2025-02-15T15:51:49Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Some code from \"builtin/bugreport.c\" uses uname(2) to get system\ninformation.\n\nLet's refactor this code into a new get_uname_info() function, so\nthat we can reuse it in a following commit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c | 13 ++-----------\n version.c           | 20 ++++++++++++++++++++\n version.h           |  7 +++++++\n 3 files changed, 29 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 7c2df035c9..5e13d532a8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -12,10 +12,10 @@\n #include \"diagnose.h\"\n #include \"object-file.h\"\n #include \"setup.h\"\n+#include \"version.h\"\n \n static void get_system_info(struct strbuf *sys_info)\n {\n-\tstruct utsname uname_info;\n \tchar *shell = NULL;\n \n \t/* get git version from native cmd */\n@@ -24,16 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tif (uname(&uname_info))\n-\t\tstrbuf_addf(sys_info, _(\"uname() failed with error '%s' (%d)\\n\"),\n-\t\t\t    strerror(errno),\n-\t\t\t    errno);\n-\telse\n-\t\tstrbuf_addf(sys_info, \"%s %s %s %s\\n\",\n-\t\t\t    uname_info.sysname,\n-\t\t\t    uname_info.release,\n-\t\t\t    uname_info.version,\n-\t\t\t    uname_info.machine);\n+\tget_uname_info(sys_info);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 60df71fd0e..3ec8b8243d 100644\n--- a/version.c\n+++ b/version.c\n@@ -3,6 +3,7 @@\n #include \"version-def.h\"\n #include \"strbuf.h\"\n #include \"sane-ctype.h\"\n+#include \"gettext.h\"\n \n const char git_version_string[] = GIT_VERSION;\n const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;\n@@ -47,3 +48,22 @@ const char *git_user_agent_sanitized(void)\n \n \treturn agent;\n }\n+\n+int get_uname_info(struct strbuf *buf)\n+{\n+\tstruct utsname uname_info;\n+\n+\tif (uname(&uname_info)) {\n+\t\tstrbuf_addf(buf, _(\"uname() failed with error '%s' (%d)\\n\"),\n+\t\t\t    strerror(errno),\n+\t\t\t    errno);\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t    uname_info.sysname,\n+\t\t    uname_info.release,\n+\t\t    uname_info.version,\n+\t\t    uname_info.machine);\n+\treturn 0;\n+}\ndiff --git a/version.h b/version.h\nindex 7c62e80577..afe3dbbab7 100644\n--- a/version.h\n+++ b/version.h\n@@ -7,4 +7,11 @@ extern const char git_built_from_commit_string[];\n const char *git_user_agent(void);\n const char *git_user_agent_sanitized(void);\n \n+/*\n+  Try to get information about the system using uname(2).\n+  Return -1 and put an error message into 'buf' in case of uname()\n+  error. Return 0 and put uname info into 'buf' otherwise.\n+*/\n+int get_uname_info(struct strbuf *buf);\n+\n #endif /* VERSION_H */\n-- \n2.48.1\n\n"},{"id":"512467","messageId":"20250215155130.1756934-5-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250215155130.1756934-1-usmanakinyemi202@gmail.com","subject":"[PATCH v6 4/6] version: extend get_uname_info() to hide system details","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-15T15:50:50Z","receivedAt":"2025-02-15T15:51:53Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently, get_uname_info() function provides the full OS information.\nIn a following commit, we will need it to provide only the OS name.\n\nLet's extend it to accept a \"full\" flag that makes it switch between\nproviding full OS information and providing only the OS name.\n\nWe may need to refactor this function in the future if an\n`osVersion.format` is added.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n builtin/bugreport.c |  2 +-\n version.c           | 16 +++++++++-------\n version.h           |  2 +-\n 3 files changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/bugreport.c b/builtin/bugreport.c\nindex 5e13d532a8..e3288a86c8 100644\n--- a/builtin/bugreport.c\n+++ b/builtin/bugreport.c\n@@ -24,7 +24,7 @@ static void get_system_info(struct strbuf *sys_info)\n \n \t/* system call for other version info */\n \tstrbuf_addstr(sys_info, \"uname: \");\n-\tget_uname_info(sys_info);\n+\tget_uname_info(sys_info, 1);\n \n \tstrbuf_addstr(sys_info, _(\"compiler info: \"));\n \tget_compiler_info(sys_info);\ndiff --git a/version.c b/version.c\nindex 3ec8b8243d..d95221a72a 100644\n--- a/version.c\n+++ b/version.c\n@@ -49,7 +49,7 @@ const char *git_user_agent_sanitized(void)\n \treturn agent;\n }\n \n-int get_uname_info(struct strbuf *buf)\n+int get_uname_info(struct strbuf *buf, unsigned int full)\n {\n \tstruct utsname uname_info;\n \n@@ -59,11 +59,13 @@ int get_uname_info(struct strbuf *buf)\n \t\t\t    errno);\n \t\treturn -1;\n \t}\n-\n-\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n-\t\t    uname_info.sysname,\n-\t\t    uname_info.release,\n-\t\t    uname_info.version,\n-\t\t    uname_info.machine);\n+\tif (full)\n+\t\tstrbuf_addf(buf, \"%s %s %s %s\\n\",\n+\t\t\t    uname_info.sysname,\n+\t\t\t    uname_info.release,\n+\t\t\t    uname_info.version,\n+\t\t\t    uname_info.machine);\n+\telse\n+\t     strbuf_addf(buf, \"%s\\n\", uname_info.sysname);\n \treturn 0;\n }\ndiff --git a/version.h b/version.h\nindex afe3dbbab7..5eb586c0bd 100644\n--- a/version.h\n+++ b/version.h\n@@ -12,6 +12,6 @@ const char *git_user_agent_sanitized(void);\n   Return -1 and put an error message into 'buf' in case of uname()\n   error. Return 0 and put uname info into 'buf' otherwise.\n */\n-int get_uname_info(struct strbuf *buf);\n+int get_uname_info(struct strbuf *buf, unsigned int full);\n \n #endif /* VERSION_H */\n-- \n2.48.1\n\n"},{"id":"512468","messageId":"20250215155130.1756934-6-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250215155130.1756934-1-usmanakinyemi202@gmail.com","subject":"[PATCH v6 5/6] t5701: add setup test to remove side-effect dependency","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-15T15:50:51Z","receivedAt":"2025-02-15T15:51:57Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"Currently, the \"test capability advertisement\" test creates some files\nwith expected content which are used by other tests below it.\n\nTo remove that side-effect from this test, let's split up part of\nit into a \"setup\"-type test which creates the files with expected content\nwhich gets reused by multiple tests. This will be useful in a following\ncommit.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n t/t5701-git-serve.sh | 12 +++++++++---\n 1 file changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex de904c1655..4c24a188b9 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -7,22 +7,28 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n \n-test_expect_success 'test capability advertisement' '\n+test_expect_success 'setup to generate files with expected content' '\n+\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n+\n \ttest_oid_cache <<-EOF &&\n \twrong_algo sha1:sha256\n \twrong_algo sha256:sha1\n \tEOF\n+\n \tcat >expect.base <<-EOF &&\n \tversion 2\n-\tagent=git/$(git version | cut -d\" \" -f3)\n+\t$(cat agent_capability)\n \tls-refs=unborn\n \tfetch=shallow wait-for-done\n \tserver-option\n \tobject-format=$(test_oid algo)\n \tEOF\n-\tcat >expect.trailer <<-EOF &&\n+\tcat >expect.trailer <<-EOF\n \t0000\n \tEOF\n+'\n+\n+test_expect_success 'test capability advertisement' '\n \tcat expect.base expect.trailer >expect &&\n \n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n-- \n2.48.1\n\n"},{"id":"512469","messageId":"20250215155130.1756934-7-usmanakinyemi202@gmail.com","threadId":"62747","inReplyTo":"20250215155130.1756934-1-usmanakinyemi202@gmail.com","subject":"[PATCH v6 6/6] agent: advertise OS name via agent capability","fromName":"Usman Akinyemi","fromEmail":"usmanakinyemi202@gmail.com","sentAt":"2025-02-15T15:50:52Z","receivedAt":"2025-02-15T15:52:01Z","isPatch":true,"sender":{"key":"usmanakinyemi202@gmail.com","avatar":"https://avatars.githubusercontent.com/u/86585626?v=4"},"body":"As some issues that can happen with a Git client can be operating system\nspecific, it can be useful for a server to know which OS a client is\nusing. In the same way it can be useful for a client to know which OS\na server is using.\n\nOur current agent capability is in the form of \"package/version\" (e.g.,\n\"git/1.8.3.1\"). Let's extend it to include the operating system name (os)\ni.e in the form \"package/version-os\" (e.g., \"git/1.8.3.1-Linux\").\n\nIncluding OS details in the agent capability simplifies implementation,\nmaintains backward compatibility, avoids introducing a new capability,\nencourages adoption across Git-compatible software, and enhances\ndebugging by providing complete environment information without affecting\nfunctionality. The operating system name is retrieved using the 'sysname'\nfield of the `uname(2)` system call or its equivalent.\n\nHowever, there are differences between `uname(1)` (command-line utility)\nand `uname(2)` (system call) outputs on Windows. These discrepancies\ncomplicate testing on Windows platforms. For example:\n  - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\\\n  .2024-02-14.20:17.UTC.x86_64\n  - `uname(2)` output: Windows.10.0.20348\n\nOn Windows, uname(2) is not actually system-supplied but is instead\nalready faked up by Git itself. We could have overcome the test issue\non Windows by implementing a new `uname` subcommand in `test-tool`\nusing uname(2), but except uname(2), which would be tested against\nitself, there would be nothing platform specific, so it's just simpler\nto disable the tests on Windows.\n\nMentored-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>\n---\n Documentation/gitprotocol-v2.txt | 13 ++++++++-----\n connect.c                        |  2 +-\n t/t5701-git-serve.sh             | 16 +++++++++++++++-\n t/test-lib-functions.sh          |  8 ++++++++\n version.c                        | 29 ++++++++++++++++++++++++++++-\n version.h                        |  3 +++\n 6 files changed, 63 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\nindex 1652fef3ae..ce4a4e5e3b 100644\n--- a/Documentation/gitprotocol-v2.txt\n+++ b/Documentation/gitprotocol-v2.txt\n@@ -184,11 +184,14 @@ form `agent=X`) to notify the client that the server is running version\n the `agent` capability with a value `Y` (in the form `agent=Y`) in its\n request to the server (but it MUST NOT do so if the server did not\n advertise the agent capability). The `X` and `Y` strings may contain any\n-printable ASCII characters except space (i.e., the byte range 32 < x <\n-127), and are typically of the form \"package/version\" (e.g.,\n-\"git/1.8.3.1\"). The agent strings are purely informative for statistics\n-and debugging purposes, and MUST NOT be used to programmatically assume\n-the presence or absence of particular features.\n+printable ASCII characters (i.e., the byte range 33 <= x <= 126), and are\n+typically of the form \"package/version-os\" (e.g., \"git/1.8.3.1-Linux\")\n+where `os` is the operating system name (e.g., \"Linux\"). `X` and `Y` can\n+be configured using the GIT_USER_AGENT environment variable and it takes\n+priority. The `os` is retrieved using the 'sysname' field of the `uname(2)`\n+system call or its equivalent. The agent strings are purely informative for\n+statistics and debugging purposes, and MUST NOT be used to programmatically\n+assume the presence or absence of particular features.\n \n ls-refs\n ~~~~~~~\ndiff --git a/connect.c b/connect.c\nindex 10fad43e98..4d85479075 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -625,7 +625,7 @@ const char *parse_feature_value(const char *feature_list, const char *feature, s\n \t\t\t\t\t*offset = found + len - orig_start;\n \t\t\t\treturn value;\n \t\t\t}\n-\t\t\t/* feature with a value (e.g., \"agent=git/1.2.3\") */\n+\t\t\t/* feature with a value (e.g., \"agent=git/1.2.3-Linux\") */\n \t\t\telse if (*value == '=') {\n \t\t\t\tsize_t end;\n \ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex 4c24a188b9..678a346ed0 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -8,13 +8,19 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n . ./test-lib.sh\n \n test_expect_success 'setup to generate files with expected content' '\n-\tprintf \"agent=git/%s\\n\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n+\tprintf \"agent=git/%s\" \"$(git version | cut -d\" \" -f3)\" >agent_capability &&\n \n \ttest_oid_cache <<-EOF &&\n \twrong_algo sha1:sha256\n \twrong_algo sha256:sha1\n \tEOF\n \n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tprintf \"agent=FAKE\\n\" >agent_capability\n+\telse\n+\t\tprintf -- \"-%s\\n\" $(uname -s | test_redact_non_printables) >>agent_capability\n+\tfi &&\n \tcat >expect.base <<-EOF &&\n \tversion 2\n \t$(cat agent_capability)\n@@ -31,6 +37,10 @@ test_expect_success 'setup to generate files with expected content' '\n test_expect_success 'test capability advertisement' '\n \tcat expect.base expect.trailer >expect &&\n \n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tGIT_USER_AGENT=FAKE && export GIT_USER_AGENT\n+\tfi &&\n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n \t\t--advertise-capabilities >out &&\n \ttest-tool pkt-line unpack <out >actual &&\n@@ -361,6 +371,10 @@ test_expect_success 'test capability advertisement with uploadpack.advertiseBund\n \t    expect.extra \\\n \t    expect.trailer >expect &&\n \n+\tif test_have_prereq WINDOWS\n+\tthen\n+\t\tGIT_USER_AGENT=FAKE && export GIT_USER_AGENT\n+\tfi &&\n \tGIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \\\n \t\t--advertise-capabilities >out &&\n \ttest-tool pkt-line unpack <out >actual &&\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 78e054ab50..3465904323 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -2007,3 +2007,11 @@ test_trailing_hash () {\n \t\ttest-tool hexdump |\n \t\tsed \"s/ //g\"\n }\n+\n+# Trim and replace each character with ascii code below 32 or above\n+# 127 (included) using a dot '.' character.\n+# Octal intervals \\001-\\040 and \\177-\\377\n+# correspond to decimal intervals 1-32 and 127-255\n+test_redact_non_printables () {\n+    tr -d \"\\n\\r\" | tr \"[\\001-\\040][\\177-\\377]\" \".\"\n+}\ndiff --git a/version.c b/version.c\nindex d95221a72a..8e927cf1eb 100644\n--- a/version.c\n+++ b/version.c\n@@ -1,8 +1,9 @@\n+#define USE_THE_REPOSITORY_VARIABLE\n+\n #include \"git-compat-util.h\"\n #include \"version.h\"\n #include \"version-def.h\"\n #include \"strbuf.h\"\n-#include \"sane-ctype.h\"\n #include \"gettext.h\"\n \n const char git_version_string[] = GIT_VERSION;\n@@ -34,6 +35,27 @@ const char *git_user_agent(void)\n \treturn agent;\n }\n \n+/*\n+  Retrieve, sanitize and cache operating system info for subsequent\n+  calls. Return a pointer to the sanitized operating system info\n+  string.\n+*/\n+static const char *os_info(void)\n+{\n+\tstatic const char *os = NULL;\n+\n+\tif (!os) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tget_uname_info(&buf, 0);\n+\t\t/* Sanitize the os information immediately */\n+\t\tredact_non_printables(&buf);\n+\t\tos = strbuf_detach(&buf, NULL);\n+\t}\n+\n+\treturn os;\n+}\n+\n const char *git_user_agent_sanitized(void)\n {\n \tstatic const char *agent = NULL;\n@@ -42,6 +64,11 @@ const char *git_user_agent_sanitized(void)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&buf, git_user_agent());\n+\n+\t\tif (!getenv(\"GIT_USER_AGENT\")) {\n+\t\t\tstrbuf_addch(&buf, '-');\n+\t\t\tstrbuf_addstr(&buf, os_info());\n+\t\t}\n \t\tredact_non_printables(&buf);\n \t\tagent = strbuf_detach(&buf, NULL);\n \t}\ndiff --git a/version.h b/version.h\nindex 5eb586c0bd..bbde6d371a 100644\n--- a/version.h\n+++ b/version.h\n@@ -1,6 +1,8 @@\n #ifndef VERSION_H\n #define VERSION_H\n \n+struct repository;\n+\n extern const char git_version_string[];\n extern const char git_built_from_commit_string[];\n \n@@ -14,4 +16,5 @@ const char *git_user_agent_sanitized(void);\n */\n int get_uname_info(struct strbuf *buf, unsigned int full);\n \n+\n #endif /* VERSION_H */\n-- \n2.48.1\n\n"},{"id":"512621","messageId":"xmqqa5ajyyby.fsf@gitster.g","threadId":"62747","inReplyTo":"20250215155130.1756934-1-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v6 0/6][Outreachy] extend agent capability to include OS name","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-18T17:09:05Z","receivedAt":"2025-02-18T17:09:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> Changes since v5\n> ================\n>  - Used \"-\" instead of \" \" for seperating \"version\" and \"os\" in the agent string.\n>\n> Usman Akinyemi (6):\n>   version: replace manual ASCII checks with isprint() for clarity\n>   version: refactor redact_non_printables()\n>   version: refactor get_uname_info()\n>   version: extend get_uname_info() to hide system details\n>   t5701: add setup test to remove side-effect dependency\n>   agent: advertise OS name via agent capability\n\nOverall everything looks good.  I spotted just one nit in the\nprotocol documentation update, which I'll comment on separately.\n\nThanks.\n"},{"id":"512624","messageId":"xmqq5xl7yy3r.fsf@gitster.g","threadId":"62747","inReplyTo":"20250215155130.1756934-7-usmanakinyemi202@gmail.com","subject":"Re: [PATCH v6 6/6] agent: advertise OS name via agent capability","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-18T17:14:00Z","receivedAt":"2025-02-18T17:14:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Usman Akinyemi <usmanakinyemi202@gmail.com> writes:\n\n> diff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt\n> ...\n>  advertise the agent capability). The `X` and `Y` strings may contain any\n> -printable ASCII characters except space (i.e., the byte range 32 < x <\n> -127), and are typically of the form \"package/version\" (e.g.,\n> ...\n> -the presence or absence of particular features.\n> +printable ASCII characters (i.e., the byte range 33 <= x <= 126), and are\n> +typically of the form \"package/version-os\" (e.g., \"git/1.8.3.1-Linux\")\n\nTHe above updates the way the byte range is expressed as inequality\nbut the series does not change the byte range itself.  Hence, \"any\nprintable ASCII chavacters except space\" should stay the same as-is,\nwithout losing \"except space\", I would think.\n\nNo need to resend just to update this.\n\nThaskn.\n"}]}