summaryrefslogtreecommitdiff
path: root/src/win32
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2022-01-17 17:16:02 +0000
committerEdward Thomson <ethomson@edwardthomson.com>2022-01-17 22:02:12 -0500
commit475c6eba4f5ad806b68ebf399818a7e9e9ec4a18 (patch)
tree81f894596bdf8f71bced4fa060043653066b7bce /src/win32
parent925abee95b55ab24b0cf92771c713569f1f128f4 (diff)
downloadlibgit2-475c6eba4f5ad806b68ebf399818a7e9e9ec4a18.tar.gz
win32: improve impl & tests for system path / g4w interopethomson/win32_findfile_fixes
We look for a Git for Windows installation to use its git config, so that clients built on libgit2 can interoperate with the Git for Windows CLI (and clients that are built on top of _it_). Look for `git` both in the `PATH` and in the registry. Use the _first_ git install in the path, and the first git install in the registry. Look in both the `etc` dir and the architecture-specific `etc` dirs (`mingw64/etc` and `mingw32/etc`) beneath the installation root. Prefer the git in the `PATH` to the git location in the registry so that users can override that. Include more tests for this behavior.
Diffstat (limited to 'src/win32')
-rw-r--r--src/win32/findfile.c316
-rw-r--r--src/win32/findfile.h6
2 files changed, 152 insertions, 170 deletions
diff --git a/src/win32/findfile.c b/src/win32/findfile.c
index b82347fc6..516391d7f 100644
--- a/src/win32/findfile.c
+++ b/src/win32/findfile.c
@@ -11,13 +11,8 @@
#include "utf-conv.h"
#include "fs_path.h"
-#define REG_MSYSGIT_INSTALL_LOCAL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
-
-#ifndef _WIN64
-#define REG_MSYSGIT_INSTALL REG_MSYSGIT_INSTALL_LOCAL
-#else
-#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
-#endif
+#define REG_GITFORWINDOWS_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
+#define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
static int git_win32__expand_path(git_win32_path dest, const wchar_t *src)
{
@@ -44,164 +39,130 @@ static int win32_path_to_8(git_str *dest, const wchar_t *src)
return git_str_sets(dest, utf8_path);
}
-static wchar_t *win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen)
-{
- wchar_t term, *base = path;
-
- GIT_ASSERT_ARG_WITH_RETVAL(path, NULL);
- GIT_ASSERT_ARG_WITH_RETVAL(buf, NULL);
- GIT_ASSERT_ARG_WITH_RETVAL(buflen, NULL);
-
- term = (*path == L'"') ? *path++ : L';';
-
- for (buflen--; *path && *path != term && buflen; buflen--)
- *buf++ = *path++;
-
- *buf = L'\0'; /* reserved a byte via initial subtract */
+static git_win32_path mock_registry;
+static bool mock_registry_set;
- while (*path == term || *path == L';')
- path++;
-
- return (path != base) ? path : NULL;
-}
-
-static int win32_find_git_for_windows_architecture_root(git_win32_path root_path, const wchar_t *subdir)
+extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir)
{
- /* Git for Windows >= 2 comes with a special architecture root (mingw64 and mingw32)
- * under which the "share" folder is located, check which we need (none is also ok) */
-
- static const wchar_t *architecture_roots[4] = {
- L"", // starting with Git 2.24 the etc folder is directly in the root folder
- L"mingw64\\",
- L"mingw32\\",
- NULL,
- };
-
- const wchar_t **roots = architecture_roots;
- size_t root_path_len = wcslen(root_path);
-
- for (; *roots != NULL; ++roots) {
- git_win32_path tmp_root;
- DWORD subdir_len;
- if (wcscpy(tmp_root, root_path) &&
- root_path_len + wcslen(*roots) <= MAX_PATH &&
- wcscat(tmp_root, *roots) &&
- !_waccess(tmp_root, F_OK)) {
- wcscpy(root_path, tmp_root);
- root_path_len += (DWORD)wcslen(*roots);
-
- subdir_len = (DWORD)wcslen(subdir);
- if (root_path_len + subdir_len >= MAX_PATH)
- break;
-
- // append subdir and check whether it exists for the Git installation
- wcscat(tmp_root, subdir);
- if (!_waccess(tmp_root, F_OK)) {
- wcscpy(root_path, tmp_root);
- root_path_len += subdir_len;
- break;
- }
+ if (!mock_sysdir) {
+ mock_registry[0] = L'\0';
+ mock_registry_set = false;
+ } else {
+ size_t len = wcslen(mock_sysdir);
+
+ if (len > GIT_WIN_PATH_MAX) {
+ git_error_set(GIT_ERROR_INVALID, "mock path too long");
+ return -1;
}
+
+ wcscpy(mock_registry, mock_sysdir);
+ mock_registry_set = true;
}
return 0;
}
-static int win32_find_git_in_path(git_str *buf, const wchar_t *gitexe, const wchar_t *subdir)
+static int lookup_registry_key(
+ git_win32_path out,
+ const HKEY hive,
+ const wchar_t* key,
+ const wchar_t *value)
{
- wchar_t *path, *env, lastch;
- git_win32_path root;
- size_t gitexe_len = wcslen(gitexe);
- DWORD len;
- bool found = false;
-
- len = GetEnvironmentVariableW(L"PATH", NULL, 0);
-
- if (len < 0)
- return -1;
-
- path = git__malloc(len * sizeof(wchar_t));
- GIT_ERROR_CHECK_ALLOC(path);
+ HKEY hkey;
+ DWORD type, size;
+ int error = GIT_ENOTFOUND;
- len = GetEnvironmentVariableW(L"PATH", path, len);
+ /*
+ * Registry data may not be NUL terminated, provide room to do
+ * it ourselves.
+ */
+ size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t));
+
+ if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0)
+ return GIT_ENOTFOUND;
+
+ if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 &&
+ type == REG_SZ &&
+ size > 0 &&
+ size < sizeof(git_win32_path)) {
+ size_t wsize = size / sizeof(wchar_t);
+ size_t len = wsize - 1;
+
+ if (out[wsize - 1] != L'\0') {
+ len = wsize;
+ out[wsize] = L'\0';
+ }
- if (len < 0)
- return -1;
+ if (out[len - 1] == L'\\')
+ out[len - 1] = L'\0';
- env = path;
+ if (_waccess(out, F_OK) == 0)
+ error = 0;
+ }
- while ((env = win32_walkpath(env, root, MAX_PATH-1)) && *root) {
- size_t root_len = wcslen(root);
- lastch = root[root_len - 1];
+ RegCloseKey(hkey);
+ return error;
+}
- /* ensure trailing slash (MAX_PATH-1 to walkpath guarantees space) */
- if (lastch != L'/' && lastch != L'\\') {
- root[root_len++] = L'\\';
- root[root_len] = L'\0';
- }
+static int find_sysdir_in_registry(git_win32_path out)
+{
+ if (mock_registry_set) {
+ if (mock_registry[0] == L'\0')
+ return GIT_ENOTFOUND;
- if (root_len + gitexe_len >= MAX_PATH)
- continue;
-
- if (!_waccess(root, F_OK)) {
- /* check whether we found a Git for Windows installation and do some path adjustments OR just append subdir */
- if ((root_len > 5 && wcscmp(root - 4, L"cmd\\")) || wcscmp(root - 4, L"bin\\")) {
- /* strip "bin" or "cmd" and try to find architecture root for appending subdir */
- root_len -= 4;
- root[root_len] = L'\0';
- if (win32_find_git_for_windows_architecture_root(root, subdir))
- continue;
- } else {
- if (root_len + wcslen(subdir) >= MAX_PATH)
- continue;
- wcscat(root, subdir);
- }
-
- win32_path_to_8(buf, root);
- found = true;
- break;
- }
+ wcscpy(out, mock_registry);
+ return 0;
}
- git__free(path);
- return found ? 0 : GIT_ENOTFOUND;
+ if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 ||
+ lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 ||
+ lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 ||
+ lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0)
+ return 0;
+
+ return GIT_ENOTFOUND;
}
-static int win32_find_git_in_registry(
- git_str *buf, const HKEY hive, const wchar_t *key, const wchar_t *subdir)
+static int find_sysdir_in_path(git_win32_path out)
{
- HKEY hKey;
- int error = GIT_ENOTFOUND;
+ size_t out_len;
- GIT_ASSERT_ARG(buf);
+ if (git_win32_path_find_executable(out, L"git.exe") < 0 &&
+ git_win32_path_find_executable(out, L"git.cmd") < 0)
+ return GIT_ENOTFOUND;
- if (!RegOpenKeyExW(hive, key, 0, KEY_READ, &hKey)) {
- DWORD dwType, cbData;
- git_win32_path path;
+ out_len = wcslen(out);
- /* Ensure that the buffer is big enough to have the suffix attached
- * after we receive the result. */
- cbData = (DWORD)(sizeof(path) - wcslen(subdir) * sizeof(wchar_t));
+ /* Trim the file name */
+ if (out_len <= CONST_STRLEN(L"git.exe"))
+ return GIT_ENOTFOUND;
- /* InstallLocation points to the root of the git directory */
- if (!RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, (LPBYTE)path, &cbData) &&
- dwType == REG_SZ) {
+ out_len -= CONST_STRLEN(L"git.exe");
- /* Convert to UTF-8, with forward slashes, and output the path
- * to the provided buffer */
- if (!win32_find_git_for_windows_architecture_root(path, subdir) &&
- !win32_path_to_8(buf, path))
- error = 0;
- }
+ if (out_len && out[out_len - 1] == L'\\')
+ out_len--;
- RegCloseKey(hKey);
- }
+ /*
+ * Git for Windows usually places the command in a 'bin' or
+ * 'cmd' directory, trim that.
+ */
+ if (out_len >= CONST_STRLEN(L"\\bin") &&
+ wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0)
+ out_len -= CONST_STRLEN(L"\\bin");
+ else if (out_len >= CONST_STRLEN(L"\\cmd") &&
+ wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0)
+ out_len -= CONST_STRLEN(L"\\cmd");
- return error;
+ if (!out_len)
+ return GIT_ENOTFOUND;
+
+ out[out_len] = L'\0';
+ return 0;
}
static int win32_find_existing_dirs(
- git_str *out, const wchar_t *tmpl[])
+ git_str* out,
+ const wchar_t* tmpl[])
{
git_win32_path path16;
git_str buf = GIT_STR_INIT;
@@ -210,9 +171,8 @@ static int win32_find_existing_dirs(
for (; *tmpl != NULL; tmpl++) {
if (!git_win32__expand_path(path16, *tmpl) &&
- path16[0] != L'%' &&
- !_waccess(path16, F_OK))
- {
+ path16[0] != L'%' &&
+ !_waccess(path16, F_OK)) {
win32_path_to_8(&buf, path16);
if (buf.size)
@@ -225,47 +185,67 @@ static int win32_find_existing_dirs(
return (git_str_oom(out) ? -1 : 0);
}
-int git_win32__find_system_dir_in_path(git_str *out, const wchar_t *subdir)
-{
- /* directories where git.exe & git.cmd are found */
- if (win32_find_git_in_path(out, L"git.exe", subdir) == 0)
- return 0;
-
- return win32_find_git_in_path(out, L"git.cmd", subdir);
-}
-
-static int git_win32__find_system_dir_in_registry(git_str *out, const wchar_t *subdir)
+static int append_subdir(git_str *out, git_str *path, const char *subdir)
{
- git_str buf = GIT_STR_INIT;
-
- /* directories where git is installed according to registry */
- if (!win32_find_git_in_registry(
- &buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL, subdir) && buf.size)
- git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+ static const char* architecture_roots[] = {
+ "",
+ "mingw64",
+ "mingw32",
+ NULL
+ };
+ const char **root;
+ size_t orig_path_len = path->size;
-#ifdef GIT_ARCH_64
- if (!win32_find_git_in_registry(
- &buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL_LOCAL, subdir) && buf.size)
- git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
-#endif
+ for (root = architecture_roots; *root; root++) {
+ if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) ||
+ git_str_joinpath(path, path->ptr, subdir) < 0)
+ return -1;
- if (!win32_find_git_in_registry(
- &buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL, subdir) && buf.size)
- git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+ if (git_fs_path_exists(path->ptr) &&
+ git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0)
+ return -1;
- git_str_dispose(&buf);
+ git_str_truncate(path, orig_path_len);
+ }
- return (git_str_oom(out) ? -1 : 0);
+ return 0;
}
-int git_win32__find_system_dirs(git_str *out, const wchar_t *subdir)
+int git_win32__find_system_dirs(git_str *out, const char *subdir)
{
+ git_win32_path pathdir, regdir;
+ git_str path8 = GIT_STR_INIT;
+ bool has_pathdir, has_regdir;
int error;
- if ((error = git_win32__find_system_dir_in_path(out, subdir)) == 0)
- error = git_win32__find_system_dir_in_registry(out, subdir);
+ has_pathdir = (find_sysdir_in_path(pathdir) == 0);
+ has_regdir = (find_sysdir_in_registry(regdir) == 0);
- return error;
+ if (!has_pathdir && !has_regdir)
+ return GIT_ENOTFOUND;
+
+ /*
+ * Usually the git in the path is the same git in the registry,
+ * in this case there's no need to duplicate the paths.
+ */
+ if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0)
+ has_regdir = false;
+
+ if (has_pathdir) {
+ if ((error = win32_path_to_8(&path8, pathdir)) < 0 ||
+ (error = append_subdir(out, &path8, subdir)) < 0)
+ goto done;
+ }
+
+ if (has_regdir) {
+ if ((error = win32_path_to_8(&path8, regdir)) < 0 ||
+ (error = append_subdir(out, &path8, subdir)) < 0)
+ goto done;
+ }
+
+done:
+ git_str_dispose(&path8);
+ return error;
}
int git_win32__find_global_dirs(git_str *out)
diff --git a/src/win32/findfile.h b/src/win32/findfile.h
index 7f7691ce1..61fb7dbad 100644
--- a/src/win32/findfile.h
+++ b/src/win32/findfile.h
@@ -10,8 +10,10 @@
#include "common.h"
-extern int git_win32__find_system_dir_in_path(git_str* out, const wchar_t* subdir);
-extern int git_win32__find_system_dirs(git_str *out, const wchar_t *subpath);
+/** Sets the mock registry root for Git for Windows for testing. */
+extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir);
+
+extern int git_win32__find_system_dirs(git_str *out, const char *subpath);
extern int git_win32__find_global_dirs(git_str *out);
extern int git_win32__find_xdg_dirs(git_str *out);
extern int git_win32__find_programdata_dirs(git_str *out);