Disable unsupported commands
[TortoiseGit.git] / src / GitWCRev / status.cpp
blobcb4f0225365c423bce4b37be11529a30ccef9a46
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2017 - TortoiseGit
4 // Copyright (C) 2003-2016 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "GitWCRev.h"
22 #include "status.h"
23 #include "registry.h"
24 #include "StringUtils.h"
25 #include "UnicodeUtils.h"
26 #include <fstream>
27 #include <ShlObj.h>
28 #include "git2/sys/repository.h"
30 void LoadIgnorePatterns(const char* wc, GitWCRev_t* GitStat)
32 std::string path = wc;
33 std::string ignorepath = path + "/.GitWCRevignore";
35 std::ifstream infile;
36 infile.open(ignorepath);
37 if (!infile.good())
38 return;
40 GitStat->ignorepatterns.emplace("*");
42 std::string line;
43 while (std::getline(infile, line))
45 if (line.empty())
46 continue;
48 line.insert(line.begin(), '!');
49 GitStat->ignorepatterns.emplace(line);
53 static std::wstring GetHomePath()
55 wchar_t* tmp;
56 if ((tmp = _wgetenv(L"HOME")) != nullptr && *tmp)
57 return tmp;
59 if ((tmp = _wgetenv(L"HOMEDRIVE")) != nullptr)
61 std::wstring home(tmp);
62 if ((tmp = _wgetenv(L"HOMEPATH")) != nullptr)
64 home.append(tmp);
65 if (PathIsDirectory(home.c_str()))
66 return home;
70 if ((tmp = _wgetenv(L"USERPROFILE")) != nullptr && *tmp)
71 return tmp;
73 return {};
76 static int is_cygwin_msys2_hack_active()
78 HKEY hKey;
79 DWORD dwType = REG_DWORD;
80 DWORD dwValue = 0;
81 DWORD dwSize = sizeof(dwValue);
82 if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\TortoiseGit", 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
84 RegQueryValueExW(hKey, L"CygwinHack", nullptr, &dwType, (LPBYTE)&dwValue, &dwSize);
85 if (dwValue != 1)
86 RegQueryValueExW(hKey, L"Msys2Hack", nullptr, &dwType, (LPBYTE)&dwValue, &dwSize);
87 RegCloseKey(hKey);
89 return dwValue == 1;
92 static std::wstring GetProgramDataConfig()
94 wchar_t wbuffer[MAX_PATH];
96 // do not use shared windows-wide system config when cygwin hack is active
97 if (is_cygwin_msys2_hack_active())
98 return {};
100 if (SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA, nullptr, SHGFP_TYPE_CURRENT, wbuffer) != S_OK || wcslen(wbuffer) >= MAX_PATH - 11) /* 11 = len("\\Git\\config") */
101 return{};
103 wcscat(wbuffer, L"\\Git\\config");
105 return wbuffer;
108 static std::wstring GetSystemGitConfig()
110 HKEY hKey;
111 DWORD dwType = REG_SZ;
112 TCHAR path[MAX_PATH] = { 0 };
113 DWORD dwSize = _countof(path) - 1;
114 if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\TortoiseGit", 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
116 RegQueryValueExW(hKey, L"SystemConfig", nullptr, &dwType, (LPBYTE)&path, &dwSize);
117 RegCloseKey(hKey);
119 return path;
122 int GetStatus(const TCHAR* path, GitWCRev_t& GitStat)
124 std::string pathA = CUnicodeUtils::StdGetUTF8(path);
125 CAutoBuf dotgitdir;
126 if (git_repository_discover(dotgitdir, pathA.c_str(), 0, nullptr) < 0)
127 return ERR_NOWC;
129 CAutoRepository repo;
130 if (git_repository_open(repo.GetPointer(), dotgitdir->ptr))
131 return ERR_NOWC;
133 CAutoConfig config(true);
134 std::string gitdir(dotgitdir->ptr, dotgitdir->size);
135 git_config_add_file_ondisk(config, (gitdir + "config").c_str(), GIT_CONFIG_LEVEL_LOCAL, FALSE);
136 std::string home(CUnicodeUtils::StdGetUTF8(GetHomePath()));
137 git_config_add_file_ondisk(config, (home + "\\.gitconfig").c_str(), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
138 git_config_add_file_ondisk(config, (home + "\\.config\\git\\config").c_str(), GIT_CONFIG_LEVEL_XDG, FALSE);
139 std::wstring systemConfig = GetSystemGitConfig();
140 if (!systemConfig.empty())
141 git_config_add_file_ondisk(config, CUnicodeUtils::StdGetUTF8(systemConfig).c_str(), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
142 std::wstring programDataConfig = GetProgramDataConfig();
143 if (!programDataConfig.empty())
144 git_config_add_file_ondisk(config, CUnicodeUtils::StdGetUTF8(programDataConfig).c_str(), GIT_CONFIG_LEVEL_PROGRAMDATA, FALSE);
145 git_repository_set_config(repo, config);
147 if (git_repository_head_unborn(repo))
149 memset(GitStat.HeadHash, 0, sizeof(GitStat.HeadHash));
150 strncpy_s(GitStat.HeadHashReadable, GIT_OID_HEX_ZERO, strlen(GIT_OID_HEX_ZERO));
151 GitStat.bIsUnborn = TRUE;
152 return 0;
155 CAutoReference head;
156 if (git_repository_head(head.GetPointer(), repo) < 0)
157 return ERR_GIT_ERR;
159 CAutoObject object;
160 if (git_reference_peel(object.GetPointer(), head, GIT_OBJ_COMMIT) < 0)
161 return ERR_GIT_ERR;
163 const git_oid* oid = git_object_id(object);
164 git_oid_cpy((git_oid*)GitStat.HeadHash, oid);
165 git_oid_tostr(GitStat.HeadHashReadable, sizeof(GitStat.HeadHashReadable), oid);
167 CAutoCommit commit;
168 if (git_commit_lookup(commit.GetPointer(), repo, oid) < 0)
169 return ERR_GIT_ERR;
171 const git_signature* sig = git_commit_author(commit);
172 GitStat.HeadAuthor = sig->name;
173 GitStat.HeadEmail = sig->email;
174 GitStat.HeadTime = sig->when.time;
176 #pragma warning(push)
177 #pragma warning(disable: 4510 4512 4610)
178 struct TagPayload { git_repository* repo; GitWCRev_t& GitStat; } tagpayload = { repo, GitStat };
179 #pragma warning(pop)
181 if (git_tag_foreach(repo, [](const char*, git_oid* tagoid, void* payload)
183 auto pl = reinterpret_cast<struct TagPayload*>(payload);
184 if (git_oid_cmp(tagoid, (git_oid*)pl->GitStat.HeadHash) == 0)
186 pl->GitStat.bIsTagged = TRUE;
187 return 0;
190 CAutoTag tag;
191 if (git_tag_lookup(tag.GetPointer(), pl->repo, tagoid))
192 return 0; // not an annotated tag
193 CAutoObject tagObject;
194 if (git_tag_peel(tagObject.GetPointer(), tag))
195 return -1;
196 if (git_oid_cmp(git_object_id(tagObject), (git_oid*)pl->GitStat.HeadHash) == 0)
197 pl->GitStat.bIsTagged = TRUE;
199 return 0;
200 }, &tagpayload))
201 return ERR_GIT_ERR;
203 git_status_options git_status_options = GIT_STATUS_OPTIONS_INIT;
204 git_status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
205 if (GitStat.bNoSubmodules)
206 git_status_options.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
208 std::string workdir(git_repository_workdir(repo));
209 LoadIgnorePatterns(workdir.c_str(), &GitStat);
211 std::vector<char*> pathspec;
212 if (!GitStat.ignorepatterns.empty())
214 for (auto& i : GitStat.ignorepatterns)
215 pathspec.emplace_back(const_cast<char*>(i.c_str()));
216 git_status_options.pathspec.count = GitStat.ignorepatterns.size();
217 git_status_options.pathspec.strings = &pathspec.at(0);
220 CAutoStatusList status;
221 if (git_status_list_new(status.GetPointer(), repo, &git_status_options) < 0)
222 return ERR_GIT_ERR;
224 for (size_t i = 0, maxi = git_status_list_entrycount(status); i < maxi; ++i)
226 const git_status_entry* s = git_status_byindex(status, i);
227 if (s->index_to_workdir && s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT)
229 GitStat.bHasSubmodule = TRUE;
230 unsigned int smstatus = 0;
231 if (!git_submodule_status(&smstatus, repo, s->index_to_workdir->new_file.path, GIT_SUBMODULE_IGNORE_UNSPECIFIED))
233 if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED) // HEAD of submodule not matching
234 GitStat.bHasSubmoduleNewCommits = TRUE;
235 else if ((smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) || (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED))
236 GitStat.bHasSubmoduleMods = TRUE;
237 else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED)
238 GitStat.bHasSubmoduleUnversioned = TRUE;
240 continue;
242 if (s->status == GIT_STATUS_CURRENT)
243 continue;
244 if (s->status == GIT_STATUS_WT_NEW)
245 GitStat.HasUnversioned = TRUE;
246 else
247 GitStat.HasMods = TRUE;
250 std::transform(pathA.begin(), pathA.end(), pathA.begin(), [](char c) { return (c == '\\') ? '/' : c; });
251 pathA.erase(pathA.begin(), pathA.begin() + min(workdir.length(), pathA.length())); // workdir always ends with a slash, however, wcA is not guaranteed to
252 if (pathA.empty()) // working tree root is always versioned
254 GitStat.bIsGitItem = TRUE;
255 return 0;
257 unsigned int status_flags = 0;
258 int ret = git_status_file(&status_flags, repo, pathA.c_str());
259 GitStat.bIsGitItem = (ret == GIT_EAMBIGUOUS || (ret == 0 && !(status_flags & GIT_STATUS_IGNORED)));
260 return 0;