Merge branch 'ua/os-version-capability'

The value of "uname -s" is by default sent over the wire as a part
of the "version" capability.

* ua/os-version-capability:
  agent: advertise OS name via agent capability
  t5701: add setup test to remove side-effect dependency
  version: extend get_uname_info() to hide system details
  version: refactor get_uname_info()
  version: refactor redact_non_printables()
  version: replace manual ASCII checks with isprint() for clarity
maint
Junio C Hamano 2025-02-27 15:22:59 -08:00
commit 9d8cce051a
7 changed files with 115 additions and 23 deletions

View File

@ -184,9 +184,13 @@ form `agent=X`) to notify the client that the server is running version
the `agent` capability with a value `Y` (in the form `agent=Y`) in its
request to the server (but it MUST NOT do so if the server did not
advertise the agent capability). The `X` and `Y` strings may contain any
printable ASCII characters except space (i.e., the byte range 32 < x <
127), and are typically of the form "package/version" (e.g.,
"git/1.8.3.1"). The agent strings are purely informative for statistics
printable ASCII characters except space (i.e., the byte range 33 <= x <=
126), and are typically of the form "package/version-os" (e.g.,
"git/1.8.3.1-Linux") where `os` is the operating system name (e.g.,
"Linux"). `X` and `Y` can be configured using the GIT_USER_AGENT
environment variable and it takes priority. The `os` is
retrieved using the 'sysname' field of the `uname(2)` system call
or its equivalent. The agent strings are purely informative for statistics
and debugging purposes, and MUST NOT be used to programmatically assume
the presence or absence of particular features.


View File

@ -12,10 +12,10 @@
#include "diagnose.h"
#include "object-file.h"
#include "setup.h"
#include "version.h"

static void get_system_info(struct strbuf *sys_info)
{
struct utsname uname_info;
char *shell = NULL;

/* get git version from native cmd */
@ -24,16 +24,7 @@ static void get_system_info(struct strbuf *sys_info)

/* system call for other version info */
strbuf_addstr(sys_info, "uname: ");
if (uname(&uname_info))
strbuf_addf(sys_info, _("uname() failed with error '%s' (%d)\n"),
strerror(errno),
errno);
else
strbuf_addf(sys_info, "%s %s %s %s\n",
uname_info.sysname,
uname_info.release,
uname_info.version,
uname_info.machine);
get_uname_info(sys_info, 1);

strbuf_addstr(sys_info, _("compiler info: "));
get_compiler_info(sys_info);

View File

@ -624,7 +624,7 @@ const char *parse_feature_value(const char *feature_list, const char *feature, s
*offset = found + len - orig_start;
return value;
}
/* feature with a value (e.g., "agent=git/1.2.3") */
/* feature with a value (e.g., "agent=git/1.2.3-Linux") */
else if (*value == '=') {
size_t end;


View File

@ -7,24 +7,40 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME

. ./test-lib.sh

test_expect_success 'test capability advertisement' '
test_expect_success 'setup to generate files with expected content' '
printf "agent=git/%s" "$(git version | cut -d" " -f3)" >agent_capability &&

test_oid_cache <<-EOF &&
wrong_algo sha1:sha256
wrong_algo sha256:sha1
EOF

if test_have_prereq WINDOWS
then
printf "agent=FAKE\n" >agent_capability
else
printf -- "-%s\n" $(uname -s | test_redact_non_printables) >>agent_capability
fi &&
cat >expect.base <<-EOF &&
version 2
agent=git/$(git version | cut -d" " -f3)
$(cat agent_capability)
ls-refs=unborn
fetch=shallow wait-for-done
server-option
object-format=$(test_oid algo)
EOF
cat >expect.trailer <<-EOF &&
cat >expect.trailer <<-EOF
0000
EOF
'

test_expect_success 'test capability advertisement' '
cat expect.base expect.trailer >expect &&

if test_have_prereq WINDOWS
then
GIT_USER_AGENT=FAKE && export GIT_USER_AGENT
fi &&
GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \
--advertise-capabilities >out &&
test-tool pkt-line unpack <out >actual &&
@ -355,6 +371,10 @@ test_expect_success 'test capability advertisement with uploadpack.advertiseBund
expect.extra \
expect.trailer >expect &&

if test_have_prereq WINDOWS
then
GIT_USER_AGENT=FAKE && export GIT_USER_AGENT
fi &&
GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \
--advertise-capabilities >out &&
test-tool pkt-line unpack <out >actual &&

View File

@ -2043,3 +2043,11 @@ test_trailing_hash () {
test-tool hexdump |
sed "s/ //g"
}

# Trim and replace each character with ascii code below 32 or above
# 127 (included) using a dot '.' character.
# Octal intervals \001-\040 and \177-\377
# correspond to decimal intervals 1-32 and 127-255
test_redact_non_printables () {
tr -d "\n\r" | tr "[\001-\040][\177-\377]" "."
}

View File

@ -1,6 +1,9 @@
#define USE_THE_REPOSITORY_VARIABLE

#include "git-compat-util.h"
#include "version.h"
#include "strbuf.h"
#include "gettext.h"

#ifndef GIT_VERSION_H
# include "version-def.h"
@ -11,6 +14,19 @@
const char git_version_string[] = GIT_VERSION;
const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;

/*
* Trim and replace each character with ascii code below 32 or above
* 127 (included) using a dot '.' character.
*/
static void redact_non_printables(struct strbuf *buf)
{
strbuf_trim(buf);
for (size_t i = 0; i < buf->len; i++) {
if (!isprint(buf->buf[i]) || buf->buf[i] == ' ')
buf->buf[i] = '.';
}
}

const char *git_user_agent(void)
{
static const char *agent = NULL;
@ -24,6 +40,27 @@ const char *git_user_agent(void)
return agent;
}

/*
Retrieve, sanitize and cache operating system info for subsequent
calls. Return a pointer to the sanitized operating system info
string.
*/
static const char *os_info(void)
{
static const char *os = NULL;

if (!os) {
struct strbuf buf = STRBUF_INIT;

get_uname_info(&buf, 0);
/* Sanitize the os information immediately */
redact_non_printables(&buf);
os = strbuf_detach(&buf, NULL);
}

return os;
}

const char *git_user_agent_sanitized(void)
{
static const char *agent = NULL;
@ -32,13 +69,35 @@ const char *git_user_agent_sanitized(void)
struct strbuf buf = STRBUF_INIT;

strbuf_addstr(&buf, git_user_agent());
strbuf_trim(&buf);
for (size_t i = 0; i < buf.len; i++) {
if (buf.buf[i] <= 32 || buf.buf[i] >= 127)
buf.buf[i] = '.';

if (!getenv("GIT_USER_AGENT")) {
strbuf_addch(&buf, '-');
strbuf_addstr(&buf, os_info());
}
agent = buf.buf;
redact_non_printables(&buf);
agent = strbuf_detach(&buf, NULL);
}

return agent;
}

int get_uname_info(struct strbuf *buf, unsigned int full)
{
struct utsname uname_info;

if (uname(&uname_info)) {
strbuf_addf(buf, _("uname() failed with error '%s' (%d)\n"),
strerror(errno),
errno);
return -1;
}
if (full)
strbuf_addf(buf, "%s %s %s %s\n",
uname_info.sysname,
uname_info.release,
uname_info.version,
uname_info.machine);
else
strbuf_addf(buf, "%s\n", uname_info.sysname);
return 0;
}

View File

@ -1,10 +1,20 @@
#ifndef VERSION_H
#define VERSION_H

struct repository;

extern const char git_version_string[];
extern const char git_built_from_commit_string[];

const char *git_user_agent(void);
const char *git_user_agent_sanitized(void);

/*
Try to get information about the system using uname(2).
Return -1 and put an error message into 'buf' in case of uname()
error. Return 0 and put uname info into 'buf' otherwise.
*/
int get_uname_info(struct strbuf *buf, unsigned int full);


#endif /* VERSION_H */