Merge branch 'ty/migrate-excludes-file' into jch

The 'excludes_file' and various other global configuration variables
(including 'editor_program', 'pager_program', 'askpass_program', and
'push_default') have been migrated into the per-repository structure.

* ty/migrate-excludes-file:
  repository: adjust the comment of config_values_private_
  environment: move object_creation_mode into repo_config_values
  environment: move autorebase into repo_config_values
  environment: move push_default into repo_config_values
  environment: migrate apply_default_whitespace and apply_default_ignorewhitespace
  environment: move askpass_program into repo_config_values
  environment: move pager_program into repo_config_values
  environment: move editor_program into repo_config_values
  environment: move excludes_file into repo_config_values
  repository: introduce repo_config_values_clear()
jch
Junio C Hamano 2026-07-24 14:40:38 -07:00
commit 96e75340f9
13 changed files with 158 additions and 86 deletions

28
apply.c
View File

@ -47,11 +47,17 @@ struct gitdiff_data {
int p_value;
};

static void git_apply_config(void)
static void git_apply_config(struct repository *repo)
{
repo_config_get_string(the_repository, "apply.whitespace", &apply_default_whitespace);
repo_config_get_string(the_repository, "apply.ignorewhitespace", &apply_default_ignorewhitespace);
repo_config(the_repository, git_xmerge_config, NULL);
struct repo_config_values *cfg = repo_config_values(repo);

FREE_AND_NULL(cfg->apply_default_whitespace);
repo_config_get_string(repo, "apply.whitespace",
&cfg->apply_default_whitespace);
FREE_AND_NULL(cfg->apply_default_ignorewhitespace);
repo_config_get_string(repo, "apply.ignorewhitespace",
&cfg->apply_default_ignorewhitespace);
repo_config(repo, git_xmerge_config, NULL);
}

static int parse_whitespace_option(struct apply_state *state, const char *option)
@ -109,6 +115,8 @@ int init_apply_state(struct apply_state *state,
struct repository *repo,
const char *prefix)
{
struct repo_config_values *cfg = repo_config_values(repo);

memset(state, 0, sizeof(*state));
state->prefix = prefix;
state->repo = repo;
@ -126,10 +134,13 @@ int init_apply_state(struct apply_state *state,
strset_init(&state->kept_symlinks);
strbuf_init(&state->root, 0);

git_apply_config();
if (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))
git_apply_config(repo);

if (cfg->apply_default_whitespace &&
parse_whitespace_option(state, cfg->apply_default_whitespace))
return -1;
if (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace))
if (cfg->apply_default_ignorewhitespace &&
parse_ignorewhitespace_option(state, cfg->apply_default_ignorewhitespace))
return -1;
return 0;
}
@ -192,7 +203,8 @@ int check_apply_state(struct apply_state *state, int force_apply)

static void set_default_whitespace_mode(struct apply_state *state)
{
if (!state->whitespace_option && !apply_default_whitespace)
if (!state->whitespace_option &&
!repo_config_values(state->repo)->apply_default_whitespace)
state->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);
}


View File

@ -61,7 +61,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)

static int should_setup_rebase(const char *origin)
{
switch (autorebase) {
switch (repo_config_values(the_repository)->autorebase) {
case AUTOREBASE_NEVER:
return 0;
case AUTOREBASE_LOCAL:

View File

@ -74,6 +74,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,
struct remote *remote, struct ref *matched)
{
const char *branch_name;
struct repo_config_values *cfg = repo_config_values(the_repository);

if (remote->push.nr) {
struct refspec_item query = {
@ -89,7 +90,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,
}
}

if (push_default == PUSH_DEFAULT_UPSTREAM &&
if (cfg->push_default == PUSH_DEFAULT_UPSTREAM &&
skip_prefix(matched->name, "refs/heads/", &branch_name)) {
struct branch *branch = branch_get(branch_name);
if (branch->merge_nr == 1 && branch->merge[0]->src) {
@ -161,7 +162,7 @@ static NORETURN void die_push_simple(struct branch *branch,
* Don't show advice for people who explicitly set
* push.default.
*/
if (push_default == PUSH_DEFAULT_UNSPECIFIED)
if (cfg->push_default == PUSH_DEFAULT_UNSPECIFIED)
advice_pushdefault_maybe = _("\n"
"To choose either option permanently, "
"see push.default in 'git help config'.\n");
@ -232,8 +233,9 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)
struct branch *branch;
const char *dst;
int same_remote;
struct repo_config_values *cfg = repo_config_values(the_repository);

switch (push_default) {
switch (cfg->push_default) {
case PUSH_DEFAULT_MATCHING:
refspec_append(&rs, ":");
return;
@ -253,7 +255,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)
dst = branch->refname;
same_remote = !strcmp(remote->name, remote_for_branch(branch, NULL));

switch (push_default) {
switch (cfg->push_default) {
default:
case PUSH_DEFAULT_UNSPECIFIED:
case PUSH_DEFAULT_SIMPLE:

4
dir.c
View File

@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude")

void setup_standard_excludes(struct dir_struct *dir)
{
const char *excludes_file = repo_excludes_file(the_repository);

dir->exclude_per_dir = ".gitignore";

/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */
if (!excludes_file)
excludes_file = xdg_config_home("ignore");
if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
add_patterns_from_file_1(dir, excludes_file,
dir->untracked ? &dir->internal.ss_excludes_file : NULL);

View File

@ -29,8 +29,8 @@ const char *git_editor(void)
const char *editor = getenv("GIT_EDITOR");
int terminal_is_dumb = is_terminal_dumb();

if (!editor && editor_program)
editor = editor_program;
if (!editor)
editor = repo_config_values(the_repository)->editor_program;
if (!editor && !terminal_is_dumb)
editor = getenv("VISUAL");
if (!editor)

View File

@ -45,25 +45,17 @@ int minimum_abbrev = 4, default_abbrev = -1;
int assume_unchanged;
char *git_commit_encoding;
char *git_log_output_encoding;
char *apply_default_whitespace;
char *apply_default_ignorewhitespace;
int fsync_object_files = -1;
int use_fsync = -1;
enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;
enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;
char *editor_program;
char *askpass_program;
char *excludes_file;
enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
enum eol core_eol = EOL_UNSET;
int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;
char *check_roundtrip_encoding;
enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
#ifndef OBJECT_CREATION_MODE
#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
#endif
enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
int grafts_keep_true_parents;
unsigned long pack_size_limit_cfg;

@ -160,6 +152,16 @@ int repo_has_symlinks(struct repository *repo)
: platform_has_symlinks();
}

const char *repo_excludes_file(struct repository *repo)
{
struct repo_config_values *cfg = repo_config_values(repo);

if (!cfg->excludes_file)
cfg->excludes_file = xdg_config_home("ignore");

return cfg->excludes_file;
}

int have_git_dir(void)
{
return startup_info->have_repository
@ -455,8 +457,8 @@ int git_default_core_config(const char *var, const char *value,
}

if (!strcmp(var, "core.editor")) {
FREE_AND_NULL(editor_program);
return git_config_string(&editor_program, var, value);
FREE_AND_NULL(cfg->editor_program);
return git_config_string(&cfg->editor_program, var, value);
}

if (!strcmp(var, "core.commentchar") ||
@ -483,13 +485,13 @@ int git_default_core_config(const char *var, const char *value,
}

if (!strcmp(var, "core.askpass")) {
FREE_AND_NULL(askpass_program);
return git_config_string(&askpass_program, var, value);
FREE_AND_NULL(cfg->askpass_program);
return git_config_string(&cfg->askpass_program, var, value);
}

if (!strcmp(var, "core.excludesfile")) {
FREE_AND_NULL(excludes_file);
return git_config_pathname(&excludes_file, var, value);
FREE_AND_NULL(cfg->excludes_file);
return git_config_pathname(&cfg->excludes_file, var, value);
}

if (!strcmp(var, "core.whitespace")) {
@ -536,9 +538,9 @@ int git_default_core_config(const char *var, const char *value,
if (!value)
return config_error_nonbool(var);
if (!strcmp(value, "rename"))
object_creation_mode = OBJECT_CREATION_USES_RENAMES;
cfg->object_creation_mode = OBJECT_CREATION_USES_RENAMES;
else if (!strcmp(value, "link"))
object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;
cfg->object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;
else
die(_("invalid mode for object creation: %s"), value);
return 0;
@ -624,13 +626,13 @@ static int git_default_branch_config(const char *var, const char *value)
if (!value)
return config_error_nonbool(var);
else if (!strcmp(value, "never"))
autorebase = AUTOREBASE_NEVER;
cfg->autorebase = AUTOREBASE_NEVER;
else if (!strcmp(value, "local"))
autorebase = AUTOREBASE_LOCAL;
cfg->autorebase = AUTOREBASE_LOCAL;
else if (!strcmp(value, "remote"))
autorebase = AUTOREBASE_REMOTE;
cfg->autorebase = AUTOREBASE_REMOTE;
else if (!strcmp(value, "always"))
autorebase = AUTOREBASE_ALWAYS;
cfg->autorebase = AUTOREBASE_ALWAYS;
else
return error(_("malformed value for %s"), var);
return 0;
@ -642,21 +644,23 @@ static int git_default_branch_config(const char *var, const char *value)

static int git_default_push_config(const char *var, const char *value)
{
struct repo_config_values *cfg = repo_config_values(the_repository);

if (!strcmp(var, "push.default")) {
if (!value)
return config_error_nonbool(var);
else if (!strcmp(value, "nothing"))
push_default = PUSH_DEFAULT_NOTHING;
cfg->push_default = PUSH_DEFAULT_NOTHING;
else if (!strcmp(value, "matching"))
push_default = PUSH_DEFAULT_MATCHING;
cfg->push_default = PUSH_DEFAULT_MATCHING;
else if (!strcmp(value, "simple"))
push_default = PUSH_DEFAULT_SIMPLE;
cfg->push_default = PUSH_DEFAULT_SIMPLE;
else if (!strcmp(value, "upstream"))
push_default = PUSH_DEFAULT_UPSTREAM;
cfg->push_default = PUSH_DEFAULT_UPSTREAM;
else if (!strcmp(value, "tracking")) /* deprecated */
push_default = PUSH_DEFAULT_UPSTREAM;
cfg->push_default = PUSH_DEFAULT_UPSTREAM;
else if (!strcmp(value, "current"))
push_default = PUSH_DEFAULT_CURRENT;
cfg->push_default = PUSH_DEFAULT_CURRENT;
else {
error(_("malformed value for %s: %s"), var, value);
return error(_("must be one of nothing, matching, simple, "
@ -742,6 +746,15 @@ int git_default_config(const char *var, const char *value,
void repo_config_values_init(struct repo_config_values *cfg)
{
cfg->attributes_file = NULL;
cfg->excludes_file = NULL;
cfg->editor_program = NULL;
cfg->pager_program = NULL;
cfg->askpass_program = NULL;
cfg->apply_default_whitespace = NULL;
cfg->apply_default_ignorewhitespace = NULL;
cfg->push_default = PUSH_DEFAULT_UNSPECIFIED;
cfg->autorebase = AUTOREBASE_NEVER;
cfg->object_creation_mode = OBJECT_CREATION_MODE;
cfg->apply_sparse_checkout = 0;
cfg->protect_hfs = PROTECT_HFS_DEFAULT;
cfg->protect_ntfs = PROTECT_NTFS_DEFAULT;
@ -758,3 +771,14 @@ void repo_config_values_init(struct repo_config_values *cfg)
cfg->sparse_expect_files_outside_of_patterns = 0;
cfg->warn_on_object_refname_ambiguity = 1;
}

void repo_config_values_clear(struct repo_config_values *cfg)
{
FREE_AND_NULL(cfg->attributes_file);
FREE_AND_NULL(cfg->excludes_file);
FREE_AND_NULL(cfg->editor_program);
FREE_AND_NULL(cfg->pager_program);
FREE_AND_NULL(cfg->askpass_program);
FREE_AND_NULL(cfg->apply_default_whitespace);
FREE_AND_NULL(cfg->apply_default_ignorewhitespace);
}

View File

@ -87,9 +87,45 @@ extern const char * const local_repo_env[];
struct strvec;

struct repository;

/*
* NEEDSWORK: It would be better if these definitions could be moved to
* other more specific files, but care is needed to avoid circular
* inclusion issues.
*/
enum push_default_type {
PUSH_DEFAULT_NOTHING = 0,
PUSH_DEFAULT_MATCHING,
PUSH_DEFAULT_SIMPLE,
PUSH_DEFAULT_UPSTREAM,
PUSH_DEFAULT_CURRENT,
PUSH_DEFAULT_UNSPECIFIED
};

enum rebase_setup_type {
AUTOREBASE_NEVER = 0,
AUTOREBASE_LOCAL,
AUTOREBASE_REMOTE,
AUTOREBASE_ALWAYS
};

enum object_creation_mode {
OBJECT_CREATION_USES_HARDLINKS = 0,
OBJECT_CREATION_USES_RENAMES = 1
};

struct repo_config_values {
/* section "core" config values */
char *attributes_file;
char *excludes_file;
char *editor_program;
char *pager_program;
char *askpass_program;
char *apply_default_whitespace;
char *apply_default_ignorewhitespace;
enum push_default_type push_default;
enum rebase_setup_type autorebase;
enum object_creation_mode object_creation_mode;
int apply_sparse_checkout;
int trust_ctime;
int check_stat;
@ -157,10 +193,21 @@ int repo_trust_executable_bit(struct repository *repo);

int repo_has_symlinks(struct repository *repo);

const char *repo_excludes_file(struct repository *repo);

void repo_config_values_init(struct repo_config_values *cfg);

int is_bare_repository(struct repository *repo);

/*
* Frees memory allocated for dynamically loaded configuration values
* inside `repo_config_values`.
*
* As dynamically allocated variables are migrated into this struct,
* their FREE_AND_NULL() calls should be appended here.
*/
void repo_config_values_clear(struct repo_config_values *cfg);

/*
* TODO: All the below state either explicitly or implicitly relies on
* `the_repository`. We should eventually get rid of these and make the
@ -186,34 +233,8 @@ int have_git_dir(void);
/* Environment bits from configuration mechanism */
extern int minimum_abbrev, default_abbrev;
extern int assume_unchanged;
extern char *apply_default_whitespace;
extern char *apply_default_ignorewhitespace;
extern unsigned long pack_size_limit_cfg;

enum rebase_setup_type {
AUTOREBASE_NEVER = 0,
AUTOREBASE_LOCAL,
AUTOREBASE_REMOTE,
AUTOREBASE_ALWAYS
};
extern enum rebase_setup_type autorebase;

enum push_default_type {
PUSH_DEFAULT_NOTHING = 0,
PUSH_DEFAULT_MATCHING,
PUSH_DEFAULT_SIMPLE,
PUSH_DEFAULT_UPSTREAM,
PUSH_DEFAULT_CURRENT,
PUSH_DEFAULT_UNSPECIFIED
};
extern enum push_default_type push_default;

enum object_creation_mode {
OBJECT_CREATION_USES_HARDLINKS = 0,
OBJECT_CREATION_USES_RENAMES = 1
};
extern enum object_creation_mode object_creation_mode;

extern int grafts_keep_true_parents;

const char *get_log_output_encoding(void);
@ -222,10 +243,6 @@ const char *get_commit_output_encoding(void);
extern char *git_commit_encoding;
extern char *git_log_output_encoding;

extern char *editor_program;
extern char *askpass_program;
extern char *excludes_file;

/*
* The character that begins a commented line in user-editable file
* that is subject to stripspace.

View File

@ -409,11 +409,12 @@ int finalize_object_file_flags(struct repository *repo,
{
unsigned retries = 0;
int ret;
struct repo_config_values *cfg = repo_config_values(repo);

retry:
ret = 0;

if (object_creation_mode == OBJECT_CREATION_USES_RENAMES)
if (cfg->object_creation_mode == OBJECT_CREATION_USES_RENAMES)
goto try_rename;
else if (link(tmpfile, filename))
ret = errno;

32
pager.c
View File

@ -5,6 +5,8 @@
#include "run-command.h"
#include "sigchain.h"
#include "alias.h"
#include "repository.h"
#include "environment.h"

int pager_use_color = 1;

@ -13,7 +15,6 @@ int pager_use_color = 1;
#endif

static struct child_process pager_process;
static char *pager_program;
static int old_fd1 = -1, old_fd2 = -1;

/* Is the value coming back from term_columns() just a guess? */
@ -75,10 +76,17 @@ static void wait_for_pager_signal(int signo)

static int core_pager_config(const char *var, const char *value,
const struct config_context *ctx UNUSED,
void *data UNUSED)
void *data)
{
if (!strcmp(var, "core.pager"))
return git_config_string(&pager_program, var, value);
struct repository *r = data;

if (!strcmp(var, "core.pager")) {
struct repo_config_values *cfg = repo_config_values(r);

FREE_AND_NULL(cfg->pager_program);
return git_config_string(&cfg->pager_program, var, value);
}

return 0;
}

@ -91,10 +99,12 @@ const char *git_pager(struct repository *r, int stdout_is_tty)

pager = getenv("GIT_PAGER");
if (!pager) {
if (!pager_program)
struct repo_config_values *cfg = repo_config_values(r);

if (!cfg->pager_program)
read_early_config(r,
core_pager_config, NULL);
pager = pager_program;
core_pager_config, r);
pager = cfg->pager_program;
}
if (!pager)
pager = getenv("PAGER");
@ -302,7 +312,11 @@ int check_pager_config(struct repository *r, const char *cmd)

read_early_config(r, pager_command_config, &data);

if (data.value)
pager_program = data.value;
if (data.value) {
struct repo_config_values *cfg = repo_config_values(r);

free(cfg->pager_program);
cfg->pager_program = data.value;
}
return data.want;
}

View File

@ -3,6 +3,7 @@
#include "git-compat-util.h"
#include "parse.h"
#include "environment.h"
#include "repository.h"
#include "run-command.h"
#include "strbuf.h"
#include "prompt.h"
@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags)

askpass = getenv("GIT_ASKPASS");
if (!askpass)
askpass = askpass_program;
askpass = repo_config_values(the_repository)->askpass_program;
if (!askpass)
askpass = getenv("SSH_ASKPASS");
if (askpass && *askpass)

View File

@ -1933,7 +1933,7 @@ static char *branch_get_push_1(struct repository *repo,
if (remote->mirror)
return tracking_for_push_dest(remote, branch->refname, err);

switch (push_default) {
switch (repo_config_values(repo)->push_default) {
case PUSH_DEFAULT_NOTHING:
return error_buf(err, _("push has no destination (push.default is 'nothing')"));


View File

@ -390,6 +390,7 @@ void repo_clear(struct repository *repo)
FREE_AND_NULL(repo->parsed_objects);

repo_settings_clear(repo);
repo_config_values_clear(&repo->config_values_private_);

if (repo->config) {
git_configset_clear(repo->config);

View File

@ -167,7 +167,7 @@ struct repository {
/* Repository's compatibility hash algorithm. */
const struct git_hash_algo *compat_hash_algo;

/* Repository's config values parsed by git_default_config() */
/* Repository-specific configuration values. */
struct repo_config_values config_values_private_;

/* Repository's reference storage format, as serialized on disk. */