Skip to content

Commit

Permalink
Merge branch 'pt/xdg-config-path' into maint
Browse files Browse the repository at this point in the history
Code clean-up for xdg configuration path support.

* pt/xdg-config-path:
  path.c: remove home_config_paths()
  git-config: replace use of home_config_paths()
  git-commit: replace use of home_config_paths()
  credential-store.c: replace home_config_paths() with xdg_config_home()
  dir.c: replace home_config_paths() with xdg_config_home()
  attr.c: replace home_config_paths() with xdg_config_home()
  path.c: implement xdg_config_home()
  t0302: "unreadable" test needs POSIXPERM
  t0302: test credential-store support for XDG_CONFIG_HOME
  git-credential-store: support XDG_CONFIG_HOME
  git-credential-store: support multiple credential files
  • Loading branch information
Junio C Hamano committed Jun 5, 2015
2 parents 9eabf5b + 846e5df commit d9c82fa
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 81 deletions.
35 changes: 33 additions & 2 deletions Documentation/git-credential-store.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,41 @@ OPTIONS

--file=<path>::

Use `<path>` to store credentials. The file will have its
Use `<path>` to lookup and store credentials. The file will have its
filesystem permissions set to prevent other users on the system
from reading it, but will not be encrypted or otherwise
protected. Defaults to `~/.git-credentials`.
protected. If not specified, credentials will be searched for from
`~/.git-credentials` and `$XDG_CONFIG_HOME/git/credentials`, and
credentials will be written to `~/.git-credentials` if it exists, or
`$XDG_CONFIG_HOME/git/credentials` if it exists and the former does
not. See also <<FILES>>.

[[FILES]]
FILES
-----

If not set explicitly with '--file', there are two files where
git-credential-store will search for credentials in order of precedence:

~/.git-credentials::
User-specific credentials file.

$XDG_CONFIG_HOME/git/credentials::
Second user-specific credentials file. If '$XDG_CONFIG_HOME' is not set
or empty, `$HOME/.config/git/credentials` will be used. Any credentials
stored in this file will not be used if `~/.git-credentials` has a
matching credential as well. It is a good idea not to create this file
if you sometimes use older versions of Git that do not support it.

For credential lookups, the files are read in the order given above, with the
first matching credential found taking precedence over credentials found in
files further down the list.

Credential storage will by default write to the first existing file in the
list. If none of these files exist, `~/.git-credentials` will be created and
written to.

When erasing credentials, matching credentials will be erased from all files.

EXAMPLES
--------
Expand Down
7 changes: 2 additions & 5 deletions attr.c
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,6 @@ static int git_attr_system(void)
static void bootstrap_attr_stack(void)
{
struct attr_stack *elem;
char *xdg_attributes_file;

if (attr_stack)
return;
Expand All @@ -512,10 +511,8 @@ static void bootstrap_attr_stack(void)
}
}

if (!git_attributes_file) {
home_config_paths(NULL, &xdg_attributes_file, "attributes");
git_attributes_file = xdg_attributes_file;
}
if (!git_attributes_file)
git_attributes_file = xdg_config_home("attributes");
if (git_attributes_file) {
elem = read_attr_from_file(git_attributes_file, 1);
if (elem) {
Expand Down
8 changes: 3 additions & 5 deletions builtin/commit.c
Original file line number Diff line number Diff line change
Expand Up @@ -1398,12 +1398,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)

static const char *implicit_ident_advice(void)
{
char *user_config = NULL;
char *xdg_config = NULL;
int config_exists;
char *user_config = expand_user_path("~/.gitconfig");
char *xdg_config = xdg_config_home("config");
int config_exists = file_exists(user_config) || file_exists(xdg_config);

home_config_paths(&user_config, &xdg_config, "config");
config_exists = file_exists(user_config) || file_exists(xdg_config);
free(user_config);
free(xdg_config);

Expand Down
6 changes: 2 additions & 4 deletions builtin/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -488,10 +488,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}

if (use_global_config) {
char *user_config = NULL;
char *xdg_config = NULL;

home_config_paths(&user_config, &xdg_config, "config");
char *user_config = expand_user_path("~/.gitconfig");
char *xdg_config = xdg_config_home("config");

if (!user_config)
/*
Expand Down
8 changes: 7 additions & 1 deletion cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,6 @@ enum scld_error safe_create_leading_directories(char *path);
enum scld_error 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)
Expand All @@ -836,6 +835,13 @@ char *strip_path_suffix(const char *path, const char *suffix);
int daemon_avoid_alias(const char *path);
extern int is_ntfs_dotgit(const char *name);

/**
* Return a newly allocated string with the evaluation of
* "$XDG_CONFIG_HOME/git/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
* "$HOME/.config/git/$filename". Return NULL upon error.
*/
extern char *xdg_config_home(const char *filename);

/* object replacement */
#define LOOKUP_REPLACE_OBJECT 1
extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
Expand Down
6 changes: 2 additions & 4 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1185,10 +1185,8 @@ int git_config_system(void)
int git_config_early(config_fn_t fn, void *data, const char *repo_config)
{
int ret = 0, found = 0;
char *xdg_config = NULL;
char *user_config = NULL;

home_config_paths(&user_config, &xdg_config, "config");
char *xdg_config = xdg_config_home("config");
char *user_config = expand_user_path("~/.gitconfig");

if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
Expand Down
90 changes: 63 additions & 27 deletions credential-store.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,28 @@

static struct lock_file credential_lock;

static void parse_credential_file(const char *fn,
static int parse_credential_file(const char *fn,
struct credential *c,
void (*match_cb)(struct credential *),
void (*other_cb)(struct strbuf *))
{
FILE *fh;
struct strbuf line = STRBUF_INIT;
struct credential entry = CREDENTIAL_INIT;
int found_credential = 0;

fh = fopen(fn, "r");
if (!fh) {
if (errno != ENOENT)
if (errno != ENOENT && errno != EACCES)
die_errno("unable to open %s", fn);
return;
return found_credential;
}

while (strbuf_getline(&line, fh, '\n') != EOF) {
credential_from_url(&entry, line.buf);
if (entry.username && entry.password &&
credential_match(c, &entry)) {
found_credential = 1;
if (match_cb) {
match_cb(&entry);
break;
Expand All @@ -38,6 +40,7 @@ static void parse_credential_file(const char *fn,
credential_clear(&entry);
strbuf_release(&line);
fclose(fh);
return found_credential;
}

static void print_entry(struct credential *c)
Expand All @@ -64,21 +67,10 @@ static void rewrite_credential_file(const char *fn, struct credential *c,
die_errno("unable to commit credential store");
}

static void store_credential(const char *fn, struct credential *c)
static void store_credential_file(const char *fn, struct credential *c)
{
struct strbuf buf = STRBUF_INIT;

/*
* Sanity check that what we are storing is actually sensible.
* In particular, we can't make a URL without a protocol field.
* Without either a host or pathname (depending on the scheme),
* we have no primary key. And without a username and password,
* we are not actually storing a credential.
*/
if (!c->protocol || !(c->host || c->path) ||
!c->username || !c->password)
return;

strbuf_addf(&buf, "%s://", c->protocol);
strbuf_addstr_urlencode(&buf, c->username, 1);
strbuf_addch(&buf, ':');
Expand All @@ -95,8 +87,37 @@ static void store_credential(const char *fn, struct credential *c)
strbuf_release(&buf);
}

static void remove_credential(const char *fn, struct credential *c)
static void store_credential(const struct string_list *fns, struct credential *c)
{
struct string_list_item *fn;

/*
* Sanity check that what we are storing is actually sensible.
* In particular, we can't make a URL without a protocol field.
* Without either a host or pathname (depending on the scheme),
* we have no primary key. And without a username and password,
* we are not actually storing a credential.
*/
if (!c->protocol || !(c->host || c->path) || !c->username || !c->password)
return;

for_each_string_list_item(fn, fns)
if (!access(fn->string, F_OK)) {
store_credential_file(fn->string, c);
return;
}
/*
* Write credential to the filename specified by fns->items[0], thus
* creating it
*/
if (fns->nr)
store_credential_file(fns->items[0].string, c);
}

static void remove_credential(const struct string_list *fns, struct credential *c)
{
struct string_list_item *fn;

/*
* Sanity check that we actually have something to match
* against. The input we get is a restrictive pattern,
Expand All @@ -105,14 +126,20 @@ static void remove_credential(const char *fn, struct credential *c)
* to empty input. So explicitly disallow it, and require that the
* pattern have some actual content to match.
*/
if (c->protocol || c->host || c->path || c->username)
rewrite_credential_file(fn, c, NULL);
if (!c->protocol && !c->host && !c->path && !c->username)
return;
for_each_string_list_item(fn, fns)
if (!access(fn->string, F_OK))
rewrite_credential_file(fn->string, c, NULL);
}

static int lookup_credential(const char *fn, struct credential *c)
static void lookup_credential(const struct string_list *fns, struct credential *c)
{
parse_credential_file(fn, c, print_entry, NULL);
return c->username && c->password;
struct string_list_item *fn;

for_each_string_list_item(fn, fns)
if (parse_credential_file(fn->string, c, print_entry, NULL))
return; /* Found credential */
}

int main(int argc, char **argv)
Expand All @@ -123,6 +150,7 @@ int main(int argc, char **argv)
};
const char *op;
struct credential c = CREDENTIAL_INIT;
struct string_list fns = STRING_LIST_INIT_DUP;
char *file = NULL;
struct option options[] = {
OPT_STRING(0, "file", &file, "path",
Expand All @@ -137,22 +165,30 @@ int main(int argc, char **argv)
usage_with_options(usage, options);
op = argv[0];

if (!file)
file = expand_user_path("~/.git-credentials");
if (!file)
if (file) {
string_list_append(&fns, file);
} else {
if ((file = expand_user_path("~/.git-credentials")))
string_list_append_nodup(&fns, file);
file = xdg_config_home("credentials");
if (file)
string_list_append_nodup(&fns, file);
}
if (!fns.nr)
die("unable to set up default path; use --file");

if (credential_read(&c, stdin) < 0)
die("unable to read credential");

if (!strcmp(op, "get"))
lookup_credential(file, &c);
lookup_credential(&fns, &c);
else if (!strcmp(op, "erase"))
remove_credential(file, &c);
remove_credential(&fns, &c);
else if (!strcmp(op, "store"))
store_credential(file, &c);
store_credential(&fns, &c);
else
; /* Ignore unknown operation. */

string_list_clear(&fns, 0);
return 0;
}
7 changes: 2 additions & 5 deletions dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -1671,14 +1671,11 @@ 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 (!excludes_file)
excludes_file = xdg_config_home("ignore");
if (!access_or_warn(path, R_OK, 0))
add_excludes_from_file(dir, path);
if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
Expand Down
43 changes: 15 additions & 28 deletions path.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,34 +130,6 @@ char *git_path(const char *fmt, ...)
return ret;
}

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) {
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();
Expand Down Expand Up @@ -851,3 +823,18 @@ int is_ntfs_dotgit(const char *name)
len = -1;
}
}

char *xdg_config_home(const char *filename)
{
const char *home, *config_home;

assert(filename);
config_home = getenv("XDG_CONFIG_HOME");
if (config_home && *config_home)
return mkpathdup("%s/git/%s", config_home, filename);

home = getenv("HOME");
if (home)
return mkpathdup("%s/.config/git/%s", home, filename);
return NULL;
}
Loading

0 comments on commit d9c82fa

Please sign in to comment.