Re-integrate the GPG signed commit into the unit tests
[TortoiseGit.git] / src / TortoiseProc / ProjectProperties.cpp
blobb30c52147f9d1f32d16117d7804ab498f51a1f86
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2011, 2013-2016 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "ProjectProperties.h"
21 #include "CommonAppUtils.h"
22 #include "Git.h"
23 #include "UnicodeUtils.h"
24 #include "TempFile.h"
26 struct num_compare
28 bool operator() (const CString& lhs, const CString& rhs) const
30 return StrCmpLogicalW(lhs, rhs) < 0;
34 ProjectProperties::ProjectProperties(void)
35 : regExNeedUpdate (true)
36 , nBugIdPos(-1)
37 , bWarnNoSignedOffBy(FALSE)
38 , bNumber(TRUE)
39 , bWarnIfNoIssue(FALSE)
40 , nLogWidthMarker(0)
41 , nMinLogSize(0)
42 , bFileListInEnglish(TRUE)
43 , bAppend(TRUE)
44 , lProjectLanguage(0)
48 int ProjectProperties::ReadProps()
50 CAutoConfig gitconfig(true);
51 CString adminDirPath;
52 if (GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDirPath))
53 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(adminDirPath + L"config"), GIT_CONFIG_LEVEL_APP, FALSE); // this needs to have the highest priority in order to override .tgitconfig settings
55 if (!GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
56 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(g_Git.CombinePath(L".tgitconfig")), GIT_CONFIG_LEVEL_LOCAL, FALSE); // this needs to have the second highest priority
57 else
59 CString tmpFile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
60 CTGitPath path(L".tgitconfig");
61 if (g_Git.GetOneFile(L"HEAD", path, tmpFile) == 0)
62 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(tmpFile), GIT_CONFIG_LEVEL_LOCAL, FALSE); // this needs to have the second highest priority
65 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(g_Git.GetGitGlobalConfig()), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
66 git_config_add_file_ondisk(gitconfig,CGit::GetGitPathStringA(g_Git.GetGitGlobalXDGConfig()), GIT_CONFIG_LEVEL_XDG, FALSE);
67 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(g_Git.GetGitSystemConfig()), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
68 if (!g_Git.ms_bCygwinGit && !g_Git.ms_bMsys2Git)
69 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(g_Git.GetGitProgramDataConfig()), GIT_CONFIG_LEVEL_PROGRAMDATA, FALSE);
70 giterr_clear();
72 CString sPropVal;
74 gitconfig.GetString(BUGTRAQPROPNAME_LABEL, sLabel);
75 gitconfig.GetString(BUGTRAQPROPNAME_MESSAGE, sMessage);
76 nBugIdPos = sMessage.Find(L"%BUGID%");
77 gitconfig.GetString(BUGTRAQPROPNAME_URL, sUrl);
79 gitconfig.GetBOOL(BUGTRAQPROPNAME_WARNIFNOISSUE, bWarnIfNoIssue);
80 gitconfig.GetBOOL(BUGTRAQPROPNAME_NUMBER, bNumber);
81 gitconfig.GetBOOL(BUGTRAQPROPNAME_APPEND, bAppend);
83 gitconfig.GetString(BUGTRAQPROPNAME_PROVIDERUUID, sProviderUuid);
84 gitconfig.GetString(BUGTRAQPROPNAME_PROVIDERUUID64, sProviderUuid64);
85 gitconfig.GetString(BUGTRAQPROPNAME_PROVIDERPARAMS, sProviderParams);
87 gitconfig.GetBOOL(PROJECTPROPNAME_WARNNOSIGNEDOFFBY, bWarnNoSignedOffBy);
88 gitconfig.GetString(PROJECTPROPNAME_ICON, sIcon);
90 gitconfig.GetString(BUGTRAQPROPNAME_LOGREGEX, sPropVal);
92 sCheckRe = sPropVal;
93 if (sCheckRe.Find('\n')>=0)
95 sBugIDRe = sCheckRe.Mid(sCheckRe.Find('\n')).Trim();
96 sCheckRe = sCheckRe.Left(sCheckRe.Find('\n')).Trim();
98 if (!sCheckRe.IsEmpty())
99 sCheckRe = sCheckRe.Trim();
101 if (gitconfig.GetString(PROJECTPROPNAME_LOGWIDTHLINE, sPropVal) == 0)
103 CString val;
104 val = sPropVal;
105 if (!val.IsEmpty())
106 nLogWidthMarker = _wtoi(val);
109 if (gitconfig.GetString(PROJECTPROPNAME_PROJECTLANGUAGE, sPropVal) == 0)
111 CString val;
112 val = sPropVal;
113 if (val == L"-1")
114 lProjectLanguage = -1;
115 if (!val.IsEmpty())
117 LPTSTR strEnd;
118 lProjectLanguage = wcstol(val, &strEnd, 0);
122 if (gitconfig.GetString(PROJECTPROPNAME_LOGMINSIZE, sPropVal) == 0)
124 CString val;
125 val = sPropVal;
126 if (!val.IsEmpty())
127 nMinLogSize = _wtoi(val);
130 return 0;
133 CString ProjectProperties::GetBugIDFromLog(CString& msg)
135 CString sBugID;
137 if (!sMessage.IsEmpty())
139 CString sBugLine;
140 CString sFirstPart;
141 CString sLastPart;
142 BOOL bTop = FALSE;
143 if (nBugIdPos < 0)
144 return sBugID;
145 sFirstPart = sMessage.Left(nBugIdPos);
146 sLastPart = sMessage.Mid(nBugIdPos + 7);
147 msg.TrimRight('\n');
148 if (msg.ReverseFind('\n')>=0)
150 if (bAppend)
151 sBugLine = msg.Mid(msg.ReverseFind('\n')+1);
152 else
154 sBugLine = msg.Left(msg.Find('\n'));
155 bTop = TRUE;
158 else
160 if (bNumber)
162 // find out if the message consists only of numbers
163 bool bOnlyNumbers = true;
164 for (int i=0; i<msg.GetLength(); ++i)
166 if (!_istdigit(msg[i]))
168 bOnlyNumbers = false;
169 break;
172 if (bOnlyNumbers)
173 sBugLine = msg;
175 else
176 sBugLine = msg;
178 if (sBugLine.IsEmpty() && (msg.ReverseFind('\n') < 0))
179 sBugLine = msg.Mid(msg.ReverseFind('\n')+1);
180 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
181 sBugLine.Empty();
182 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
183 sBugLine.Empty();
184 if (sBugLine.IsEmpty())
186 if (msg.Find('\n')>=0)
187 sBugLine = msg.Left(msg.Find('\n'));
188 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
189 sBugLine.Empty();
190 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
191 sBugLine.Empty();
192 bTop = TRUE;
194 if (sBugLine.IsEmpty())
195 return sBugID;
196 sBugID = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
197 if (bTop)
199 msg = msg.Mid(sBugLine.GetLength());
200 msg.TrimLeft('\n');
202 else
204 msg = msg.Left(msg.GetLength()-sBugLine.GetLength());
205 msg.TrimRight('\n');
208 return sBugID;
211 void ProjectProperties::AutoUpdateRegex()
213 if (regExNeedUpdate)
217 regCheck = std::tr1::wregex(sCheckRe);
218 regBugID = std::tr1::wregex(sBugIDRe);
220 catch (std::exception&)
224 regExNeedUpdate = false;
228 std::vector<CHARRANGE> ProjectProperties::FindBugIDPositions(const CString& msg)
230 size_t offset1 = 0;
231 size_t offset2 = 0;
232 std::vector<CHARRANGE> result;
234 // first use the checkre string to find bug ID's in the message
235 if (!sCheckRe.IsEmpty())
237 if (!sBugIDRe.IsEmpty())
239 // match with two regex strings (without grouping!)
242 AutoUpdateRegex();
243 const std::tr1::wsregex_iterator end;
244 std::wstring s = msg;
245 for (std::tr1::wsregex_iterator it(s.cbegin(), s.cend(), regCheck); it != end; ++it)
247 // (*it)[0] is the matched string
248 std::wstring matchedString = (*it)[0];
249 ptrdiff_t matchpos = it->position(0);
250 for (std::tr1::wsregex_iterator it2(matchedString.cbegin(), matchedString.cend(), regBugID); it2 != end; ++it2)
252 ATLTRACE(L"matched id : %s\n", (*it2)[0].str().c_str());
253 ptrdiff_t matchposID = it2->position(0);
254 CHARRANGE range = {(LONG)(matchpos+matchposID), (LONG)(matchpos+matchposID+(*it2)[0].str().size())};
255 result.push_back(range);
259 catch (std::exception&) {}
261 else
265 AutoUpdateRegex();
266 const std::tr1::wsregex_iterator end;
267 std::wstring s = msg;
268 for (std::tr1::wsregex_iterator it(s.cbegin(), s.cend(), regCheck); it != end; ++it)
270 const std::tr1::wsmatch match = *it;
271 // we define group 1 as the whole issue text and
272 // group 2 as the bug ID
273 if (match.size() >= 2)
275 ATLTRACE(L"matched id : %s\n", std::wstring(match[1]).c_str());
276 CHARRANGE range = {(LONG)(match[1].first - s.cbegin()), (LONG)(match[1].second - s.cbegin())};
277 result.push_back(range);
281 catch (std::exception&) {}
284 else if (result.empty() && (!sMessage.IsEmpty()))
286 CString sBugLine;
287 CString sFirstPart;
288 CString sLastPart;
289 BOOL bTop = FALSE;
290 if (nBugIdPos < 0)
291 return result;
293 sFirstPart = sMessage.Left(nBugIdPos);
294 sLastPart = sMessage.Mid(nBugIdPos + 7);
295 CString sMsg = msg;
296 sMsg.TrimRight('\n');
297 if (sMsg.ReverseFind('\n')>=0)
299 if (bAppend)
300 sBugLine = sMsg.Mid(sMsg.ReverseFind('\n')+1);
301 else
303 sBugLine = sMsg.Left(sMsg.Find('\n'));
304 bTop = TRUE;
307 else
308 sBugLine = sMsg;
309 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
310 sBugLine.Empty();
311 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
312 sBugLine.Empty();
313 if (sBugLine.IsEmpty())
315 if (sMsg.Find('\n')>=0)
316 sBugLine = sMsg.Left(sMsg.Find('\n'));
317 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
318 sBugLine.Empty();
319 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
320 sBugLine.Empty();
321 bTop = TRUE;
323 if (sBugLine.IsEmpty())
324 return result;
326 CString sBugIDPart = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
327 if (sBugIDPart.IsEmpty())
328 return result;
330 //the bug id part can contain several bug id's, separated by commas
331 if (!bTop)
332 offset1 = sMsg.GetLength() - sBugLine.GetLength() + sFirstPart.GetLength();
333 else
334 offset1 = sFirstPart.GetLength();
335 sBugIDPart.Trim(L',');
336 while (sBugIDPart.Find(',')>=0)
338 offset2 = offset1 + sBugIDPart.Find(',');
339 CHARRANGE range = {(LONG)offset1, (LONG)offset2};
340 result.push_back(range);
341 sBugIDPart = sBugIDPart.Mid(sBugIDPart.Find(',')+1);
342 offset1 = offset2 + 1;
344 offset2 = offset1 + sBugIDPart.GetLength();
345 CHARRANGE range = {(LONG)offset1, (LONG)offset2};
346 result.push_back(range);
349 return result;
352 BOOL ProjectProperties::FindBugID(const CString& msg, CWnd * pWnd)
354 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
355 CCommonAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
357 return positions.empty() ? FALSE : TRUE;
360 std::set<CString> ProjectProperties::FindBugIDs (const CString& msg)
362 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
363 std::set<CString> bugIDs;
365 for (const auto& pos : positions)
366 bugIDs.insert(msg.Mid(pos.cpMin, pos.cpMax - pos.cpMin));
368 return bugIDs;
371 CString ProjectProperties::FindBugID(const CString& msg)
373 CString sRet;
374 if (!sCheckRe.IsEmpty() || (nBugIdPos >= 0))
376 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
377 std::set<CString, num_compare> bugIDs;
378 for (const auto& pos : positions)
379 bugIDs.insert(msg.Mid(pos.cpMin, pos.cpMax - pos.cpMin));
381 for (const auto& id : bugIDs)
383 sRet += id;
384 sRet += L' ';
386 sRet.Trim();
389 return sRet;
392 bool ProjectProperties::MightContainABugID()
394 return !sCheckRe.IsEmpty() || (nBugIdPos >= 0);
397 CString ProjectProperties::GetBugIDUrl(const CString& sBugID)
399 CString ret;
400 if (sUrl.IsEmpty())
401 return ret;
402 if (!sMessage.IsEmpty() || !sCheckRe.IsEmpty())
404 ret = sUrl;
405 ret.Replace(L"%BUGID%", sBugID);
407 return ret;
410 BOOL ProjectProperties::CheckBugID(const CString& sID)
412 if (bNumber)
414 // check if the revision actually _is_ a number
415 // or a list of numbers separated by colons
416 TCHAR c = 0;
417 int len = sID.GetLength();
418 for (int i=0; i<len; ++i)
420 c = sID.GetAt(i);
421 if ((c < '0')&&(c != ',')&&(c != ' '))
422 return FALSE;
423 if (c > '9')
424 return FALSE;
427 return TRUE;
430 BOOL ProjectProperties::HasBugID(const CString& sMsg)
432 if (!sCheckRe.IsEmpty())
436 AutoUpdateRegex();
437 return std::tr1::regex_search((LPCTSTR)sMsg, regCheck);
439 catch (std::exception&) {}
441 return FALSE;