Fixed issue #2495: "Show Reflog" dialog shows empty action for "push" entries
[TortoiseGit.git] / src / TortoiseProc / ProjectProperties.cpp
blob4c189c76b47950b2ae0fa72b992e293b69faeef5
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2011,2013-2015 - 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"
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)
39 bNumber = TRUE;
40 bWarnIfNoIssue = FALSE;
41 nLogWidthMarker = 0;
42 nMinLogSize = 0;
43 bFileListInEnglish = TRUE;
44 bAppend = TRUE;
45 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 = GetTempFile();
60 CTGitPath path(_T(".tgitconfig"));
61 if (g_Git.GetOneFile(_T("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.ms_LastMsysGitDir + _T("\\..\\etc\\gitconfig")), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
68 giterr_clear();
70 CString sPropVal;
72 gitconfig.GetString(BUGTRAQPROPNAME_LABEL, sLabel);
73 gitconfig.GetString(BUGTRAQPROPNAME_MESSAGE, sMessage);
74 nBugIdPos = sMessage.Find(L"%BUGID%");
75 gitconfig.GetString(BUGTRAQPROPNAME_URL, sUrl);
77 gitconfig.GetBOOL(BUGTRAQPROPNAME_WARNIFNOISSUE, bWarnIfNoIssue);
78 gitconfig.GetBOOL(BUGTRAQPROPNAME_NUMBER, bNumber);
79 gitconfig.GetBOOL(BUGTRAQPROPNAME_APPEND, bAppend);
81 gitconfig.GetString(BUGTRAQPROPNAME_PROVIDERUUID, sProviderUuid);
82 gitconfig.GetString(BUGTRAQPROPNAME_PROVIDERUUID64, sProviderUuid64);
83 gitconfig.GetString(BUGTRAQPROPNAME_PROVIDERPARAMS, sProviderParams);
85 gitconfig.GetBOOL(PROJECTPROPNAME_WARNNOSIGNEDOFFBY, bWarnNoSignedOffBy);
86 gitconfig.GetString(PROJECTPROPNAME_ICON, sIcon);
88 gitconfig.GetString(BUGTRAQPROPNAME_LOGREGEX, sPropVal);
90 sCheckRe = sPropVal;
91 if (sCheckRe.Find('\n')>=0)
93 sBugIDRe = sCheckRe.Mid(sCheckRe.Find('\n')).Trim();
94 sCheckRe = sCheckRe.Left(sCheckRe.Find('\n')).Trim();
96 if (!sCheckRe.IsEmpty())
98 sCheckRe = sCheckRe.Trim();
101 if (gitconfig.GetString(PROJECTPROPNAME_LOGWIDTHLINE, sPropVal) == 0)
103 CString val;
104 val = sPropVal;
105 if (!val.IsEmpty())
106 nLogWidthMarker = _ttoi(val) + 2; // HACK, + 2 needed
109 if (gitconfig.GetString(PROJECTPROPNAME_PROJECTLANGUAGE, sPropVal) == 0)
111 CString val;
112 val = sPropVal;
113 if (val == _T("-1"))
114 lProjectLanguage = -1;
115 if (!val.IsEmpty())
117 LPTSTR strEnd;
118 lProjectLanguage = _tcstol(val, &strEnd, 0);
122 if (gitconfig.GetString(PROJECTPROPNAME_LOGMINSIZE, sPropVal) == 0)
124 CString val;
125 val = sPropVal;
126 if (!val.IsEmpty())
127 nMinLogSize = _ttoi(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))
180 sBugLine = msg.Mid(msg.ReverseFind('\n')+1);
182 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
183 sBugLine.Empty();
184 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
185 sBugLine.Empty();
186 if (sBugLine.IsEmpty())
188 if (msg.Find('\n')>=0)
189 sBugLine = msg.Left(msg.Find('\n'));
190 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
191 sBugLine.Empty();
192 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
193 sBugLine.Empty();
194 bTop = TRUE;
196 if (sBugLine.IsEmpty())
197 return sBugID;
198 sBugID = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
199 if (bTop)
201 msg = msg.Mid(sBugLine.GetLength());
202 msg.TrimLeft('\n');
204 else
206 msg = msg.Left(msg.GetLength()-sBugLine.GetLength());
207 msg.TrimRight('\n');
210 return sBugID;
213 void ProjectProperties::AutoUpdateRegex()
215 if (regExNeedUpdate)
219 regCheck = std::tr1::wregex(sCheckRe);
220 regBugID = std::tr1::wregex(sBugIDRe);
222 catch (std::exception)
226 regExNeedUpdate = false;
230 std::vector<CHARRANGE> ProjectProperties::FindBugIDPositions(const CString& msg)
232 size_t offset1 = 0;
233 size_t offset2 = 0;
234 std::vector<CHARRANGE> result;
236 // first use the checkre string to find bug ID's in the message
237 if (!sCheckRe.IsEmpty())
239 if (!sBugIDRe.IsEmpty())
242 // match with two regex strings (without grouping!)
245 AutoUpdateRegex();
246 const std::tr1::wsregex_iterator end;
247 std::wstring s = msg;
248 for (std::tr1::wsregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)
250 // (*it)[0] is the matched string
251 std::wstring matchedString = (*it)[0];
252 ptrdiff_t matchpos = it->position(0);
253 for (std::tr1::wsregex_iterator it2(matchedString.begin(), matchedString.end(), regBugID); it2 != end; ++it2)
255 ATLTRACE(_T("matched id : %s\n"), (*it2)[0].str().c_str());
256 ptrdiff_t matchposID = it2->position(0);
257 CHARRANGE range = {(LONG)(matchpos+matchposID), (LONG)(matchpos+matchposID+(*it2)[0].str().size())};
258 result.push_back(range);
262 catch (std::exception) {}
264 else
268 AutoUpdateRegex();
269 const std::tr1::wsregex_iterator end;
270 std::wstring s = msg;
271 for (std::tr1::wsregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)
273 const std::tr1::wsmatch match = *it;
274 // we define group 1 as the whole issue text and
275 // group 2 as the bug ID
276 if (match.size() >= 2)
278 ATLTRACE(_T("matched id : %s\n"), std::wstring(match[1]).c_str());
279 CHARRANGE range = {(LONG)(match[1].first-s.begin()), (LONG)(match[1].second-s.begin())};
280 result.push_back(range);
284 catch (std::exception) {}
287 else if (result.empty() && (!sMessage.IsEmpty()))
289 CString sBugLine;
290 CString sFirstPart;
291 CString sLastPart;
292 BOOL bTop = FALSE;
293 if (nBugIdPos < 0)
294 return result;
296 sFirstPart = sMessage.Left(nBugIdPos);
297 sLastPart = sMessage.Mid(nBugIdPos + 7);
298 CString sMsg = msg;
299 sMsg.TrimRight('\n');
300 if (sMsg.ReverseFind('\n')>=0)
302 if (bAppend)
303 sBugLine = sMsg.Mid(sMsg.ReverseFind('\n')+1);
304 else
306 sBugLine = sMsg.Left(sMsg.Find('\n'));
307 bTop = TRUE;
310 else
311 sBugLine = sMsg;
312 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
313 sBugLine.Empty();
314 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
315 sBugLine.Empty();
316 if (sBugLine.IsEmpty())
318 if (sMsg.Find('\n')>=0)
319 sBugLine = sMsg.Left(sMsg.Find('\n'));
320 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
321 sBugLine.Empty();
322 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
323 sBugLine.Empty();
324 bTop = TRUE;
326 if (sBugLine.IsEmpty())
327 return result;
329 CString sBugIDPart = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
330 if (sBugIDPart.IsEmpty())
331 return result;
333 //the bug id part can contain several bug id's, separated by commas
334 if (!bTop)
335 offset1 = sMsg.GetLength() - sBugLine.GetLength() + sFirstPart.GetLength();
336 else
337 offset1 = sFirstPart.GetLength();
338 sBugIDPart.Trim(_T(","));
339 while (sBugIDPart.Find(',')>=0)
341 offset2 = offset1 + sBugIDPart.Find(',');
342 CHARRANGE range = {(LONG)offset1, (LONG)offset2};
343 result.push_back(range);
344 sBugIDPart = sBugIDPart.Mid(sBugIDPart.Find(',')+1);
345 offset1 = offset2 + 1;
347 offset2 = offset1 + sBugIDPart.GetLength();
348 CHARRANGE range = {(LONG)offset1, (LONG)offset2};
349 result.push_back(range);
352 return result;
355 BOOL ProjectProperties::FindBugID(const CString& msg, CWnd * pWnd)
357 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
358 CCommonAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
360 return positions.empty() ? FALSE : TRUE;
363 std::set<CString> ProjectProperties::FindBugIDs (const CString& msg)
365 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
366 std::set<CString> bugIDs;
368 for (std::vector<CHARRANGE>::iterator iter = positions.begin(), end = positions.end(); iter != end; ++iter)
370 bugIDs.insert(msg.Mid(iter->cpMin, iter->cpMax - iter->cpMin));
373 return bugIDs;
376 CString ProjectProperties::FindBugID(const CString& msg)
378 CString sRet;
379 if (!sCheckRe.IsEmpty() || (nBugIdPos >= 0))
381 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
382 std::set<CString, num_compare> bugIDs;
383 for (std::vector<CHARRANGE>::iterator iter = positions.begin(), end = positions.end(); iter != end; ++iter)
385 bugIDs.insert(msg.Mid(iter->cpMin, iter->cpMax - iter->cpMin));
388 for (std::set<CString, num_compare>::iterator it = bugIDs.begin(); it != bugIDs.end(); ++it)
390 sRet += *it;
391 sRet += _T(" ");
393 sRet.Trim();
396 return sRet;
399 bool ProjectProperties::MightContainABugID()
401 return !sCheckRe.IsEmpty() || (nBugIdPos >= 0);
404 CString ProjectProperties::GetBugIDUrl(const CString& sBugID)
406 CString ret;
407 if (sUrl.IsEmpty())
408 return ret;
409 if (!sMessage.IsEmpty() || !sCheckRe.IsEmpty())
411 ret = sUrl;
412 ret.Replace(_T("%BUGID%"), sBugID);
414 return ret;
417 BOOL ProjectProperties::CheckBugID(const CString& sID)
419 if (bNumber)
421 // check if the revision actually _is_ a number
422 // or a list of numbers separated by colons
423 TCHAR c = 0;
424 int len = sID.GetLength();
425 for (int i=0; i<len; ++i)
427 c = sID.GetAt(i);
428 if ((c < '0')&&(c != ',')&&(c != ' '))
430 return FALSE;
432 if (c > '9')
433 return FALSE;
436 return TRUE;
439 BOOL ProjectProperties::HasBugID(const CString& sMessage)
441 if (!sCheckRe.IsEmpty())
445 AutoUpdateRegex();
446 return std::tr1::regex_search((LPCTSTR)sMessage, regCheck);
448 catch (std::exception) {}
450 return FALSE;