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.
20 #include "TortoiseProc.h"
21 #include "UnicodeUtils.h"
22 #include "ProjectProperties.h"
24 //#include "GitProperties.h"
33 bool operator() (const CString
& lhs
, const CString
& rhs
) const
35 return StrCmpLogicalW(lhs
, rhs
) < 0;
39 ProjectProperties::ProjectProperties(void)
40 : regExNeedUpdate (true)
44 bWarnIfNoIssue
= FALSE
;
48 bFileListInEnglish
= TRUE
;
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
]))
70 BOOL
ProjectProperties::GetStringProps(CString
&prop
,TCHAR
*key
,bool bRemoveCR
)
75 output
= g_Git
.GetConfigValue(key
,CP_UTF8
,NULL
, bRemoveCR
);
88 BOOL
ProjectProperties::GetBOOLProps(BOOL
&b
,TCHAR
*key
)
91 if(!GetStringProps(str
,key
))
94 low
= str
.MakeLower().Trim();
95 if(low
== _T("true") || low
== _T("on") || low
== _T("yes") || StrToInt(low
) != 0)
104 BOOL
ProjectProperties::ReadProps(CTGitPath path
)
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);
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();
132 CString
ProjectProperties::GetBugIDFromLog(CString
& msg
)
136 if (!sMessage
.IsEmpty())
144 sFirstPart
= sMessage
.Left(nBugIdPos
);
145 sLastPart
= sMessage
.Mid(nBugIdPos
+ 7);
147 if (msg
.ReverseFind('\n')>=0)
150 sBugLine
= msg
.Mid(msg
.ReverseFind('\n')+1);
153 sBugLine
= msg
.Left(msg
.Find('\n'));
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;
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)
183 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
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)
191 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
195 if (sBugLine
.IsEmpty())
197 sBugID
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
200 msg
= msg
.Mid(sBugLine
.GetLength());
205 msg
= msg
.Left(msg
.GetLength()-sBugLine
.GetLength());
212 void ProjectProperties::AutoUpdateRegex()
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
)
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!)
245 const tr1::wsregex_iterator end
;
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
);
268 const tr1::wsregex_iterator end
;
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
);
286 else if (result
.empty() && (!sMessage
.IsEmpty()))
295 sFirstPart
= sMessage
.Left(nBugIdPos
);
296 sLastPart
= sMessage
.Mid(nBugIdPos
+ 7);
298 sMsg
.TrimRight('\n');
299 if (sMsg
.ReverseFind('\n')>=0)
302 sBugLine
= sMsg
.Mid(sMsg
.ReverseFind('\n')+1);
305 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
311 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
313 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
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)
321 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
325 if (sBugLine
.IsEmpty())
328 CString sBugIDPart
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
329 if (sBugIDPart
.IsEmpty())
332 //the bug id part can contain several bug id's, separated by commas
334 offset1
= sMsg
.GetLength() - sBugLine
.GetLength() + sFirstPart
.GetLength();
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
);
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
));
375 CString
ProjectProperties::FindBugID(const CString
& msg
)
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
)
398 bool ProjectProperties::MightContainABugID()
400 return !sCheckRe
.IsEmpty() || (nBugIdPos
>= 0);
403 CString
ProjectProperties::GetBugIDUrl(const CString
& sBugID
)
408 if (!sMessage
.IsEmpty() || !sCheckRe
.IsEmpty())
411 ret
.Replace(_T("%BUGID%"), sBugID
);
416 BOOL
ProjectProperties::CheckBugID(const CString
& sID
)
420 // check if the revision actually _is_ a number
421 // or a list of numbers separated by colons
423 int len
= sID
.GetLength();
424 for (int i
=0; i
<len
; ++i
)
427 if ((c
< '0')&&(c
!= ',')&&(c
!= ' '))
438 BOOL
ProjectProperties::HasBugID(const CString
& sMessage
)
440 if (!sCheckRe
.IsEmpty())
445 return tr1::regex_search((LPCTSTR
)sMessage
, regCheck
);
453 static class 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"));
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")));
480 ATLASSERT(sRet
.Compare(_T("666 7463"))==0);
481 sRet
= props
.FindBugID(_T("This is a test for Issue #850,#1234,#1345"));
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"));
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"));
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")));