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.
20 #include "ProjectProperties.h"
21 #include "CommonAppUtils.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
)
39 , bWarnIfNoIssue(FALSE
)
42 , bFileListInEnglish(TRUE
)
48 int ProjectProperties::ReadProps()
50 CAutoConfig
gitconfig(true);
51 CAutoRepository
repo(g_Git
.GetGitRepository());
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
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
);
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
);
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)
107 nLogWidthMarker
= _wtoi(val
);
110 if (gitconfig
.GetString(PROJECTPROPNAME_PROJECTLANGUAGE
, sPropVal
) == 0)
115 lProjectLanguage
= -1;
119 lProjectLanguage
= wcstol(val
, &strEnd
, 0);
123 if (gitconfig
.GetString(PROJECTPROPNAME_LOGMINSIZE
, sPropVal
) == 0)
128 nMinLogSize
= _wtoi(val
);
134 CString
ProjectProperties::GetBugIDFromLog(CString
& msg
)
138 if (!sMessage
.IsEmpty())
146 sFirstPart
= sMessage
.Left(nBugIdPos
);
147 sLastPart
= sMessage
.Mid(nBugIdPos
+ 7);
149 if (msg
.ReverseFind('\n')>=0)
152 sBugLine
= msg
.Mid(msg
.ReverseFind('\n')+1);
155 sBugLine
= msg
.Left(msg
.Find('\n'));
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;
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)
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
= std::wregex(sCheckRe
);
219 regBugID
= std::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())
240 // match with two regex strings (without grouping!)
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
&) {}
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()))
294 sFirstPart
= sMessage
.Left(nBugIdPos
);
295 sLastPart
= sMessage
.Mid(nBugIdPos
+ 7);
297 sMsg
.TrimRight('\n');
298 if (sMsg
.ReverseFind('\n')>=0)
301 sBugLine
= sMsg
.Mid(sMsg
.ReverseFind('\n')+1);
304 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
310 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
312 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
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)
320 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
324 if (sBugLine
.IsEmpty())
327 CString sBugIDPart
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
328 if (sBugIDPart
.IsEmpty())
331 //the bug id part can contain several bug id's, separated by commas
333 offset1
= sMsg
.GetLength() - sBugLine
.GetLength() + sFirstPart
.GetLength();
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
);
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
));
372 CString
ProjectProperties::FindBugID(const CString
& msg
)
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
)
393 bool ProjectProperties::MightContainABugID()
395 return !sCheckRe
.IsEmpty() || (nBugIdPos
>= 0);
398 CString
ProjectProperties::GetBugIDUrl(const CString
& sBugID
)
403 if (!sMessage
.IsEmpty() || !sCheckRe
.IsEmpty())
406 ret
.Replace(L
"%BUGID%", sBugID
);
411 BOOL
ProjectProperties::CheckBugID(const CString
& sID
)
415 // check if the revision actually _is_ a number
416 // or a list of numbers separated by colons
418 int len
= sID
.GetLength();
419 for (int i
=0; i
<len
; ++i
)
422 if ((c
< '0')&&(c
!= ',')&&(c
!= ' '))
431 BOOL
ProjectProperties::HasBugID(const CString
& sMsg
)
433 if (!sCheckRe
.IsEmpty())
438 return std::regex_search((LPCTSTR
)sMsg
, regCheck
);
440 catch (std::exception
&) {}