Fixed some warnings
[TortoiseGit.git] / src / TortoiseProc / ProjectProperties.cpp
blob115f33a1a6bc31a3bc65554eedb4539b1bc1f27c
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2011 - 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 "TortoiseProc.h"
21 #include "UnicodeUtils.h"
22 #include "ProjectProperties.h"
23 #include "AppUtils.h"
24 //#include "GitProperties.h"
25 #include "TGitPath.h"
26 #include "git.h"
28 using namespace std;
31 struct num_compare
33 bool operator() (const CString& lhs, const CString& rhs) const
35 return StrCmpLogicalW(lhs, rhs) < 0;
39 ProjectProperties::ProjectProperties(void)
40 : regExNeedUpdate (true)
41 , nBugIdPos(-1)
43 bNumber = TRUE;
44 bWarnIfNoIssue = FALSE;
45 nLogWidthMarker = 0;
46 nMinLogSize = 0;
47 nMinLockMsgSize = 0;
48 bFileListInEnglish = TRUE;
49 bAppend = TRUE;
50 lProjectLanguage = 0;
53 ProjectProperties::~ProjectProperties(void)
58 BOOL ProjectProperties::ReadPropsPathList(const CTGitPathList& pathList)
60 for(int nPath = 0; nPath < pathList.GetCount(); nPath++)
62 if (ReadProps(pathList[nPath]))
64 return TRUE;
67 return FALSE;
70 BOOL ProjectProperties::GetStringProps(CString &prop,TCHAR *key,bool bRemoveCR)
72 CString cmd,output;
73 output.Empty();
75 output = g_Git.GetConfigValue(key,CP_UTF8,NULL, bRemoveCR);
77 if(output.IsEmpty())
79 return FALSE;
82 prop = output;
84 return TRUE;
88 BOOL ProjectProperties::GetBOOLProps(BOOL &b,TCHAR *key)
90 CString str,low;
91 if(!GetStringProps(str,key))
92 return FALSE;
94 low = str.MakeLower().Trim();
95 if(low == _T("true") || low == _T("on") || low == _T("yes") || StrToInt(low) != 0)
96 b=true;
97 else
98 b=false;
100 return true;
104 BOOL ProjectProperties::ReadProps(CTGitPath path)
106 CString sPropVal;
108 GetStringProps(this->sLabel,BUGTRAQPROPNAME_LABEL);
109 GetStringProps(this->sMessage,BUGTRAQPROPNAME_MESSAGE);
110 nBugIdPos = sMessage.Find(L"%BUGID%");
111 GetStringProps(this->sUrl,BUGTRAQPROPNAME_URL);
113 GetBOOLProps(this->bWarnIfNoIssue,BUGTRAQPROPNAME_WARNIFNOISSUE);
114 GetBOOLProps(this->bNumber,BUGTRAQPROPNAME_NUMBER);
115 GetBOOLProps(this->bAppend,BUGTRAQPROPNAME_APPEND);
117 GetStringProps(sPropVal,BUGTRAQPROPNAME_LOGREGEX,false);
119 sCheckRe = sPropVal;
120 if (sCheckRe.Find('\n')>=0)
122 sBugIDRe = sCheckRe.Mid(sCheckRe.Find('\n')).Trim();
123 sCheckRe = sCheckRe.Left(sCheckRe.Find('\n')).Trim();
125 if (!sCheckRe.IsEmpty())
127 sCheckRe = sCheckRe.Trim();
129 return TRUE;
132 CString ProjectProperties::GetBugIDFromLog(CString& msg)
134 CString sBugID;
136 if (!sMessage.IsEmpty())
138 CString sBugLine;
139 CString sFirstPart;
140 CString sLastPart;
141 BOOL bTop = FALSE;
142 if (nBugIdPos < 0)
143 return sBugID;
144 sFirstPart = sMessage.Left(nBugIdPos);
145 sLastPart = sMessage.Mid(nBugIdPos + 7);
146 msg.TrimRight('\n');
147 if (msg.ReverseFind('\n')>=0)
149 if (bAppend)
150 sBugLine = msg.Mid(msg.ReverseFind('\n')+1);
151 else
153 sBugLine = msg.Left(msg.Find('\n'));
154 bTop = TRUE;
157 else
159 if (bNumber)
161 // find out if the message consists only of numbers
162 bool bOnlyNumbers = true;
163 for (int i=0; i<msg.GetLength(); ++i)
165 if (!_istdigit(msg[i]))
167 bOnlyNumbers = false;
168 break;
171 if (bOnlyNumbers)
172 sBugLine = msg;
174 else
175 sBugLine = msg;
177 if (sBugLine.IsEmpty() && (msg.ReverseFind('\n') < 0))
179 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 = tr1::wregex(sCheckRe);
219 regBugID = tr1::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())
241 // match with two regex strings (without grouping!)
244 AutoUpdateRegex();
245 const tr1::wsregex_iterator end;
246 wstring s = msg;
247 for (tr1::wsregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)
249 // (*it)[0] is the matched string
250 wstring matchedString = (*it)[0];
251 ptrdiff_t matchpos = it->position(0);
252 for (tr1::wsregex_iterator it2(matchedString.begin(), matchedString.end(), regBugID); it2 != end; ++it2)
254 ATLTRACE(_T("matched id : %s\n"), (*it2)[0].str().c_str());
255 ptrdiff_t matchposID = it2->position(0);
256 CHARRANGE range = {(LONG)(matchpos+matchposID), (LONG)(matchpos+matchposID+(*it2)[0].str().size())};
257 result.push_back(range);
261 catch (exception) {}
263 else
267 AutoUpdateRegex();
268 const tr1::wsregex_iterator end;
269 wstring s = msg;
270 for (tr1::wsregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)
272 const tr1::wsmatch match = *it;
273 // we define group 1 as the whole issue text and
274 // group 2 as the bug ID
275 if (match.size() >= 2)
277 ATLTRACE(_T("matched id : %s\n"), wstring(match[1]).c_str());
278 CHARRANGE range = {(LONG)(match[1].first-s.begin()), (LONG)(match[1].second-s.begin())};
279 result.push_back(range);
283 catch (exception) {}
286 else if (result.empty() && (!sMessage.IsEmpty()))
288 CString sBugLine;
289 CString sFirstPart;
290 CString sLastPart;
291 BOOL bTop = FALSE;
292 if (nBugIdPos < 0)
293 return result;
295 sFirstPart = sMessage.Left(nBugIdPos);
296 sLastPart = sMessage.Mid(nBugIdPos + 7);
297 CString sMsg = msg;
298 sMsg.TrimRight('\n');
299 if (sMsg.ReverseFind('\n')>=0)
301 if (bAppend)
302 sBugLine = sMsg.Mid(sMsg.ReverseFind('\n')+1);
303 else
305 sBugLine = sMsg.Left(sMsg.Find('\n'));
306 bTop = TRUE;
309 else
310 sBugLine = sMsg;
311 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
312 sBugLine.Empty();
313 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
314 sBugLine.Empty();
315 if (sBugLine.IsEmpty())
317 if (sMsg.Find('\n')>=0)
318 sBugLine = sMsg.Left(sMsg.Find('\n'));
319 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
320 sBugLine.Empty();
321 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
322 sBugLine.Empty();
323 bTop = TRUE;
325 if (sBugLine.IsEmpty())
326 return result;
328 CString sBugIDPart = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
329 if (sBugIDPart.IsEmpty())
330 return result;
332 //the bug id part can contain several bug id's, separated by commas
333 if (!bTop)
334 offset1 = sMsg.GetLength() - sBugLine.GetLength() + sFirstPart.GetLength();
335 else
336 offset1 = sFirstPart.GetLength();
337 sBugIDPart.Trim(_T(","));
338 while (sBugIDPart.Find(',')>=0)
340 offset2 = offset1 + sBugIDPart.Find(',');
341 CHARRANGE range = {(LONG)offset1, (LONG)offset2};
342 result.push_back(range);
343 sBugIDPart = sBugIDPart.Mid(sBugIDPart.Find(',')+1);
344 offset1 = offset2 + 1;
346 offset2 = offset1 + sBugIDPart.GetLength();
347 CHARRANGE range = {(LONG)offset1, (LONG)offset2};
348 result.push_back(range);
351 return result;
354 BOOL ProjectProperties::FindBugID(const CString& msg, CWnd * pWnd)
356 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
357 CAppUtils::SetCharFormat(pWnd, CFM_LINK, CFE_LINK, positions);
359 return positions.empty() ? FALSE : TRUE;
362 std::set<CString> ProjectProperties::FindBugIDs (const CString& msg)
364 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
365 std::set<CString> bugIDs;
367 for (std::vector<CHARRANGE>::iterator iter = positions.begin(), end = positions.end(); iter != end; ++iter)
369 bugIDs.insert(msg.Mid(iter->cpMin, iter->cpMax - iter->cpMin));
372 return bugIDs;
375 CString ProjectProperties::FindBugID(const CString& msg)
377 CString sRet;
378 if (!sCheckRe.IsEmpty() || (nBugIdPos >= 0))
380 std::vector<CHARRANGE> positions = FindBugIDPositions(msg);
381 std::set<CString, num_compare> bugIDs;
382 for (std::vector<CHARRANGE>::iterator iter = positions.begin(), end = positions.end(); iter != end; ++iter)
384 bugIDs.insert(msg.Mid(iter->cpMin, iter->cpMax - iter->cpMin));
387 for (std::set<CString, num_compare>::iterator it = bugIDs.begin(); it != bugIDs.end(); ++it)
389 sRet += *it;
390 sRet += _T(" ");
392 sRet.Trim();
395 return sRet;
398 bool ProjectProperties::MightContainABugID()
400 return !sCheckRe.IsEmpty() || (nBugIdPos >= 0);
403 CString ProjectProperties::GetBugIDUrl(const CString& sBugID)
405 CString ret;
406 if (sUrl.IsEmpty())
407 return ret;
408 if (!sMessage.IsEmpty() || !sCheckRe.IsEmpty())
410 ret = sUrl;
411 ret.Replace(_T("%BUGID%"), sBugID);
413 return ret;
416 BOOL ProjectProperties::CheckBugID(const CString& sID)
418 if (bNumber)
420 // check if the revision actually _is_ a number
421 // or a list of numbers separated by colons
422 TCHAR c = 0;
423 int len = sID.GetLength();
424 for (int i=0; i<len; ++i)
426 c = sID.GetAt(i);
427 if ((c < '0')&&(c != ',')&&(c != ' '))
429 return FALSE;
431 if (c > '9')
432 return FALSE;
435 return TRUE;
438 BOOL ProjectProperties::HasBugID(const CString& sMessage)
440 if (!sCheckRe.IsEmpty())
444 AutoUpdateRegex();
445 return tr1::regex_search((LPCTSTR)sMessage, regCheck);
447 catch (exception) {}
449 return FALSE;
452 #ifdef DEBUG
453 static class PropTest
455 public:
456 PropTest()
458 CString msg = _T("this is a test logmessage: issue 222\nIssue #456, #678, 901 #456");
459 CString sUrl = _T("http://tortoisesvn.tigris.org/issues/show_bug.cgi?id=%BUGID%");
460 CString sCheckRe = _T("[Ii]ssue #?(\\d+)(,? ?#?(\\d+))+");
461 CString sBugIDRe = _T("(\\d+)");
462 ProjectProperties props;
463 props.sCheckRe = _T("PAF-[0-9]+");
464 props.sUrl = _T("http://tortoisesvn.tigris.org/issues/show_bug.cgi?id=%BUGID%");
465 CString sRet = props.FindBugID(_T("This is a test for PAF-88"));
466 ATLASSERT(sRet.IsEmpty());
467 props.sCheckRe = _T("[Ii]ssue #?(\\d+)");
468 props.regExNeedUpdate = true;
469 sRet = props.FindBugID(_T("Testing issue #99"));
470 sRet.Trim();
471 ATLASSERT(sRet.Compare(_T("99"))==0);
472 props.sCheckRe = _T("[Ii]ssues?:?(\\s*(,|and)?\\s*#\\d+)+");
473 props.sBugIDRe = _T("(\\d+)");
474 props.sUrl = _T("http://tortoisesvn.tigris.org/issues/show_bug.cgi?id=%BUGID%");
475 props.regExNeedUpdate = true;
476 sRet = props.FindBugID(_T("This is a test for Issue #7463,#666"));
477 ATLASSERT(props.HasBugID(_T("This is a test for Issue #7463,#666")));
478 ATLASSERT(!props.HasBugID(_T("This is a test for Issue 7463,666")));
479 sRet.Trim();
480 ATLASSERT(sRet.Compare(_T("666 7463"))==0);
481 sRet = props.FindBugID(_T("This is a test for Issue #850,#1234,#1345"));
482 sRet.Trim();
483 ATLASSERT(sRet.Compare(_T("850 1234 1345"))==0);
484 props.sCheckRe = _T("^\\[(\\d+)\\].*");
485 props.sUrl = _T("http://tortoisesvn.tigris.org/issues/show_bug.cgi?id=%BUGID%");
486 props.regExNeedUpdate = true;
487 sRet = props.FindBugID(_T("[000815] some stupid programming error fixed"));
488 sRet.Trim();
489 ATLASSERT(sRet.Compare(_T("000815"))==0);
490 props.sCheckRe = _T("\\[\\[(\\d+)\\]\\]\\]");
491 props.sUrl = _T("http://tortoisesvn.tigris.org/issues/show_bug.cgi?id=%BUGID%");
492 props.regExNeedUpdate = true;
493 sRet = props.FindBugID(_T("test test [[000815]]] some stupid programming error fixed"));
494 sRet.Trim();
495 ATLASSERT(sRet.Compare(_T("000815"))==0);
496 ATLASSERT(props.HasBugID(_T("test test [[000815]]] some stupid programming error fixed")));
497 ATLASSERT(!props.HasBugID(_T("test test [000815]] some stupid programming error fixed")));
499 } PropTest;
500 #endif