From 21cf32279120799a766d22416be7d82d9ecfbd04 Mon Sep 17 00:00:00 2001 From: Huynh Khoi Nguyen Nguyen Date: Fri, 22 Jun 2012 11:03:23 +0200 Subject: [PATCH 1/4] config: read (but not write) from $XDG_CONFIG_HOME/git/config file Teach git to read the "gitconfig" information from a new location, $XDG_CONFIG_HOME/git/config; this allows the user to avoid cluttering $HOME with many per-application configuration files. In the order of reading, this file comes between the global configuration file (typically $HOME/.gitconfig) and the system wide configuration file (typically /etc/gitconfig). We do not write to this new location (yet). If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/config will be used. This is in line with XDG specification. If the new file does not exist, the behavior is unchanged. Signed-off-by: Huynh Khoi Nguyen Nguyen Signed-off-by: Valentin Duperray Signed-off-by: Franck Jonas Signed-off-by: Lucien Kong Signed-off-by: Thomas Nguy Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Documentation/git-config.txt | 14 ++++++-- builtin/config.c | 28 ++++++++++----- cache.h | 3 ++ config.c | 23 +++++++----- path.c | 41 +++++++++++++++++++++ t/t1306-xdg-files.sh | 70 ++++++++++++++++++++++++++++++++++++ 6 files changed, 158 insertions(+), 21 deletions(-) create mode 100755 t/t1306-xdg-files.sh diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index d9463cb387..09675cba4a 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -99,8 +99,8 @@ OPTIONS For writing options: write to global ~/.gitconfig file rather than the repository .git/config. + -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 <>. @@ -194,7 +194,7 @@ See also <>. 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 +204,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/builtin/config.c b/builtin/config.c index 33c8820af6..da54fd1e12 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,20 @@ 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) && + (actions == ACTION_LIST || + actions == ACTION_GET_COLOR || + actions == ACTION_GET_COLORBOOL)) + 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 06413e1584..fbba2d687a 100644 --- a/cache.h +++ b/cache.h @@ -619,6 +619,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))); @@ -708,6 +710,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 71ef171cab..d28a499b0b 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/path.c b/path.c index 6f2aa699ad..66acd24fa8 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 0000000000..5b971d93eb --- /dev/null +++ b/t/t1306-xdg-files.sh @@ -0,0 +1,70 @@ +#!/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_done From dc79687e0b70805894d1b4432cef7164ae86e033 Mon Sep 17 00:00:00 2001 From: Huynh Khoi Nguyen Nguyen Date: Fri, 22 Jun 2012 11:03:24 +0200 Subject: [PATCH 2/4] Let core.excludesfile default to $XDG_CONFIG_HOME/git/ignore To use the feature of core.excludesfile, the user needs: 1. to create such a file, 2. and add configuration variable to point at it. Instead, we can make this a one-step process by choosing a default value which points to a filename in the user's $HOME, that is unlikely to already exist on the system, and only use the presence of the file as a cue that the user wants to use that feature. And we use "${XDG_CONFIG_HOME:-$HOME/.config/git}/ignore" as such a file, in the same directory as the newly added configuration file ("${XDG_CONFIG_HOME:-$HOME/.config/git}/config). The use of this directory is in line with XDG specification as a location to store such application specific files. Signed-off-by: Huynh Khoi Nguyen Nguyen Signed-off-by: Valentin Duperray Signed-off-by: Franck Jonas Signed-off-by: Lucien Kong Signed-off-by: Thomas Nguy Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 +++- Documentation/gitignore.txt | 4 +++- dir.c | 7 ++++++- t/t1306-xdg-files.sh | 29 +++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 915cb5a547..20e95318f3 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -483,7 +483,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 diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 2e7328b830..c1f692a71e 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/dir.c b/dir.c index 2c02b312b7..390367fa67 100644 --- a/dir.c +++ b/dir.c @@ -1292,12 +1292,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/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh index 5b971d93eb..05103f54ad 100755 --- a/t/t1306-xdg-files.sh +++ b/t/t1306-xdg-files.sh @@ -67,4 +67,33 @@ test_expect_success 'read with --list: xdg file exists and ~/.gitconfig exists' ' +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_done From 684e40f657ae004d60dfa833567bec4a409d78f9 Mon Sep 17 00:00:00 2001 From: Huynh Khoi Nguyen Nguyen Date: Fri, 22 Jun 2012 11:03:25 +0200 Subject: [PATCH 3/4] Let core.attributesfile default to $XDG_CONFIG_HOME/git/attributes This gives the default value for the core.attributesfile variable following the exact same logic of the previous change for the core.excludesfile setting. Signed-off-by: Huynh Khoi Nguyen Nguyen Signed-off-by: Valentin Duperray Signed-off-by: Franck Jonas Signed-off-by: Lucien Kong Signed-off-by: Thomas Nguy Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 +++- Documentation/gitattributes.txt | 2 ++ attr.c | 17 ++++++++++------- t/t1306-xdg-files.sh | 29 +++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 20e95318f3..db07459bbb 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -500,7 +500,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/gitattributes.txt b/Documentation/gitattributes.txt index 80120ea14f..e16f3e175b 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/attr.c b/attr.c index 303751f6c2..aef93d896f 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/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh index 05103f54ad..e8cd78a48d 100755 --- a/t/t1306-xdg-files.sh +++ b/t/t1306-xdg-files.sh @@ -96,4 +96,33 @@ test_expect_success 'Exclusion in a non-XDG global ignore file' ' ' +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_done From 0e8593dc5b812df00400347e88f5707225fe831e Mon Sep 17 00:00:00 2001 From: Huynh Khoi Nguyen Nguyen Date: Fri, 22 Jun 2012 11:03:26 +0200 Subject: [PATCH 4/4] config: write to $XDG_CONFIG_HOME/git/config file when appropriate Teach git to write to $XDG_CONFIG_HOME/git/config if - it already exists, - $HOME/.gitconfig file doesn't, and - The --global option is used. Otherwise, write to $HOME/.gitconfig when the --global option is given, as before. If the user doesn't create $XDG_CONFIG_HOME/git/config, there is absolutely no change. Users can use this new file only if they want. If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/config will be used. Advice for users who often come back to an old version of Git: you shouldn't create this file. Signed-off-by: Huynh Khoi Nguyen Nguyen Signed-off-by: Valentin Duperray Signed-off-by: Franck Jonas Signed-off-by: Lucien Kong Signed-off-by: Thomas Nguy Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Documentation/git-config.txt | 3 ++- builtin/config.c | 5 +---- t/t1306-xdg-files.sh | 30 ++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 09675cba4a..2d6ef32a08 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -97,7 +97,8 @@ 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 and from $XDG_CONFIG_HOME/git/config rather than from all available files. diff --git a/builtin/config.c b/builtin/config.c index da54fd1e12..e8e1c0a456 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -387,10 +387,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) home_config_paths(&user_config, &xdg_config, "config"); - if (access(user_config, R_OK) && !access(xdg_config, R_OK) && - (actions == ACTION_LIST || - actions == ACTION_GET_COLOR || - actions == ACTION_GET_COLORBOOL)) + 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; diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh index e8cd78a48d..3c75c3f2e7 100755 --- a/t/t1306-xdg-files.sh +++ b/t/t1306-xdg-files.sh @@ -125,4 +125,34 @@ test_expect_success 'Checking attributes in a non-XDG global attributes file' ' ' +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