diff --git a/include/git2/config.h b/include/git2/config.h index 21d8a0b05..a7d897443 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -61,12 +61,32 @@ typedef struct { * may be used on any `git_config` call to load the * global configuration file. * + * This method will not guess the path to the xdg compatible + * config file (.config/git/config). + * * @param global_config_path Buffer of GIT_PATH_MAX length to store the path * @return 0 if a global configuration file has been * found. Its path will be stored in `buffer`. */ GIT_EXTERN(int) git_config_find_global(char *global_config_path, size_t length); +/** + * Locate the path to the global xdg compatible configuration file + * + * The xdg compatible configuration file is usually + * located in `$HOME/.config/git/config`. + * + * This method will try to guess the full path to that + * file, if the file exists. The returned path + * may be used on any `git_config` call to load the + * xdg compatible configuration file. + * + * @param xdg_config_path Buffer of GIT_PATH_MAX length to store the path + * @return 0 if a xdg compatible configuration file has been + * found. Its path will be stored in `buffer`. + */ +GIT_EXTERN(int) git_config_find_xdg(char *xdg_config_path, size_t length); + /** * Locate the path to the system configuration file * diff --git a/src/config.c b/src/config.c index e62dccf51..b89c16b1c 100644 --- a/src/config.c +++ b/src/config.c @@ -451,8 +451,12 @@ int git_config_find_global_r(git_buf *path) { int error = git_futils_find_global_file(path, GIT_CONFIG_FILENAME); - if (error == GIT_ENOTFOUND) - error = git_futils_find_global_file(path, GIT_CONFIG_FILENAME_ALT); + return error; +} + +int git_config_find_xdg_r(git_buf *path) +{ + int error = git_futils_find_global_file(path, GIT_CONFIG_FILENAME_ALT); return error; } @@ -479,6 +483,28 @@ int git_config_find_global(char *global_config_path, size_t length) return 0; } +int git_config_find_xdg(char *xdg_config_path, size_t length) +{ + git_buf path = GIT_BUF_INIT; + int ret = git_config_find_xdg_r(&path); + + if (ret < 0) { + git_buf_free(&path); + return ret; + } + + if (path.size >= length) { + git_buf_free(&path); + giterr_set(GITERR_NOMEMORY, + "Path is to long to fit on the given buffer"); + return -1; + } + + git_buf_copy_cstr(xdg_config_path, length, &path); + git_buf_free(&path); + return 0; +} + int git_config_find_system_r(git_buf *path) { return git_futils_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM); @@ -515,6 +541,9 @@ int git_config_open_default(git_config **out) error = git_config_new(&cfg); if (!error && !git_config_find_global_r(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, 3); + + if (!error && !git_config_find_xdg_r(&buf)) error = git_config_add_file_ondisk(cfg, buf.ptr, 2); if (!error && !git_config_find_system_r(&buf)) diff --git a/src/config.h b/src/config.h index 5475ef384..471b42dad 100644 --- a/src/config.h +++ b/src/config.h @@ -24,6 +24,7 @@ struct git_config { }; extern int git_config_find_global_r(git_buf *global_config_path); +extern int git_config_find_xdg_r(git_buf *system_config_path); extern int git_config_find_system_r(git_buf *system_config_path); extern int git_config_parse_bool(int *out, const char *bool_string); diff --git a/src/fileops.c b/src/fileops.c index 8ccf063d5..763537a46 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -7,6 +7,9 @@ #include "common.h" #include "fileops.h" #include +#if GIT_WIN32 +#include "win32/findfile.h" +#endif int git_futils_mkpath2file(const char *file_path, const mode_t mode) { @@ -371,98 +374,59 @@ int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type return error; } -#ifdef GIT_WIN32 -struct win32_path { - wchar_t path[MAX_PATH]; - DWORD len; -}; - -static int win32_expand_path(struct win32_path *s_root, const wchar_t *templ) -{ - s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH); - return s_root->len ? 0 : -1; -} - -static int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename) -{ - size_t len, alloc_len; - wchar_t *file_utf16 = NULL; - char file_utf8[GIT_PATH_MAX]; - - if (!root || !filename || (len = strlen(filename)) == 0) - return GIT_ENOTFOUND; - - /* allocate space for wchar_t path to file */ - alloc_len = root->len + len + 2; - file_utf16 = git__calloc(alloc_len, sizeof(wchar_t)); - GITERR_CHECK_ALLOC(file_utf16); - - /* append root + '\\' + filename as wchar_t */ - memcpy(file_utf16, root->path, root->len * sizeof(wchar_t)); - - if (*filename == '/' || *filename == '\\') - filename++; - - git__utf8_to_16(file_utf16 + root->len - 1, alloc_len, filename); - - /* check access */ - if (_waccess(file_utf16, F_OK) < 0) { - git__free(file_utf16); - return GIT_ENOTFOUND; - } - - git__utf16_to_8(file_utf8, file_utf16); - git_path_mkposix(file_utf8); - git_buf_sets(path, file_utf8); - - git__free(file_utf16); - return 0; -} -#endif - int git_futils_find_system_file(git_buf *path, const char *filename) { #ifdef GIT_WIN32 - struct win32_path root; - - if (win32_expand_path(&root, L"%PROGRAMFILES%\\Git\\etc\\") < 0 || - root.path[0] == L'%') /* i.e. no expansion happened */ - { - giterr_set(GITERR_OS, "Cannot locate the system's Program Files directory"); - return -1; - } - - if (win32_find_file(path, &root, filename) < 0) { - giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename); - git_buf_clear(path); - return GIT_ENOTFOUND; - } - - return 0; + // try to find git.exe/git.cmd on path + if (!win32_find_system_file_using_path(path, filename)) + return 0; + // try to find msysgit installation path using registry + if (!win32_find_system_file_using_registry(path, filename)) + return 0; #else if (git_buf_joinpath(path, "/etc", filename) < 0) return -1; if (git_path_exists(path->ptr) == true) return 0; +#endif git_buf_clear(path); giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename); return GIT_ENOTFOUND; -#endif } int git_futils_find_global_file(git_buf *path, const char *filename) { + const char *home = getenv("HOME"); + #ifdef GIT_WIN32 struct win32_path root; - if (win32_expand_path(&root, L"%USERPROFILE%\\") < 0 || - root.path[0] == L'%') /* i.e. no expansion happened */ - { - giterr_set(GITERR_OS, "Cannot locate the user's profile directory"); - return -1; + if (home != NULL) { + if (git_buf_joinpath(path, home, filename) < 0) + return -1; + + if (git_path_exists(path->ptr)) { + return 0; + } + } + + if (getenv("HOMEPATH") != NULL) { + if (win32_expand_path(&root, L"%HOMEDRIVE%%HOMEPATH%\\") < 0 || + root.path[0] == L'%') /* i.e. no expansion happened */ + { + giterr_set(GITERR_OS, "Cannot locate the user's profile directory"); + return -1; + } + } else { + if (win32_expand_path(&root, L"%USERPROFILE%\\") < 0 || + root.path[0] == L'%') /* i.e. no expansion happened */ + { + giterr_set(GITERR_OS, "Cannot locate the user's profile directory"); + return -1; + } } if (win32_find_file(path, &root, filename) < 0) { @@ -473,8 +437,6 @@ int git_futils_find_global_file(git_buf *path, const char *filename) return 0; #else - const char *home = getenv("HOME"); - if (home == NULL) { giterr_set(GITERR_OS, "Global file lookup failed. " "Cannot locate the user's home directory"); diff --git a/src/repository.c b/src/repository.c index 1b49228fb..96370c771 100644 --- a/src/repository.c +++ b/src/repository.c @@ -446,6 +446,7 @@ static int load_config( git_config **out, git_repository *repo, const char *global_config_path, + const char *xdg_config_path, const char *system_config_path) { git_buf config_path = GIT_BUF_INIT; @@ -460,13 +461,18 @@ static int load_config( &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0) goto on_error; - if (git_config_add_file_ondisk(cfg, config_path.ptr, 3) < 0) + if (git_config_add_file_ondisk(cfg, config_path.ptr, 4) < 0) goto on_error; git_buf_free(&config_path); if (global_config_path != NULL) { - if (git_config_add_file_ondisk(cfg, global_config_path, 2) < 0) + if (git_config_add_file_ondisk(cfg, global_config_path, 3) < 0) + goto on_error; + } + + if (xdg_config_path != NULL) { + if (git_config_add_file_ondisk(cfg, xdg_config_path, 2) < 0) goto on_error; } @@ -488,19 +494,23 @@ on_error: int git_repository_config__weakptr(git_config **out, git_repository *repo) { if (repo->_config == NULL) { - git_buf global_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT; + git_buf global_buf = GIT_BUF_INIT, xdg_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT; int res; const char *global_config_path = NULL; + const char *xdg_config_path = NULL; const char *system_config_path = NULL; if (git_config_find_global_r(&global_buf) == 0) global_config_path = global_buf.ptr; + if (git_config_find_xdg_r(&xdg_buf) == 0) + xdg_config_path = xdg_buf.ptr; + if (git_config_find_system_r(&system_buf) == 0) system_config_path = system_buf.ptr; - res = load_config(&repo->_config, repo, global_config_path, system_config_path); + res = load_config(&repo->_config, repo, global_config_path, xdg_config_path, system_config_path); git_buf_free(&global_buf); git_buf_free(&system_buf); diff --git a/src/win32/findfile.c b/src/win32/findfile.c new file mode 100644 index 000000000..de9373cbb --- /dev/null +++ b/src/win32/findfile.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "utf-conv.h" +#include "path.h" +#include "findfile.h" + +#ifndef _WIN64 +#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" +#else +#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" +#endif + +int win32_expand_path(struct win32_path *s_root, const wchar_t *templ) +{ + s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH); + return s_root->len ? 0 : -1; +} + +int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename) +{ + size_t len, alloc_len; + wchar_t *file_utf16 = NULL; + char file_utf8[GIT_PATH_MAX]; + + if (!root || !filename || (len = strlen(filename)) == 0) + return GIT_ENOTFOUND; + + /* allocate space for wchar_t path to file */ + alloc_len = root->len + len + 2; + file_utf16 = git__calloc(alloc_len, sizeof(wchar_t)); + GITERR_CHECK_ALLOC(file_utf16); + + /* append root + '\\' + filename as wchar_t */ + memcpy(file_utf16, root->path, root->len * sizeof(wchar_t)); + + if (*filename == '/' || *filename == '\\') + filename++; + + git__utf8_to_16(file_utf16 + root->len - 1, alloc_len, filename); + + /* check access */ + if (_waccess(file_utf16, F_OK) < 0) { + git__free(file_utf16); + return GIT_ENOTFOUND; + } + + git__utf16_to_8(file_utf8, file_utf16); + git_path_mkposix(file_utf8); + git_buf_sets(path, file_utf8); + + git__free(file_utf16); + return 0; +} + +wchar_t* win32_nextpath(wchar_t *path, wchar_t *buf, size_t buflen) +{ + wchar_t term, *base = path; + + assert(path && buf && buflen); + + term = (*path == L'"') ? *path++ : L';'; + + for (buflen--; *path && *path != term && buflen; buflen--) + *buf++ = *path++; + + *buf = L'\0'; /* reserved a byte via initial subtract */ + + while (*path == term || *path == L';') + path++; + + return (path != base) ? path : NULL; +} + +int win32_find_system_file_using_path(git_buf *path, const char *filename) +{ + wchar_t * env = NULL; + struct win32_path root; + + env = _wgetenv(L"PATH"); + if (!env) + return -1; + + // search in all paths defined in PATH + while ((env = win32_nextpath(env, root.path, MAX_PATH - 1)) != NULL && *root.path) + { + wchar_t * pfin = root.path + wcslen(root.path) - 1; // last char of the current path entry + + // ensure trailing slash + if (*pfin != L'/' && *pfin != L'\\') + wcscpy(++pfin, L"\\"); // we have enough space left, MAX_PATH - 1 is used in nextpath above + + root.len = (DWORD)wcslen(root.path) + 1; + + if (win32_find_file(path, &root, "git.cmd") == 0 || win32_find_file(path, &root, "git.exe") == 0) { + // we found the cmd or bin directory of a git installaton + if (root.len > 5) { + wcscpy(root.path + wcslen(root.path) - 4, L"etc\\"); + if (win32_find_file(path, &root, filename) == 0) + return 0; + } + } + } + + return GIT_ENOTFOUND; +} + +int win32_find_system_file_using_registry(git_buf *path, const char *filename) +{ + struct win32_path root; + + HKEY hKey; + DWORD dwType = REG_SZ; + DWORD dwSize = MAX_PATH; + + root.len = 0; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) + { + if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType,(LPBYTE)&root.path, &dwSize) == ERROR_SUCCESS) + { + // InstallLocation points to the root of the msysgit directory + if (dwSize + 4 > MAX_PATH) // 4 = wcslen(L"etc\\") + { + giterr_set(GITERR_OS, "Cannot locate the system's msysgit directory - path too long"); + return -1; + } + wcscat(root.path, L"etc\\"); + root.len = (DWORD)wcslen(root.path) + 1; + } + } + RegCloseKey(hKey); + + if (!root.len) { + giterr_set(GITERR_OS, "Cannot locate the system's msysgit directory"); + return -1; + } + + if (win32_find_file(path, &root, filename) < 0) { + giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename); + git_buf_clear(path); + return GIT_ENOTFOUND; + } + + return 0; +} diff --git a/src/win32/findfile.h b/src/win32/findfile.h new file mode 100644 index 000000000..8751b7391 --- /dev/null +++ b/src/win32/findfile.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_git_findfile_h__ +#define INCLUDE_git_findfile_h__ + +struct win32_path { + wchar_t path[MAX_PATH]; + DWORD len; +}; + +int win32_expand_path(struct win32_path *s_root, const wchar_t *templ); + +int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename); +int win32_find_system_file_using_path(git_buf *path, const char *filename); +int win32_find_system_file_using_registry(git_buf *path, const char *filename); + +#endif +