Merge branch 'ic/includeif-hostname' into seen

The conditional configuration inclusion mechanism via 'includeIf'
has been extended to support matching the machine's hostname, and
the system's hostname as seen by Git can now be queried using 'git
var GIT_HOSTNAME'.

* ic/includeif-hostname:
  config: learn the "hostname:" includeIf condition
  t6500: get hostname from "git var"
  var: add GIT_HOSTNAME
seen
Junio C Hamano 2026-10-12 14:23:54 -07:00
commit ddd528c137
7 changed files with 99 additions and 1 deletions

View File

@ -232,6 +232,16 @@ As for the naming of this keyword, it is for forwards compatibility with
a naming scheme that supports more variable-based include conditions,
but currently Git only supports the exact keyword described above.

`hostname`::
The data that follows the keyword `hostname:` is taken to be a
pattern with standard globbing wildcards. If the hostname of
the machine matches the pattern, the include condition is met.
+
Depending on how your system is configured, the hostname used for
matching may be short (e.g., "myhost") or a fully qualified domain
name ("myhost.example.com"). Run `git var GIT_HOSTNAME` to see the
hostname Git uses (see linkgit:git-var[1]).

A few more notes on matching via `gitdir` and `gitdir/i`:

* Symlinks in `$GIT_DIR` are not resolved before matching.
@ -315,6 +325,10 @@ Example
path = foo.inc
[remote "origin"]
url = https://example.com/git

; include only if the hostname of the machine matches some-hostname
[includeIf "hostname:some-hostname"]
path = foo.inc
----

Values

View File

@ -130,6 +130,11 @@ endif::git-default-pager[]
The path to the global (per-user) configuration files, if any.
This variable can have multiple values.

`GIT_HOSTNAME`::
The hostname of the machine, as reported by the operating system.
This is the hostname matched by `includeIf "hostname:..."` (see
linkgit:git-config[1]).

Most path values contain only one value. However, some can contain multiple
values, which are separated by newlines (or NUL bytes if `-z` is given),
and are listed in order from highest to lowest priority. When querying

View File

@ -181,6 +181,15 @@ static void git_config_val_global(struct string_list *list)
free(user);
}

static char *hostname(int ident_flag UNUSED)
{
char buf[HOST_NAME_MAX + 1];

if (xgethostname(buf, sizeof(buf)))
return NULL;
return xstrdup(buf);
}

struct git_var {
const char *name;
char *(*read)(int);
@ -255,6 +264,10 @@ static struct git_var git_vars[] = {
.name = "GIT_CONFIG_GLOBAL",
.multiread = git_config_val_global,
},
{
.name = "GIT_HOSTNAME",
.read = hostname,
},
{
.name = "",
.read = NULL,

View File

@ -316,6 +316,21 @@ static int include_by_branch(struct config_include_data *data,
return ret;
}

static int include_by_hostname(const char *cond, size_t cond_len)
{
int ret;
char my_host[HOST_NAME_MAX + 1];
char *pattern;

if (xgethostname(my_host, sizeof(my_host)))
return 0;

pattern = xmemdupz(cond, cond_len);
ret = !wildmatch(pattern, my_host, 0);
free(pattern);
return ret;
}

static int add_remote_url(const char *var, const char *value,
const struct config_context *ctx UNUSED, void *data)
{
@ -411,6 +426,8 @@ static int include_condition_is_true(const struct key_value_info *kvi,
else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
&cond_len))
return include_by_remote_url(inc, cond, cond_len);
else if (skip_prefix_mem(cond, cond_len, "hostname:", &cond, &cond_len))
return include_by_hostname(cond, cond_len);

/* unknown conditionals are always false */
return 0;

View File

@ -224,6 +224,11 @@ test_expect_success 'GIT_CONFIG_GLOBAL points to the correct location' '
)
'

test_expect_success 'GIT_HOSTNAME is not empty' '
hostname=$(git var GIT_HOSTNAME) &&
test -n "$hostname"
'

# For git var -l, we check only a representative variable;
# testing the whole output would make our test too brittle with
# respect to unrelated changes in the test suite's environment.

View File

@ -524,4 +524,48 @@ test_expect_success 'conditional include, worktree without repository but explic
test_must_fail nongit git --git-dir=nonexistent config get foo.bar
'

test_expect_success 'conditional include, hostname' '
hostname=$(git var GIT_HOSTNAME) &&
cat >>.git/config <<-EOF &&
[includeIf "hostname:${hostname}a"]
path = bar12
EOF
cat >>.git/bar12 <<-EOF &&
[test]
twelve=12
EOF

test_must_fail git config test.twelve &&

cat >>.git/config <<-EOF &&
[includeIf "hostname:$hostname"]
path = bar12
EOF
echo 12 >expect &&
git config test.twelve >actual &&
test_cmp expect actual
'

test_expect_success 'conditional include, hostname, wildcard' '
hostname=$(git var GIT_HOSTNAME) &&
cat >>.git/config <<-EOF &&
[includeIf "hostname:${hostname}a*"]
path = bar13
EOF
cat >>.git/bar13 <<-EOF &&
[test]
thirteen = 13
EOF

test_must_fail git config test.thirteen &&

cat >>.git/config <<-EOF &&
[includeIf "hostname:$hostname*"]
path = bar13
EOF
echo 13 >expect &&
git config test.thirteen >actual &&
test_cmp expect actual
'

test_done

View File

@ -413,7 +413,7 @@ test_expect_success 'background auto gc respects lock for all operations' '

# now fake a concurrent gc that holds the lock; we can use our
# shell pid so that it looks valid.
hostname=$(hostname || echo unknown) &&
hostname=$(git var GIT_HOSTNAME || echo unknown) &&
shell_pid=$$ &&
if test_have_prereq MINGW && test -f /proc/$shell_pid/winpid
then