Refactor rebase commit list: Don't fill list again and again when reordering commits
[TortoiseGit.git] / src / TortoiseProc / ProjectProperties.cpp
blob0a22bf2f8977cb02bd44585866caadabf556886e
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2011, 2013-2017 - 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 CAutoRepository repo(g_Git.GetGitRepository());
52 CString adminDirPath;
53 if (GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDirPath))
54 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(adminDirPath + L"config"), GIT_CONFIG_LEVEL_APP, repo, FALSE); // this needs to have the highest priority in order to override .tgitconfig settings
56 if (!GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
57 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(g_Git.CombinePath(L".tgitconfig")), GIT_CONFIG_LEVEL_LOCAL, nullptr, FALSE); // this needs to have the second highest priority
58 else
60 CString tmpFile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
61 CTGitPath path(L".tgitconfig");
62 if (g_Git.GetOneFile(L"HEAD", path, tmpFile) == 0)
63 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(tmpFile), GIT_CONFIG_LEVEL_LOCAL, nullptr, FALSE); // this needs to have the second highest priority
66 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(g_Git.GetGitGlobalConfig()), GIT_CONFIG_LEVEL_GLOBAL, repo, FALSE);
67 git_config_add_file_ondisk(gitconfig,CGit::GetGitPathStringA(g_Git.GetGitGlobalXDGConfig()), GIT_CONFIG_LEVEL_XDG, repo, FALSE);
68 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(g_Git.GetGitSystemConfig()), GIT_CONFIG_LEVEL_SYSTEM, repo, FALSE);
69 if (!g_Git.ms_bCygwinGit && !g_Git.ms_bMsys2Git)
70 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(g_Git.GetGitProgramDataConfig()), GIT_CONFIG_LEVEL_PROGRAMDATA, repo, FALSE);
71 giterr_clear();
73 CString sPropVal;
75 gitconfig.GetString(BUGTRAQPROPNAME_LABEL, sLabel);
76 gitconfig.GetString(BUGTRAQPROPNAME_MESSAGE, sMessage);
77 nBugIdPos = sMessage.Find(L"%BUGID%");
78 gitconfig.GetString(BUGTRAQPROPNAME_URL, sUrl);
80 gitconfig.GetBOOL(BUGTRAQPROPNAME_WARNIFNOISSUE, bWarnIfNoIssue);
81 gitconfig.GetBOOL(BUGTRAQPROPNAME_NUMBER, bNumber);
82 gitconfig.GetBOOL(BUGTRAQPROPNAME_APPEND, bAppend);
84 gitconfig.GetString(BUGTRAQPROPNAME_PROVIDERUUID, sProviderUuid);
85 gitconfig.GetString(BUGTRAQPROPNAME_PROVIDERUUID64, sProviderUuid64);
86 gitconfig.GetString(BUGTRAQPROPNAME_PROVIDERPARAMS, sProviderParams);
88 gitconfig.GetBOOL(PROJECTPROPNAME_WARNNOSIGNEDOFFBY, bWarnNoSignedOffBy);
89 gitconfig.GetString(PROJECTPROPNAME_ICON, sIcon);
91 gitconfig.GetString(BUGTRAQPROPNAME_LOGREGEX, sPropVal);
93 sCheckRe = sPropVal;
94 if (sCheckRe.Find('\n')>=0)
96 sBugIDRe = sCheckRe.Mid(sCheckRe.Find('\n')).Trim();
97 sCheckRe = sCheckRe.Left(sCheckRe.Find('\n')).Trim();
99 if (!sCheckRe.IsEmpty())
100 sCheckRe = sCheckRe.Trim();
102 if (gitconfig.GetString(PROJECTPROPNAME_LOGWIDTHLINE, sPropVal) == 0)
104 CString val;
105 val = sPropVal;
106 if (!val.IsEmpty())
107 nLogWidthMarker = _wtoi(val);
110 if (gitconfig.GetString(PROJECTPROPNAME_PROJECTLANGUAGE, sPropVal) == 0)
112 CString val;
113 val = sPropVal;
114 if (val == L"-1")
115 lProjectLanguage = -1;
116 if (!val.IsEmpty())
118 LPTSTR strEnd;
119 lProjectLanguage = wcstol(val, &strEnd, 0);
123 if (gitconfig.GetString(PROJECTPROPNAME_LOGMINSIZE, sPropVal) == 0)
125 CString val;
126 val = sPropVal;
127 if (!val.IsEmpty())
128 nMinLogSize = _wtoi(val);
131 return 0;
134 CString ProjectProperties::GetBugIDFromLog(CString& msg)
136 CString sBugID;
138 if (!sMessage.IsEmpty())
140 CString sBugLine;
141 CString sFirstPart;
142 CString sLastPart;
143 BOOL bTop = FALSE;
144 if (nBugIdPos < 0)
145 return sBugID;
146 sFirstPart = sMessage.Left(nBugIdPos);
147 sLastPart = sMessage.Mid(nBugIdPos + 7);
148 msg.TrimRight('\n');
149 if (msg.ReverseFind('\n')>=0)
151 if (bAppend)
152 sBugLine = msg.Mid(msg.ReverseFind('\n')+1);
153 else
155 sBugLine = msg.Left(msg.Find('\n'));
156 bTop = TRUE;
159 else
161 if (bNumber)
163 // find out if the message consists only of numbers
164 bool bOnlyNumbers = true;
165 for (int i=0; i<msg.GetLength(); ++i)
167 if (!_istdigit(msg[i]))
169 bOnlyNumbers = false;
170 break;
173 if (bOnlyNumbers)
174 sBugLine = msg;
176 else
177 sBugLine = msg;
179 if (sBugLine.IsEmpty() && (msg.ReverseFind('\n') < 0))
180 sBugLine = msg.Mid(msg.ReverseFind('\n')+1);
181 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
182 sBugLine.Empty();
183 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
184 sBugLine.Empty();
185 if (sBugLine.IsEmpty())
187 if (msg.Find('\n')>=0)
188 sBugLine = msg.Left(msg.Find('\n'));
189 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
190 sBugLine.Empty();
191 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
192 sBugLine.Empty();
193 bTop = TRUE;
195 if (sBugLine.IsEmpty())
196 return sBugID;
197 sBugID = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
198 if (bTop)
200 msg = msg.Mid(sBugLine.GetLength());
201 msg.TrimLeft('\n');
203 else
205 msg = msg.Left(msg.GetLength()-sBugLine.GetLength());
206 msg.TrimRight('\n');
209 return sBugID;
212 void ProjectProperties::AutoUpdateRegex()
214 if (regExNeedUpdate)
218 regCheck = std::wregex(sCheckRe);
219 regBugID = std::wregex(sBugIDRe);
221 catch (std::exception&)
225 regExNeedUpdate = false;
229 std::vector<CHARRANGE> ProjectProperties::FindBugIDPositions(const CString& msg)
231 size_t offset1 = 0;
232 size_t offset2 = 0;
233 std::vector<CHARRANGE> result;
235 // first use the checkre string to find bug ID's in the message
236 if (!sCheckRe.IsEmpty())
238 if (!sBugIDRe.IsEmpty())
240 // match with two regex strings (without grouping!)
243 AutoUpdateRegex();
244 const std::wsregex_iterator end;
245 std::wstring s = msg;
246 for (std::wsregex_iterator it(s.cbegin(), s.cend(), regCheck); it != end; ++it)
248 // (*it)[0] is the matched string
249 std::wstring matchedString = (*it)[0];
250 ptrdiff_t matchpos = it->position(0);
251 for (std::wsregex_iterator it2(matchedString.cbegin(), matchedString.cend(), regBugID); it2 != end; ++it2)
253 ATLTRACE(L"matched id : %s\n", (*it2)[0].str().c_str());
254 ptrdiff_t matchposID = it2->position(0);
255 CHARRANGE range = {(LONG)(matchpos+matchposID), (LONG)(matchpos+matchposID+(*it2)[0].str().size())};
256 result.push_back(range);
260 catch (std::exception&) {}
262 else
266 AutoUpdateRegex();
267 const std::wsregex_iterator end;
268 std::wstring s = msg;
269 for (std::wsregex_iterator it(s.cbegin(), s.cend(), regCheck); it != end; ++it)
271 const std::wsmatch match = *it;
272 // we define group 1 as the whole issue text and
273 // group 2 as the bug ID
274 if (match.size() >= 2)
276 ATLTRACE(L"matched id : %s\n", std::wstring(match[1]).c_str());
277 CHARRANGE range = {(LONG)(match[1].first - s.cbegin()), (LONG)(match[1].second - s.cbegin())};
278 result.push_back(range);
282 catch (std::exception&) {}
285 else if (result.empty() && (!sMessage.IsEmpty()))
287 CString sBugLine;
288 CString sFirstPart;
289 CString sLastPart;
290 BOOL bTop = FALSE;
291 if (nBugIdPos < 0)
292 return result;
294 sFirstPart = sMessage.Left(nBugIdPos);
295 sLastPart = sMessage.Mid(nBugIdPos + 7);
296 CString sMsg = msg;
297 sMsg.TrimRight('\n');
298 if (sMsg.ReverseFind('\n')>=0)
300 if (bAppend)
301 sBugLine = sMsg.Mid(sMsg.ReverseFind('\n')+1);
302 else
304 sBugLine = sMsg.Left(sMsg.Find('\n'));
305 bTop = TRUE;
308 else
309 sBugLine = sMsg;
310 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
311 sBugLine.Empty();
312 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
313 sBugLine.Empty();
314 if (sBugLine.IsEmpty())
316 if (sMsg.Find('\n')>=0)
317 sBugLine = sMsg.Left(sMsg.Find('\n'));
318 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
319 sBugLine.Empty();
320 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
321 sBugLine.Empty();
322 bTop = TRUE;
324 if (sBugLine.IsEmpty())
325 return result;
327 CString sBugIDPart = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
328 if (sBugIDPart.IsEmpty())
329 return result;
331 //the bug id part can contain several bug id's, separated by commas
332 if (!bTop)
333 offset1 = sMsg.GetLength() - sBugLine.GetLength() + sFirstPart.GetLength();
334 else
335 offset1 = sFirstPart.GetLength();
336 sBugIDPart.Trim(L',');
337 while (sBugIDPart.Find(',')>=0)
339 offset2 = offset1 + sBugIDPart.Find(',');
340 CHARRANGE range = {(LONG)offset1, (LONG)offset2};
341 result.push_back(range);
342 sBugIDPart = sBugIDPart.Mid(sBugIDPart.Find(',')+1);
343 offset1 = offset2 + 1;
345 offset2 = offset1 + sBugIDPart.GetLength();
346 CHARRANGE range = {(LONG)offset1, (LONG)offset2};
347 result.push_back(range);
350 return result;
353 BOOL ProjectProperties::FindBugID(const CString& msg, CWnd * pWnd)
355 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
356 CCommonAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
358 return positions.empty() ? FALSE : TRUE;
361 std::set<CString> ProjectProperties::FindBugIDs (const CString& msg)
363 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
364 std::set<CString> bugIDs;
366 for (const auto& pos : positions)
367 bugIDs.insert(msg.Mid(pos.cpMin, pos.cpMax - pos.cpMin));
369 return bugIDs;
372 CString ProjectProperties::FindBugID(const CString& msg)
374 CString sRet;
375 if (!sCheckRe.IsEmpty() || (nBugIdPos >= 0))
377 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
378 std::set<CString, num_compare> bugIDs;
379 for (const auto& pos : positions)
380 bugIDs.insert(msg.Mid(pos.cpMin, pos.cpMax - pos.cpMin));
382 for (const auto& id : bugIDs)
384 sRet += id;
385 sRet += L' ';
387 sRet.Trim();
390 return sRet;
393 bool ProjectProperties::MightContainABugID()
395 return !sCheckRe.IsEmpty() || (nBugIdPos >= 0);
398 CString ProjectProperties::GetBugIDUrl(const CString& sBugID)
400 CString ret;
401 if (sUrl.IsEmpty())
402 return ret;
403 if (!sMessage.IsEmpty() || !sCheckRe.IsEmpty())
405 ret = sUrl;
406 ret.Replace(L"%BUGID%", sBugID);
408 return ret;
411 BOOL ProjectProperties::CheckBugID(const CString& sID)
413 if (bNumber)
415 // check if the revision actually _is_ a number
416 // or a list of numbers separated by colons
417 TCHAR c = 0;
418 int len = sID.GetLength();
419 for (int i=0; i<len; ++i)
421 c = sID.GetAt(i);
422 if ((c < '0')&&(c != ',')&&(c != ' '))
423 return FALSE;
424 if (c > '9')
425 return FALSE;
428 return TRUE;
431 BOOL ProjectProperties::HasBugID(const CString& sMsg)
433 if (!sCheckRe.IsEmpty())
437 AutoUpdateRegex();
438 return std::regex_search((LPCTSTR)sMsg, regCheck);
440 catch (std::exception&) {}
442 return FALSE;