aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2012-07-09 09:00:35 -0700
committerJunio C Hamano <gitster@pobox.com>2012-07-09 09:00:36 -0700
commitd02d7ac303b1a22c7de4a6c3a00074d38b498134 (patch)
tree00bb203b30a44c8e389119694f722a5d09bcda49
parent8228a23b35f6bd34a87d4d839d06e7c18ae9e750 (diff)
parent0e8593dc5b812df00400347e88f5707225fe831e (diff)
downloadgit-d02d7ac303b1a22c7de4a6c3a00074d38b498134.tar.gz
git-d02d7ac303b1a22c7de4a6c3a00074d38b498134.tar.xz
Merge branch 'mm/config-xdg'
Teach git to read various information from $XDG_CONFIG_HOME/git/ to allow the user to avoid cluttering $HOME. * mm/config-xdg: config: write to $XDG_CONFIG_HOME/git/config file when appropriate Let core.attributesfile default to $XDG_CONFIG_HOME/git/attributes Let core.excludesfile default to $XDG_CONFIG_HOME/git/ignore config: read (but not write) from $XDG_CONFIG_HOME/git/config file
-rw-r--r--Documentation/config.txt8
-rw-r--r--Documentation/git-config.txt17
-rw-r--r--Documentation/gitattributes.txt2
-rw-r--r--Documentation/gitignore.txt4
-rw-r--r--attr.c17
-rw-r--r--builtin/config.c25
-rw-r--r--cache.h3
-rw-r--r--config.c23
-rw-r--r--dir.c7
-rw-r--r--path.c41
-rwxr-xr-xt/t1306-xdg-files.sh158
11 files changed, 272 insertions, 33 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 0bcea8a94..c6ff15e59 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -484,7 +484,9 @@ core.excludesfile::
'.git/info/exclude', git looks into this file for patterns
of files which are not meant to be tracked. "`~/`" is expanded
to the value of `$HOME` and "`~user/`" to the specified user's
- home directory. See linkgit:gitignore[5].
+ home directory. Its default value is $XDG_CONFIG_HOME/git/ignore.
+ If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/ignore
+ is used instead. See linkgit:gitignore[5].
core.askpass::
Some commands (e.g. svn and http interfaces) that interactively
@@ -499,7 +501,9 @@ core.attributesfile::
In addition to '.gitattributes' (per-directory) and
'.git/info/attributes', git looks into this file for attributes
(see linkgit:gitattributes[5]). Path expansions are made the same
- way as for `core.excludesfile`.
+ way as for `core.excludesfile`. Its default value is
+ $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME is either not
+ set or empty, $HOME/.config/git/attributes is used instead.
core.editor::
Commands such as `commit` and `tag` that lets you edit
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index d9463cb38..2d6ef32a0 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -97,10 +97,11 @@ OPTIONS
--global::
For writing options: write to global ~/.gitconfig file rather than
- the repository .git/config.
+ the repository .git/config, write to $XDG_CONFIG_HOME/git/config file
+ if this file exists and the ~/.gitconfig file doesn't.
+
-For reading options: read only from global ~/.gitconfig rather than
-from all available files.
+For reading options: read only from global ~/.gitconfig and from
+$XDG_CONFIG_HOME/git/config rather than from all available files.
+
See also <<FILES>>.
@@ -194,7 +195,7 @@ See also <<FILES>>.
FILES
-----
-If not set explicitly with '--file', there are three files where
+If not set explicitly with '--file', there are four files where
'git config' will search for configuration options:
$GIT_DIR/config::
@@ -204,6 +205,14 @@ $GIT_DIR/config::
User-specific configuration file. Also called "global"
configuration file.
+$XDG_CONFIG_HOME/git/config::
+ Second user-specific configuration file. If $XDG_CONFIG_HOME is not set
+ or empty, $HOME/.config/git/config will be used. Any single-valued
+ variable set in this file will be overwritten by whatever is in
+ ~/.gitconfig. It is a good idea not to create this file if
+ you sometimes use older versions of Git, as support for this
+ file was added fairly recently.
+
$(prefix)/etc/gitconfig::
System-wide configuration file.
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 80120ea14..e16f3e175 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -75,6 +75,8 @@ repositories (i.e., attributes of interest to all users) should go into
`.gitattributes` files. Attributes that should affect all repositories
for a single user should be placed in a file specified by the
`core.attributesfile` configuration option (see linkgit:git-config[1]).
+Its default value is $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME
+is either not set or empty, $HOME/.config/git/attributes is used instead.
Attributes for all users on a system should be placed in the
`$(prefix)/etc/gitattributes` file.
diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index 2e7328b83..c1f692a71 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -50,7 +50,9 @@ the repository but are specific to one user's workflow) should go into
the `$GIT_DIR/info/exclude` file. Patterns which a user wants git to
ignore in all situations (e.g., backup or temporary files generated by
the user's editor of choice) generally go into a file specified by
-`core.excludesfile` in the user's `~/.gitconfig`.
+`core.excludesfile` in the user's `~/.gitconfig`. Its default value is
+$XDG_CONFIG_HOME/git/ignore. If $XDG_CONFIG_HOME is either not set or empty,
+$HOME/.config/git/ignore is used instead.
The underlying git plumbing tools, such as
'git ls-files' and 'git read-tree', read
diff --git a/attr.c b/attr.c
index 303751f6c..aef93d896 100644
--- a/attr.c
+++ b/attr.c
@@ -497,6 +497,7 @@ static int git_attr_system(void)
static void bootstrap_attr_stack(void)
{
struct attr_stack *elem;
+ char *xdg_attributes_file;
if (attr_stack)
return;
@@ -515,13 +516,15 @@ static void bootstrap_attr_stack(void)
}
}
- if (git_attributes_file) {
- elem = read_attr_from_file(git_attributes_file, 1);
- if (elem) {
- elem->origin = NULL;
- elem->prev = attr_stack;
- attr_stack = elem;
- }
+ if (!git_attributes_file) {
+ home_config_paths(NULL, &xdg_attributes_file, "attributes");
+ git_attributes_file = xdg_attributes_file;
+ }
+ elem = read_attr_from_file(git_attributes_file, 1);
+ if (elem) {
+ elem->origin = NULL;
+ elem->prev = attr_stack;
+ attr_stack = elem;
}
if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
diff --git a/builtin/config.c b/builtin/config.c
index 33c8820af..e8e1c0a45 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -161,7 +161,7 @@ static int show_config(const char *key_, const char *value_, void *cb)
static int get_value(const char *key_, const char *regex_)
{
int ret = -1;
- char *global = NULL, *repo_config = NULL;
+ char *global = NULL, *xdg = NULL, *repo_config = NULL;
const char *system_wide = NULL, *local;
struct config_include_data inc = CONFIG_INCLUDE_INIT;
config_fn_t fn;
@@ -169,12 +169,10 @@ static int get_value(const char *key_, const char *regex_)
local = given_config_file;
if (!local) {
- const char *home = getenv("HOME");
local = repo_config = git_pathdup("config");
- if (home)
- global = xstrdup(mkpath("%s/.gitconfig", home));
if (git_config_system())
system_wide = git_etc_gitconfig();
+ home_config_paths(&global, &xdg, "config");
}
if (use_key_regexp) {
@@ -229,6 +227,8 @@ static int get_value(const char *key_, const char *regex_)
if (do_all && system_wide)
git_config_from_file(fn, system_wide, data);
+ if (do_all && xdg)
+ git_config_from_file(fn, xdg, data);
if (do_all && global)
git_config_from_file(fn, global, data);
if (do_all)
@@ -238,6 +238,8 @@ static int get_value(const char *key_, const char *regex_)
git_config_from_file(fn, local, data);
if (!do_all && !seen && global)
git_config_from_file(fn, global, data);
+ if (!do_all && !seen && xdg)
+ git_config_from_file(fn, xdg, data);
if (!do_all && !seen && system_wide)
git_config_from_file(fn, system_wide, data);
@@ -255,6 +257,7 @@ static int get_value(const char *key_, const char *regex_)
free_strings:
free(repo_config);
free(global);
+ free(xdg);
return ret;
}
@@ -379,13 +382,17 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
if (use_global_config) {
- char *home = getenv("HOME");
- if (home) {
- char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+ char *user_config = NULL;
+ char *xdg_config = NULL;
+
+ home_config_paths(&user_config, &xdg_config, "config");
+
+ if (access(user_config, R_OK) && !access(xdg_config, R_OK))
+ given_config_file = xdg_config;
+ else if (user_config)
given_config_file = user_config;
- } else {
+ else
die("$HOME not set");
- }
}
else if (use_system_config)
given_config_file = git_etc_gitconfig();
diff --git a/cache.h b/cache.h
index 89581041c..c22b92898 100644
--- a/cache.h
+++ b/cache.h
@@ -622,6 +622,8 @@ extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
__attribute__((format (printf, 3, 4)));
extern char *git_pathdup(const char *fmt, ...)
__attribute__((format (printf, 1, 2)));
+extern char *mkpathdup(const char *fmt, ...)
+ __attribute__((format (printf, 1, 2)));
/* Return a statically allocated filename matching the sha1 signature */
extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
@@ -711,6 +713,7 @@ int set_shared_perm(const char *path, int mode);
int safe_create_leading_directories(char *path);
int safe_create_leading_directories_const(const char *path);
int mkdir_in_gitdir(const char *path);
+extern void home_config_paths(char **global, char **xdg, char *file);
extern char *expand_user_path(const char *path);
const char *enter_repo(const char *path, int strict);
static inline int is_absolute_path(const char *path)
diff --git a/config.c b/config.c
index 71ef171ca..d28a499b0 100644
--- a/config.c
+++ b/config.c
@@ -929,7 +929,10 @@ int git_config_system(void)
int git_config_early(config_fn_t fn, void *data, const char *repo_config)
{
int ret = 0, found = 0;
- const char *home = NULL;
+ char *xdg_config = NULL;
+ char *user_config = NULL;
+
+ home_config_paths(&user_config, &xdg_config, "config");
if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
@@ -937,14 +940,14 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
found += 1;
}
- home = getenv("HOME");
- if (home) {
- char buf[PATH_MAX];
- char *user_config = mksnpath(buf, sizeof(buf), "%s/.gitconfig", home);
- if (!access(user_config, R_OK)) {
- ret += git_config_from_file(fn, user_config, data);
- found += 1;
- }
+ if (!access(xdg_config, R_OK)) {
+ ret += git_config_from_file(fn, xdg_config, data);
+ found += 1;
+ }
+
+ if (!access(user_config, R_OK)) {
+ ret += git_config_from_file(fn, user_config, data);
+ found += 1;
}
if (repo_config && !access(repo_config, R_OK)) {
@@ -963,6 +966,8 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
break;
}
+ free(xdg_config);
+ free(user_config);
return ret == 0 ? found : ret;
}
diff --git a/dir.c b/dir.c
index 0015cc54f..a772c6dc6 100644
--- a/dir.c
+++ b/dir.c
@@ -1303,12 +1303,17 @@ int remove_dir_recursively(struct strbuf *path, int flag)
void setup_standard_excludes(struct dir_struct *dir)
{
const char *path;
+ char *xdg_path;
dir->exclude_per_dir = ".gitignore";
path = git_path("info/exclude");
+ if (!excludes_file) {
+ home_config_paths(NULL, &xdg_path, "ignore");
+ excludes_file = xdg_path;
+ }
if (!access(path, R_OK))
add_excludes_from_file(dir, path);
- if (excludes_file && !access(excludes_file, R_OK))
+ if (!access(excludes_file, R_OK))
add_excludes_from_file(dir, excludes_file);
}
diff --git a/path.c b/path.c
index 6f2aa699a..66acd24fa 100644
--- a/path.c
+++ b/path.c
@@ -87,6 +87,21 @@ char *git_pathdup(const char *fmt, ...)
return xstrdup(path);
}
+char *mkpathdup(const char *fmt, ...)
+{
+ char *path;
+ struct strbuf sb = STRBUF_INIT;
+ va_list args;
+
+ va_start(args, fmt);
+ strbuf_vaddf(&sb, fmt, args);
+ va_end(args);
+ path = xstrdup(cleanup_path(sb.buf));
+
+ strbuf_release(&sb);
+ return path;
+}
+
char *mkpath(const char *fmt, ...)
{
va_list args;
@@ -122,6 +137,32 @@ char *git_path(const char *fmt, ...)
return cleanup_path(pathname);
}
+void home_config_paths(char **global, char **xdg, char *file)
+{
+ char *xdg_home = getenv("XDG_CONFIG_HOME");
+ char *home = getenv("HOME");
+ char *to_free = NULL;
+
+ if (!home) {
+ if (global)
+ *global = NULL;
+ } else {
+ if (!xdg_home) {
+ to_free = mkpathdup("%s/.config", home);
+ xdg_home = to_free;
+ }
+ if (global)
+ *global = mkpathdup("%s/.gitconfig", home);
+ }
+
+ if (!xdg_home)
+ *xdg = NULL;
+ else
+ *xdg = mkpathdup("%s/git/%s", xdg_home, file);
+
+ free(to_free);
+}
+
char *git_path_submodule(const char *path, const char *fmt, ...)
{
char *pathname = get_pathname();
diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh
new file mode 100755
index 000000000..3c75c3f2e
--- /dev/null
+++ b/t/t1306-xdg-files.sh
@@ -0,0 +1,158 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Valentin Duperray, Lucien Kong, Franck Jonas,
+# Thomas Nguy, Khoi Nguyen
+# Grenoble INP Ensimag
+#
+
+test_description='Compatibility with $XDG_CONFIG_HOME/git/ files'
+
+. ./test-lib.sh
+
+test_expect_success 'read config: xdg file exists and ~/.gitconfig doesn'\''t' '
+ mkdir -p .config/git &&
+ echo "[alias]" >.config/git/config &&
+ echo " myalias = !echo in_config" >>.config/git/config &&
+ echo in_config >expected &&
+ git myalias >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'read config: xdg file exists and ~/.gitconfig exists' '
+ >.gitconfig &&
+ echo "[alias]" >.gitconfig &&
+ echo " myalias = !echo in_gitconfig" >>.gitconfig &&
+ echo in_gitconfig >expected &&
+ git myalias >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'read with --get: xdg file exists and ~/.gitconfig doesn'\''t' '
+ rm .gitconfig &&
+ echo "[user]" >.config/git/config &&
+ echo " name = read_config" >>.config/git/config &&
+ echo read_config >expected &&
+ git config --get user.name >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'read with --get: xdg file exists and ~/.gitconfig exists' '
+ >.gitconfig &&
+ echo "[user]" >.gitconfig &&
+ echo " name = read_gitconfig" >>.gitconfig &&
+ echo read_gitconfig >expected &&
+ git config --get user.name >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'read with --list: xdg file exists and ~/.gitconfig doesn'\''t' '
+ rm .gitconfig &&
+ echo user.name=read_config >expected &&
+ git config --global --list >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'read with --list: xdg file exists and ~/.gitconfig exists' '
+ >.gitconfig &&
+ echo "[user]" >.gitconfig &&
+ echo " name = read_gitconfig" >>.gitconfig &&
+ echo user.name=read_gitconfig >expected &&
+ git config --global --list >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'Setup' '
+ git init git &&
+ cd git &&
+ echo foo >to_be_excluded
+'
+
+
+test_expect_success 'Exclusion of a file in the XDG ignore file' '
+ mkdir -p "$HOME"/.config/git/ &&
+ echo to_be_excluded >"$HOME"/.config/git/ignore &&
+ test_must_fail git add to_be_excluded
+'
+
+
+test_expect_success 'Exclusion in both XDG and local ignore files' '
+ echo to_be_excluded >.gitignore &&
+ test_must_fail git add to_be_excluded
+'
+
+
+test_expect_success 'Exclusion in a non-XDG global ignore file' '
+ rm .gitignore &&
+ echo >"$HOME"/.config/git/ignore &&
+ echo to_be_excluded >"$HOME"/my_gitignore &&
+ git config core.excludesfile "$HOME"/my_gitignore &&
+ test_must_fail git add to_be_excluded
+'
+
+
+test_expect_success 'Checking attributes in the XDG attributes file' '
+ echo foo >f &&
+ git check-attr -a f >actual &&
+ test_line_count -eq 0 actual &&
+ echo "f attr_f" >"$HOME"/.config/git/attributes &&
+ echo "f: attr_f: set" >expected &&
+ git check-attr -a f >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'Checking attributes in both XDG and local attributes files' '
+ echo "f -attr_f" >.gitattributes &&
+ echo "f: attr_f: unset" >expected &&
+ git check-attr -a f >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'Checking attributes in a non-XDG global attributes file' '
+ test_might_fail rm .gitattributes &&
+ echo "f attr_f=test" >"$HOME"/my_gitattributes &&
+ git config core.attributesfile "$HOME"/my_gitattributes &&
+ echo "f: attr_f: test" >expected &&
+ git check-attr -a f >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'write: xdg file exists and ~/.gitconfig doesn'\''t' '
+ mkdir -p "$HOME"/.config/git &&
+ >"$HOME"/.config/git/config &&
+ test_might_fail rm "$HOME"/.gitconfig &&
+ git config --global user.name "write_config" &&
+ echo "[user]" >expected &&
+ echo " name = write_config" >>expected &&
+ test_cmp expected "$HOME"/.config/git/config
+'
+
+
+test_expect_success 'write: xdg file exists and ~/.gitconfig exists' '
+ >"$HOME"/.gitconfig &&
+ git config --global user.name "write_gitconfig" &&
+ echo "[user]" >expected &&
+ echo " name = write_gitconfig" >>expected &&
+ test_cmp expected "$HOME"/.gitconfig
+'
+
+
+test_expect_success 'write: ~/.config/git/ exists and config file doesn'\''t' '
+ test_might_fail rm "$HOME"/.gitconfig &&
+ test_might_fail rm "$HOME"/.config/git/config &&
+ git config --global user.name "write_gitconfig" &&
+ echo "[user]" >expected &&
+ echo " name = write_gitconfig" >>expected &&
+ test_cmp expected "$HOME"/.gitconfig
+'
+
+
+test_done