2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
14 #define REG_MSYSGIT_INSTALL_LOCAL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
17 #define REG_MSYSGIT_INSTALL REG_MSYSGIT_INSTALL_LOCAL
19 #define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
27 static int git_win32__expand_path(_findfile_path
*dest
, const wchar_t *src
)
29 dest
->len
= ExpandEnvironmentStringsW(src
, dest
->path
, ARRAY_SIZE(dest
->path
));
31 if (!dest
->len
|| dest
->len
> ARRAY_SIZE(dest
->path
))
37 static int win32_path_to_8(git_buf
*dest
, const wchar_t *src
)
39 git_win32_utf8_path utf8_path
;
41 if (git_win32_path_to_utf8(utf8_path
, src
) < 0) {
42 git_error_set(GIT_ERROR_OS
, "unable to convert path to UTF-8");
46 /* Convert backslashes to forward slashes */
47 git_path_mkposix(utf8_path
);
49 return git_buf_sets(dest
, utf8_path
);
52 static wchar_t *win32_walkpath(wchar_t *path
, wchar_t *buf
, size_t buflen
)
54 wchar_t term
, *base
= path
;
56 GIT_ASSERT_ARG_WITH_RETVAL(path
, NULL
);
57 GIT_ASSERT_ARG_WITH_RETVAL(buf
, NULL
);
58 GIT_ASSERT_ARG_WITH_RETVAL(buflen
, NULL
);
60 term
= (*path
== L
'"') ? *path
++ : L
';';
62 for (buflen
--; *path
&& *path
!= term
&& buflen
; buflen
--)
65 *buf
= L
'\0'; /* reserved a byte via initial subtract */
67 while (*path
== term
|| *path
== L
';')
70 return (path
!= base
) ? path
: NULL
;
73 static int win32_find_git_in_path(git_buf
*buf
, const wchar_t *gitexe
, const wchar_t *subdir
)
75 wchar_t *env
= _wgetenv(L
"PATH"), lastch
;
77 size_t gitexe_len
= wcslen(gitexe
);
82 while ((env
= win32_walkpath(env
, root
.path
, MAX_PATH
-1)) && *root
.path
) {
83 root
.len
= (DWORD
)wcslen(root
.path
);
84 lastch
= root
.path
[root
.len
- 1];
86 /* ensure trailing slash (MAX_PATH-1 to walkpath guarantees space) */
87 if (lastch
!= L
'/' && lastch
!= L
'\\') {
88 root
.path
[root
.len
++] = L
'\\';
89 root
.path
[root
.len
] = L
'\0';
92 if (root
.len
+ gitexe_len
>= MAX_PATH
)
94 wcscpy(&root
.path
[root
.len
], gitexe
);
96 if (_waccess(root
.path
, F_OK
) == 0 && root
.len
> 5) {
97 /* replace "bin\\" or "cmd\\" with subdir */
98 wcscpy(&root
.path
[root
.len
- 4], subdir
);
100 win32_path_to_8(buf
, root
.path
);
105 return GIT_ENOTFOUND
;
108 static int win32_find_git_in_registry(
109 git_buf
*buf
, const HKEY hive
, const wchar_t *key
, const wchar_t *subdir
)
112 int error
= GIT_ENOTFOUND
;
116 if (!RegOpenKeyExW(hive
, key
, 0, KEY_READ
, &hKey
)) {
117 DWORD dwType
, cbData
;
120 /* Ensure that the buffer is big enough to have the suffix attached
121 * after we receive the result. */
122 cbData
= (DWORD
)(sizeof(path
) - wcslen(subdir
) * sizeof(wchar_t));
124 /* InstallLocation points to the root of the git directory */
125 if (!RegQueryValueExW(hKey
, L
"InstallLocation", NULL
, &dwType
, (LPBYTE
)path
, &cbData
) &&
128 /* Append the suffix */
129 wcscat(path
, subdir
);
131 /* Convert to UTF-8, with forward slashes, and output the path
132 * to the provided buffer */
133 if (!win32_path_to_8(buf
, path
))
143 static int win32_find_existing_dirs(
144 git_buf
*out
, const wchar_t *tmpl
[])
146 _findfile_path path16
;
147 git_buf buf
= GIT_BUF_INIT
;
151 for (; *tmpl
!= NULL
; tmpl
++) {
152 if (!git_win32__expand_path(&path16
, *tmpl
) &&
153 path16
.path
[0] != L
'%' &&
154 !_waccess(path16
.path
, F_OK
))
156 win32_path_to_8(&buf
, path16
.path
);
159 git_buf_join(out
, GIT_PATH_LIST_SEPARATOR
, out
->ptr
, buf
.ptr
);
163 git_buf_dispose(&buf
);
165 return (git_buf_oom(out
) ? -1 : 0);
168 int git_win32__find_system_dirs(git_buf
*out
, const wchar_t *subdir
)
170 git_buf buf
= GIT_BUF_INIT
;
172 /* directories where git.exe & git.cmd are found */
173 if (!win32_find_git_in_path(&buf
, L
"git.exe", subdir
) && buf
.size
)
174 git_buf_set(out
, buf
.ptr
, buf
.size
);
178 if (!win32_find_git_in_path(&buf
, L
"git.cmd", subdir
) && buf
.size
)
179 git_buf_join(out
, GIT_PATH_LIST_SEPARATOR
, out
->ptr
, buf
.ptr
);
181 /* directories where git is installed according to registry */
182 if (!win32_find_git_in_registry(
183 &buf
, HKEY_CURRENT_USER
, REG_MSYSGIT_INSTALL_LOCAL
, subdir
) && buf
.size
)
184 git_buf_join(out
, GIT_PATH_LIST_SEPARATOR
, out
->ptr
, buf
.ptr
);
186 if (!win32_find_git_in_registry(
187 &buf
, HKEY_LOCAL_MACHINE
, REG_MSYSGIT_INSTALL
, subdir
) && buf
.size
)
188 git_buf_join(out
, GIT_PATH_LIST_SEPARATOR
, out
->ptr
, buf
.ptr
);
190 git_buf_dispose(&buf
);
192 return (git_buf_oom(out
) ? -1 : 0);
195 int git_win32__find_global_dirs(git_buf
*out
)
197 static const wchar_t *global_tmpls
[4] = {
199 L
"%HOMEDRIVE%%HOMEPATH%\\",
204 return win32_find_existing_dirs(out
, global_tmpls
);
207 int git_win32__find_xdg_dirs(git_buf
*out
)
209 static const wchar_t *global_tmpls
[7] = {
210 L
"%XDG_CONFIG_HOME%\\git",
212 L
"%LOCALAPPDATA%\\git",
213 L
"%HOME%\\.config\\git",
214 L
"%HOMEDRIVE%%HOMEPATH%\\.config\\git",
215 L
"%USERPROFILE%\\.config\\git",
219 return win32_find_existing_dirs(out
, global_tmpls
);
222 int git_win32__find_programdata_dirs(git_buf
*out
)
224 static const wchar_t *programdata_tmpls
[2] = {
225 L
"%PROGRAMDATA%\\Git",
229 return win32_find_existing_dirs(out
, programdata_tmpls
);