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.
24 #include "StringUtils.h"
25 #include "UnicodeUtils.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";
36 infile
.open(ignorepath
);
40 GitStat
->ignorepatterns
.emplace("*");
43 while (std::getline(infile
, line
))
48 line
.insert(line
.begin(), '!');
49 GitStat
->ignorepatterns
.emplace(line
);
53 static std::wstring
GetHomePath()
56 if ((tmp
= _wgetenv(L
"HOME")) != nullptr && *tmp
)
59 if ((tmp
= _wgetenv(L
"HOMEDRIVE")) != nullptr)
61 std::wstring
home(tmp
);
62 if ((tmp
= _wgetenv(L
"HOMEPATH")) != nullptr)
65 if (PathIsDirectory(home
.c_str()))
70 if ((tmp
= _wgetenv(L
"USERPROFILE")) != nullptr && *tmp
)
76 static int is_cygwin_msys2_hack_active()
79 DWORD dwType
= REG_DWORD
;
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
);
86 RegQueryValueExW(hKey
, L
"Msys2Hack", nullptr, &dwType
, (LPBYTE
)&dwValue
, &dwSize
);
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())
100 if (SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA
, nullptr, SHGFP_TYPE_CURRENT
, wbuffer
) != S_OK
|| wcslen(wbuffer
) >= MAX_PATH
- wcslen(L
"\\Git\\config"))
103 wcscat(wbuffer
, L
"\\Git\\config");
108 static std::wstring
GetSystemGitConfig()
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
);
122 int GetStatus(const TCHAR
* path
, GitWCRev_t
& GitStat
)
124 std::string pathA
= CUnicodeUtils::StdGetUTF8(path
);
126 if (git_repository_discover(dotgitdir
, pathA
.c_str(), 0, nullptr) < 0)
129 CAutoRepository repo
;
130 if (git_repository_open(repo
.GetPointer(), dotgitdir
->ptr
))
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
;
156 if (git_repository_head(head
.GetPointer(), repo
) < 0)
160 if (git_reference_peel(object
.GetPointer(), head
, GIT_OBJ_COMMIT
) < 0)
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
);
168 if (git_commit_lookup(commit
.GetPointer(), repo
, oid
) < 0)
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
};
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
;
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
))
196 if (git_oid_cmp(git_object_id(tagObject
), (git_oid
*)pl
->GitStat
.HeadHash
) == 0)
197 pl
->GitStat
.bIsTagged
= TRUE
;
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)
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
;
242 if (s
->status
== GIT_STATUS_CURRENT
)
244 if (s
->status
== GIT_STATUS_WT_NEW
)
245 GitStat
.HasUnversioned
= TRUE
;
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
;
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
)));