Git fork

agent: advertise OS name via agent capability

As some issues that can happen with a Git client can be operating system
specific, it can be useful for a server to know which OS a client is
using. In the same way it can be useful for a client to know which OS
a server is using.

Our current agent capability is in the form of "package/version" (e.g.,
"git/1.8.3.1"). Let's extend it to include the operating system name (os)
i.e in the form "package/version-os" (e.g., "git/1.8.3.1-Linux").

Including OS details in the agent capability simplifies implementation,
maintains backward compatibility, avoids introducing a new capability,
encourages adoption across Git-compatible software, and enhances
debugging by providing complete environment information without affecting
functionality. The operating system name is retrieved using the 'sysname'
field of the `uname(2)` system call or its equivalent.

However, there are differences between `uname(1)` (command-line utility)
and `uname(2)` (system call) outputs on Windows. These discrepancies
complicate testing on Windows platforms. For example:
- `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\
.2024-02-14.20:17.UTC.x86_64
- `uname(2)` output: Windows.10.0.20348

On Windows, uname(2) is not actually system-supplied but is instead
already faked up by Git itself. We could have overcome the test issue
on Windows by implementing a new `uname` subcommand in `test-tool`
using uname(2), but except uname(2), which would be tested against
itself, there would be nothing platform specific, so it's just simpler
to disable the tests on Windows.

Mentored-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Usman Akinyemi and committed by
Junio C Hamano
cf7ee481 15ff2068

+62 -6
+7 -3
Documentation/gitprotocol-v2.txt
··· 184 the `agent` capability with a value `Y` (in the form `agent=Y`) in its 185 request to the server (but it MUST NOT do so if the server did not 186 advertise the agent capability). The `X` and `Y` strings may contain any 187 - printable ASCII characters except space (i.e., the byte range 32 < x < 188 - 127), and are typically of the form "package/version" (e.g., 189 - "git/1.8.3.1"). The agent strings are purely informative for statistics 190 and debugging purposes, and MUST NOT be used to programmatically assume 191 the presence or absence of particular features. 192
··· 184 the `agent` capability with a value `Y` (in the form `agent=Y`) in its 185 request to the server (but it MUST NOT do so if the server did not 186 advertise the agent capability). The `X` and `Y` strings may contain any 187 + printable ASCII characters except space (i.e., the byte range 33 <= x <= 188 + 126), and are typically of the form "package/version-os" (e.g., 189 + "git/1.8.3.1-Linux") where `os` is the operating system name (e.g., 190 + "Linux"). `X` and `Y` can be configured using the GIT_USER_AGENT 191 + environment variable and it takes priority. The `os` is 192 + retrieved using the 'sysname' field of the `uname(2)` system call 193 + or its equivalent. The agent strings are purely informative for statistics 194 and debugging purposes, and MUST NOT be used to programmatically assume 195 the presence or absence of particular features. 196
+1 -1
connect.c
··· 625 *offset = found + len - orig_start; 626 return value; 627 } 628 - /* feature with a value (e.g., "agent=git/1.2.3") */ 629 else if (*value == '=') { 630 size_t end; 631
··· 625 *offset = found + len - orig_start; 626 return value; 627 } 628 + /* feature with a value (e.g., "agent=git/1.2.3-Linux") */ 629 else if (*value == '=') { 630 size_t end; 631
+15 -1
t/t5701-git-serve.sh
··· 8 . ./test-lib.sh 9 10 test_expect_success 'setup to generate files with expected content' ' 11 - printf "agent=git/%s\n" "$(git version | cut -d" " -f3)" >agent_capability && 12 13 test_oid_cache <<-EOF && 14 wrong_algo sha1:sha256 15 wrong_algo sha256:sha1 16 EOF 17 18 cat >expect.base <<-EOF && 19 version 2 20 $(cat agent_capability) ··· 31 test_expect_success 'test capability advertisement' ' 32 cat expect.base expect.trailer >expect && 33 34 GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \ 35 --advertise-capabilities >out && 36 test-tool pkt-line unpack <out >actual && ··· 361 expect.extra \ 362 expect.trailer >expect && 363 364 GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \ 365 --advertise-capabilities >out && 366 test-tool pkt-line unpack <out >actual &&
··· 8 . ./test-lib.sh 9 10 test_expect_success 'setup to generate files with expected content' ' 11 + printf "agent=git/%s" "$(git version | cut -d" " -f3)" >agent_capability && 12 13 test_oid_cache <<-EOF && 14 wrong_algo sha1:sha256 15 wrong_algo sha256:sha1 16 EOF 17 18 + if test_have_prereq WINDOWS 19 + then 20 + printf "agent=FAKE\n" >agent_capability 21 + else 22 + printf -- "-%s\n" $(uname -s | test_redact_non_printables) >>agent_capability 23 + fi && 24 cat >expect.base <<-EOF && 25 version 2 26 $(cat agent_capability) ··· 37 test_expect_success 'test capability advertisement' ' 38 cat expect.base expect.trailer >expect && 39 40 + if test_have_prereq WINDOWS 41 + then 42 + GIT_USER_AGENT=FAKE && export GIT_USER_AGENT 43 + fi && 44 GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \ 45 --advertise-capabilities >out && 46 test-tool pkt-line unpack <out >actual && ··· 371 expect.extra \ 372 expect.trailer >expect && 373 374 + if test_have_prereq WINDOWS 375 + then 376 + GIT_USER_AGENT=FAKE && export GIT_USER_AGENT 377 + fi && 378 GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \ 379 --advertise-capabilities >out && 380 test-tool pkt-line unpack <out >actual &&
+8
t/test-lib-functions.sh
··· 2007 test-tool hexdump | 2008 sed "s/ //g" 2009 }
··· 2007 test-tool hexdump | 2008 sed "s/ //g" 2009 } 2010 + 2011 + # Trim and replace each character with ascii code below 32 or above 2012 + # 127 (included) using a dot '.' character. 2013 + # Octal intervals \001-\040 and \177-\377 2014 + # correspond to decimal intervals 1-32 and 127-255 2015 + test_redact_non_printables () { 2016 + tr -d "\n\r" | tr "[\001-\040][\177-\377]" "." 2017 + }
+28 -1
version.c
··· 1 #include "git-compat-util.h" 2 #include "version.h" 3 #include "version-def.h" 4 #include "strbuf.h" 5 - #include "sane-ctype.h" 6 #include "gettext.h" 7 8 const char git_version_string[] = GIT_VERSION; ··· 34 return agent; 35 } 36 37 const char *git_user_agent_sanitized(void) 38 { 39 static const char *agent = NULL; ··· 42 struct strbuf buf = STRBUF_INIT; 43 44 strbuf_addstr(&buf, git_user_agent()); 45 redact_non_printables(&buf); 46 agent = strbuf_detach(&buf, NULL); 47 }
··· 1 + #define USE_THE_REPOSITORY_VARIABLE 2 + 3 #include "git-compat-util.h" 4 #include "version.h" 5 #include "version-def.h" 6 #include "strbuf.h" 7 #include "gettext.h" 8 9 const char git_version_string[] = GIT_VERSION; ··· 35 return agent; 36 } 37 38 + /* 39 + Retrieve, sanitize and cache operating system info for subsequent 40 + calls. Return a pointer to the sanitized operating system info 41 + string. 42 + */ 43 + static const char *os_info(void) 44 + { 45 + static const char *os = NULL; 46 + 47 + if (!os) { 48 + struct strbuf buf = STRBUF_INIT; 49 + 50 + get_uname_info(&buf, 0); 51 + /* Sanitize the os information immediately */ 52 + redact_non_printables(&buf); 53 + os = strbuf_detach(&buf, NULL); 54 + } 55 + 56 + return os; 57 + } 58 + 59 const char *git_user_agent_sanitized(void) 60 { 61 static const char *agent = NULL; ··· 64 struct strbuf buf = STRBUF_INIT; 65 66 strbuf_addstr(&buf, git_user_agent()); 67 + 68 + if (!getenv("GIT_USER_AGENT")) { 69 + strbuf_addch(&buf, '-'); 70 + strbuf_addstr(&buf, os_info()); 71 + } 72 redact_non_printables(&buf); 73 agent = strbuf_detach(&buf, NULL); 74 }
+3
version.h
··· 1 #ifndef VERSION_H 2 #define VERSION_H 3 4 extern const char git_version_string[]; 5 extern const char git_built_from_commit_string[]; 6 ··· 13 error. Return 0 and put uname info into 'buf' otherwise. 14 */ 15 int get_uname_info(struct strbuf *buf, unsigned int full); 16 17 #endif /* VERSION_H */
··· 1 #ifndef VERSION_H 2 #define VERSION_H 3 4 + struct repository; 5 + 6 extern const char git_version_string[]; 7 extern const char git_built_from_commit_string[]; 8 ··· 15 error. Return 0 and put uname info into 'buf' otherwise. 16 */ 17 int get_uname_info(struct strbuf *buf, unsigned int full); 18 + 19 20 #endif /* VERSION_H */