1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2011,2013-2014 - 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 "ProjectProperties.h"
23 #include "UnicodeUtils.h"
28 bool operator() (const CString
& lhs
, const CString
& rhs
) const
30 return StrCmpLogicalW(lhs
, rhs
) < 0;
34 ProjectProperties::ProjectProperties(void)
35 : regExNeedUpdate (true)
37 , bWarnNoSignedOffBy(FALSE
)
40 bWarnIfNoIssue
= FALSE
;
43 bFileListInEnglish
= TRUE
;
48 int ProjectProperties::ReadProps()
50 CAutoConfig
gitconfig(true);
52 if (g_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 (!g_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
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
);
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
);
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)
106 nLogWidthMarker
= _ttoi(val
) + 2; // HACK, + 2 needed
109 if (gitconfig
.GetString(PROJECTPROPNAME_PROJECTLANGUAGE
, sPropVal
) == 0)
114 lProjectLanguage
= -1;
118 lProjectLanguage
= _tcstol(val
, &strEnd
, 0);
122 if (gitconfig
.GetString(PROJECTPROPNAME_LOGMINSIZE
, sPropVal
) == 0)
127 nMinLogSize
= _ttoi(val
);
133 CString
ProjectProperties::GetBugIDFromLog(CString
& msg
)
137 if (!sMessage
.IsEmpty())
145 sFirstPart
= sMessage
.Left(nBugIdPos
);
146 sLastPart
= sMessage
.Mid(nBugIdPos
+ 7);
148 if (msg
.ReverseFind('\n')>=0)
151 sBugLine
= msg
.Mid(msg
.ReverseFind('\n')+1);
154 sBugLine
= msg
.Left(msg
.Find('\n'));
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;
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)
184 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
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)
192 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
196 if (sBugLine
.IsEmpty())
198 sBugID
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
201 msg
= msg
.Mid(sBugLine
.GetLength());
206 msg
= msg
.Left(msg
.GetLength()-sBugLine
.GetLength());
213 void ProjectProperties::AutoUpdateRegex()
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
)
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!)
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
) {}
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()))
296 sFirstPart
= sMessage
.Left(nBugIdPos
);
297 sLastPart
= sMessage
.Mid(nBugIdPos
+ 7);
299 sMsg
.TrimRight('\n');
300 if (sMsg
.ReverseFind('\n')>=0)
303 sBugLine
= sMsg
.Mid(sMsg
.ReverseFind('\n')+1);
306 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
312 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
314 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
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)
322 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
326 if (sBugLine
.IsEmpty())
329 CString sBugIDPart
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
330 if (sBugIDPart
.IsEmpty())
333 //the bug id part can contain several bug id's, separated by commas
335 offset1
= sMsg
.GetLength() - sBugLine
.GetLength() + sFirstPart
.GetLength();
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
);
355 BOOL
ProjectProperties::FindBugID(const CString
& msg
, CWnd
* pWnd
)
357 std::vector
<CHARRANGE
> positions
= FindBugIDPositions(msg
);
358 CAppUtils::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
));
376 CString
ProjectProperties::FindBugID(const CString
& msg
)
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
)
399 bool ProjectProperties::MightContainABugID()
401 return !sCheckRe
.IsEmpty() || (nBugIdPos
>= 0);
404 CString
ProjectProperties::GetBugIDUrl(const CString
& sBugID
)
409 if (!sMessage
.IsEmpty() || !sCheckRe
.IsEmpty())
412 ret
.Replace(_T("%BUGID%"), sBugID
);
417 BOOL
ProjectProperties::CheckBugID(const CString
& sID
)
421 // check if the revision actually _is_ a number
422 // or a list of numbers separated by colons
424 int len
= sID
.GetLength();
425 for (int i
=0; i
<len
; ++i
)
428 if ((c
< '0')&&(c
!= ',')&&(c
!= ' '))
439 BOOL
ProjectProperties::HasBugID(const CString
& sMessage
)
441 if (!sCheckRe
.IsEmpty())
446 return std::tr1::regex_search((LPCTSTR
)sMessage
, regCheck
);
448 catch (std::exception
) {}
454 static class PropTest
459 CString msg
= _T("this is a test logmessage: issue 222\nIssue #456, #678, 901 #456");
460 CString sUrl
= _T("http://tortoisesvn.tigris.org/issues/show_bug.cgi?id=%BUGID%");
461 CString sCheckRe
= _T("[Ii]ssue #?(\\d+)(,? ?#?(\\d+))+");
462 CString sBugIDRe
= _T("(\\d+)");
463 ProjectProperties props
;
464 props
.sCheckRe
= _T("PAF-[0-9]+");
465 props
.sUrl
= _T("http://tortoisesvn.tigris.org/issues/show_bug.cgi?id=%BUGID%");
466 CString sRet
= props
.FindBugID(_T("This is a test for PAF-88"));
467 ATLASSERT(sRet
.IsEmpty());
468 props
.sCheckRe
= _T("[Ii]ssue #?(\\d+)");
469 props
.regExNeedUpdate
= true;
470 sRet
= props
.FindBugID(_T("Testing issue #99"));
472 ATLASSERT(sRet
.Compare(_T("99"))==0);
473 props
.sCheckRe
= _T("[Ii]ssues?:?(\\s*(,|and)?\\s*#\\d+)+");
474 props
.sBugIDRe
= _T("(\\d+)");
475 props
.sUrl
= _T("http://tortoisesvn.tigris.org/issues/show_bug.cgi?id=%BUGID%");
476 props
.regExNeedUpdate
= true;
477 sRet
= props
.FindBugID(_T("This is a test for Issue #7463,#666"));
478 ATLASSERT(props
.HasBugID(_T("This is a test for Issue #7463,#666")));
479 ATLASSERT(!props
.HasBugID(_T("This is a test for Issue 7463,666")));
481 ATLASSERT(sRet
.Compare(_T("666 7463"))==0);
482 sRet
= props
.FindBugID(_T("This is a test for Issue #850,#1234,#1345"));
484 ATLASSERT(sRet
.Compare(_T("850 1234 1345"))==0);
485 props
.sCheckRe
= _T("^\\[(\\d+)\\].*");
486 props
.sUrl
= _T("http://tortoisesvn.tigris.org/issues/show_bug.cgi?id=%BUGID%");
487 props
.regExNeedUpdate
= true;
488 sRet
= props
.FindBugID(_T("[000815] some stupid programming error fixed"));
490 ATLASSERT(sRet
.Compare(_T("000815"))==0);
491 props
.sCheckRe
= _T("\\[\\[(\\d+)\\]\\]\\]");
492 props
.sUrl
= _T("http://tortoisesvn.tigris.org/issues/show_bug.cgi?id=%BUGID%");
493 props
.regExNeedUpdate
= true;
494 sRet
= props
.FindBugID(_T("test test [[000815]]] some stupid programming error fixed"));
496 ATLASSERT(sRet
.Compare(_T("000815"))==0);
497 ATLASSERT(props
.HasBugID(_T("test test [[000815]]] some stupid programming error fixed")));
498 ATLASSERT(!props
.HasBugID(_T("test test [000815]] some stupid programming error fixed")));