1 // TortoiseSVN - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - 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"
23 //#include "GitProperties.h"
31 ProjectProperties::ProjectProperties(void)
34 bWarnIfNoIssue
= FALSE
;
38 bFileListInEnglish
= TRUE
;
43 ProjectProperties::~ProjectProperties(void)
48 BOOL
ProjectProperties::ReadPropsPathList(const CTGitPathList
& pathList
)
50 for(int nPath
= 0; nPath
< pathList
.GetCount(); nPath
++)
52 if (ReadProps(pathList
[nPath
]))
60 BOOL
ProjectProperties::GetStringProps(CString
&prop
,TCHAR
*key
,bool bRemoveCR
)
65 cmd
.Format(_T("git.exe config %s"),key
);
67 if(g_Git
.Run(cmd
,&output
,CP_ACP
))
72 prop
= output
.Tokenize(_T("\n"),start
);
80 BOOL
ProjectProperties::GetBOOLProps(BOOL
&b
,TCHAR
*key
)
83 if(!GetStringProps(str
,key
))
95 BOOL
ProjectProperties::ReadProps(CTGitPath path
)
99 GetStringProps(this->sLabel
,BUGTRAQPROPNAME_LABEL
);
100 GetStringProps(this->sMessage
,BUGTRAQPROPNAME_MESSAGE
);
101 GetStringProps(this->sUrl
,BUGTRAQPROPNAME_URL
);
103 GetBOOLProps(this->bWarnIfNoIssue
,BUGTRAQPROPNAME_WARNIFNOISSUE
);
104 GetBOOLProps(this->bNumber
,BUGTRAQPROPNAME_NUMBER
);
105 GetBOOLProps(this->bAppend
,BUGTRAQPROPNAME_APPEND
);
107 GetStringProps(sPropVal
,BUGTRAQPROPNAME_LOGREGEX
,false);
110 if (sCheckRe
.Find('\n')>=0)
112 sBugIDRe
= sCheckRe
.Mid(sCheckRe
.Find('\n')).Trim();
113 sCheckRe
= sCheckRe
.Left(sCheckRe
.Find('\n')).Trim();
115 if (!sCheckRe
.IsEmpty())
117 sCheckRe
= sCheckRe
.Trim();
123 BOOL bFoundBugtraqLabel
= FALSE
;
124 BOOL bFoundBugtraqMessage
= FALSE
;
125 BOOL bFoundBugtraqNumber
= FALSE
;
126 BOOL bFoundBugtraqLogRe
= FALSE
;
127 BOOL bFoundBugtraqURL
= FALSE
;
128 BOOL bFoundBugtraqWarnIssue
= FALSE
;
129 BOOL bFoundBugtraqAppend
= FALSE
;
130 BOOL bFoundLogWidth
= FALSE
;
131 BOOL bFoundLogTemplate
= FALSE
;
132 BOOL bFoundMinLogSize
= FALSE
;
133 BOOL bFoundMinLockMsgSize
= FALSE
;
134 BOOL bFoundFileListEnglish
= FALSE
;
135 BOOL bFoundProjectLanguage
= FALSE
;
136 BOOL bFoundUserFileProps
= FALSE
;
137 BOOL bFoundUserDirProps
= FALSE
;
138 BOOL bFoundWebViewRev
= FALSE
;
139 BOOL bFoundWebViewPathRev
= FALSE
;
140 BOOL bFoundAutoProps
= FALSE
;
141 BOOL bFoundLogSummary
= FALSE
;
143 if (!path
.IsDirectory())
144 path
= path
.GetContainingDirectory();
148 GitProperties
props(path
, GitRev::REV_WC
, false);
149 for (int i
=0; i
<props
.GetCount(); ++i
)
151 CString sPropName
= props
.GetItemName(i
).c_str();
152 CString sPropVal
= CUnicodeUtils::GetUnicode(((char *)props
.GetItemValue(i
).c_str()));
153 if ((!bFoundBugtraqLabel
)&&(sPropName
.Compare(BUGTRAQPROPNAME_LABEL
)==0))
156 bFoundBugtraqLabel
= TRUE
;
158 if ((!bFoundBugtraqMessage
)&&(sPropName
.Compare(BUGTRAQPROPNAME_MESSAGE
)==0))
161 bFoundBugtraqMessage
= TRUE
;
163 if ((!bFoundBugtraqNumber
)&&(sPropName
.Compare(BUGTRAQPROPNAME_NUMBER
)==0))
167 val
= val
.Trim(_T(" \n\r\t"));
168 if ((val
.CompareNoCase(_T("false"))==0)||(val
.CompareNoCase(_T("no"))==0))
172 bFoundBugtraqNumber
= TRUE
;
174 if ((!bFoundBugtraqLogRe
)&&(sPropName
.Compare(BUGTRAQPROPNAME_LOGREGEX
)==0))
177 if (sCheckRe
.Find('\n')>=0)
179 sBugIDRe
= sCheckRe
.Mid(sCheckRe
.Find('\n')).Trim();
180 sCheckRe
= sCheckRe
.Left(sCheckRe
.Find('\n')).Trim();
182 if (!sCheckRe
.IsEmpty())
184 sCheckRe
= sCheckRe
.Trim();
186 bFoundBugtraqLogRe
= TRUE
;
188 if ((!bFoundBugtraqURL
)&&(sPropName
.Compare(BUGTRAQPROPNAME_URL
)==0))
191 bFoundBugtraqURL
= TRUE
;
193 if ((!bFoundBugtraqWarnIssue
)&&(sPropName
.Compare(BUGTRAQPROPNAME_WARNIFNOISSUE
)==0))
197 val
= val
.Trim(_T(" \n\r\t"));
198 if ((val
.CompareNoCase(_T("true"))==0)||(val
.CompareNoCase(_T("yes"))==0))
199 bWarnIfNoIssue
= TRUE
;
201 bWarnIfNoIssue
= FALSE
;
202 bFoundBugtraqWarnIssue
= TRUE
;
204 if ((!bFoundBugtraqAppend
)&&(sPropName
.Compare(BUGTRAQPROPNAME_APPEND
)==0))
208 val
= val
.Trim(_T(" \n\r\t"));
209 if ((val
.CompareNoCase(_T("true"))==0)||(val
.CompareNoCase(_T("yes"))==0))
213 bFoundBugtraqAppend
= TRUE
;
215 if ((!bFoundLogWidth
)&&(sPropName
.Compare(PROJECTPROPNAME_LOGWIDTHLINE
)==0))
221 nLogWidthMarker
= _ttoi(val
);
223 bFoundLogWidth
= TRUE
;
225 if ((!bFoundLogTemplate
)&&(sPropName
.Compare(PROJECTPROPNAME_LOGTEMPLATE
)==0))
227 sLogTemplate
= sPropVal
;
228 sLogTemplate
.Replace(_T("\r"), _T(""));
229 sLogTemplate
.Replace(_T("\n"), _T("\r\n"));
230 bFoundLogTemplate
= TRUE
;
232 if ((!bFoundMinLogSize
)&&(sPropName
.Compare(PROJECTPROPNAME_LOGMINSIZE
)==0))
238 nMinLogSize
= _ttoi(val
);
240 bFoundMinLogSize
= TRUE
;
242 if ((!bFoundMinLockMsgSize
)&&(sPropName
.Compare(PROJECTPROPNAME_LOCKMSGMINSIZE
)==0))
248 nMinLockMsgSize
= _ttoi(val
);
250 bFoundMinLockMsgSize
= TRUE
;
252 if ((!bFoundFileListEnglish
)&&(sPropName
.Compare(PROJECTPROPNAME_LOGFILELISTLANG
)==0))
256 val
= val
.Trim(_T(" \n\r\t"));
257 if ((val
.CompareNoCase(_T("false"))==0)||(val
.CompareNoCase(_T("no"))==0))
258 bFileListInEnglish
= TRUE
;
260 bFileListInEnglish
= FALSE
;
261 bFoundFileListEnglish
= TRUE
;
263 if ((!bFoundProjectLanguage
)&&(sPropName
.Compare(PROJECTPROPNAME_PROJECTLANGUAGE
)==0))
270 lProjectLanguage
= _tcstol(val
, &strEnd
, 0);
272 bFoundProjectLanguage
= TRUE
;
274 if ((!bFoundUserFileProps
)&&(sPropName
.Compare(PROJECTPROPNAME_USERFILEPROPERTY
)==0))
277 sFPPath
.Replace(_T("\r\n"), _T("\n"));
278 bFoundUserFileProps
= TRUE
;
280 if ((!bFoundUserDirProps
)&&(sPropName
.Compare(PROJECTPROPNAME_USERDIRPROPERTY
)==0))
283 sDPPath
.Replace(_T("\r\n"), _T("\n"));
284 bFoundUserDirProps
= TRUE
;
286 if ((!bFoundAutoProps
)&&(sPropName
.Compare(PROJECTPROPNAME_AUTOPROPS
)==0))
288 sAutoProps
= sPropVal
;
289 sAutoProps
.Replace(_T("\r\n"), _T("\n"));
290 bFoundAutoProps
= TRUE
;
292 if ((!bFoundWebViewRev
)&&(sPropName
.Compare(PROJECTPROPNAME_WEBVIEWER_REV
)==0))
294 sWebViewerRev
= sPropVal
;
295 bFoundWebViewRev
= TRUE
;
297 if ((!bFoundWebViewPathRev
)&&(sPropName
.Compare(PROJECTPROPNAME_WEBVIEWER_PATHREV
)==0))
299 sWebViewerPathRev
= sPropVal
;
300 bFoundWebViewPathRev
= TRUE
;
302 if ((!bFoundLogSummary
)&&(sPropName
.Compare(PROJECTPROPNAME_LOGSUMMARY
)==0))
304 sLogSummaryRe
= sPropVal
;
305 bFoundLogSummary
= TRUE
;
308 if (PathIsRoot(path
.GetWinPath()))
311 path
= path
.GetContainingDirectory();
312 if ((!path
.HasAdminDir())||(path
.IsEmpty()))
314 if (bFoundBugtraqLabel
| bFoundBugtraqMessage
| bFoundBugtraqNumber
315 | bFoundBugtraqURL
| bFoundBugtraqWarnIssue
| bFoundLogWidth
316 | bFoundLogTemplate
| bFoundBugtraqLogRe
| bFoundMinLockMsgSize
317 | bFoundUserFileProps
| bFoundUserDirProps
| bFoundAutoProps
318 | bFoundWebViewRev
| bFoundWebViewPathRev
| bFoundLogSummary
)
327 return FALSE
; //never reached
330 CString
ProjectProperties::GetBugIDFromLog(CString
& msg
)
334 if (!sMessage
.IsEmpty())
340 if (sMessage
.Find(_T("%BUGID%"))<0)
342 sFirstPart
= sMessage
.Left(sMessage
.Find(_T("%BUGID%")));
343 sLastPart
= sMessage
.Mid(sMessage
.Find(_T("%BUGID%"))+7);
345 if (msg
.ReverseFind('\n')>=0)
348 sBugLine
= msg
.Mid(msg
.ReverseFind('\n')+1);
351 sBugLine
= msg
.Left(msg
.Find('\n'));
359 // find out if the message consists only of numbers
360 bool bOnlyNumbers
= true;
361 for (int i
=0; i
<msg
.GetLength(); ++i
)
363 if (!_istdigit(msg
[i
]))
365 bOnlyNumbers
= false;
375 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
377 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
379 if (sBugLine
.IsEmpty())
381 if (msg
.Find('\n')>=0)
382 sBugLine
= msg
.Left(msg
.Find('\n'));
383 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
385 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
389 if (sBugLine
.IsEmpty())
391 sBugID
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
394 msg
= msg
.Mid(sBugLine
.GetLength());
399 msg
= msg
.Left(msg
.GetLength()-sBugLine
.GetLength());
406 BOOL
ProjectProperties::FindBugID(const CString
& msg
, CWnd
* pWnd
)
415 // first use the checkre string to find bug ID's in the message
416 if (!sCheckRe
.IsEmpty())
418 if (!sBugIDRe
.IsEmpty())
421 // match with two regex strings (without grouping!)
424 const tr1::wregex
regCheck(sCheckRe
);
425 const tr1::wregex
regBugID(sBugIDRe
);
426 const tr1::wsregex_iterator end
;
428 for (tr1::wsregex_iterator
it(s
.begin(), s
.end(), regCheck
); it
!= end
; ++it
)
430 // (*it)[0] is the matched string
431 wstring matchedString
= (*it
)[0];
432 ptrdiff_t matchpos
= it
->position(0);
433 for (tr1::wsregex_iterator
it2(matchedString
.begin(), matchedString
.end(), regBugID
); it2
!= end
; ++it2
)
435 ATLTRACE(_T("matched id : %s\n"), (*it2
)[0].str().c_str());
436 ptrdiff_t matchposID
= it2
->position(0);
437 CHARRANGE range
= {(LONG
)(matchpos
+matchposID
), (LONG
)(matchpos
+matchposID
+(*it2
)[0].str().size())};
438 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
440 SecureZeroMemory(&format
, sizeof(CHARFORMAT2
));
441 format
.cbSize
= sizeof(CHARFORMAT2
);
442 format
.dwMask
= CFM_LINK
;
443 format
.dwEffects
= CFE_LINK
;
444 pWnd
->SendMessage(EM_SETCHARFORMAT
, SCF_SELECTION
, (LPARAM
)&format
);
455 const tr1::wregex
regCheck(sCheckRe
);
456 const tr1::wsregex_iterator end
;
458 for (tr1::wsregex_iterator
it(s
.begin(), s
.end(), regCheck
); it
!= end
; ++it
)
460 const tr1::wsmatch match
= *it
;
461 // we define group 1 as the whole issue text and
462 // group 2 as the bug ID
463 if (match
.size() >= 2)
465 ATLTRACE(_T("matched id : %s\n"), wstring(match
[1]).c_str());
466 CHARRANGE range
= {(LONG
)(match
[1].first
-s
.begin()), (LONG
)(match
[1].second
-s
.begin())};
467 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
469 SecureZeroMemory(&format
, sizeof(CHARFORMAT2
));
470 format
.cbSize
= sizeof(CHARFORMAT2
);
471 format
.dwMask
= CFM_LINK
;
472 format
.dwEffects
= CFE_LINK
;
473 pWnd
->SendMessage(EM_SETCHARFORMAT
, SCF_SELECTION
, (LPARAM
)&format
);
481 else if ((!bFound
)&&(!sMessage
.IsEmpty()))
487 if (sMessage
.Find(_T("%BUGID%"))<0)
489 sFirstPart
= sMessage
.Left(sMessage
.Find(_T("%BUGID%")));
490 sLastPart
= sMessage
.Mid(sMessage
.Find(_T("%BUGID%"))+7);
492 sMsg
.TrimRight('\n');
493 if (sMsg
.ReverseFind('\n')>=0)
496 sBugLine
= sMsg
.Mid(sMsg
.ReverseFind('\n')+1);
499 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
505 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
507 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
509 if (sBugLine
.IsEmpty())
511 if (sMsg
.Find('\n')>=0)
512 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
513 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
515 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
519 if (sBugLine
.IsEmpty())
521 CString sBugIDPart
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
522 if (sBugIDPart
.IsEmpty())
524 //the bug id part can contain several bug id's, separated by commas
526 offset1
= sMsg
.GetLength() - sBugLine
.GetLength() + sFirstPart
.GetLength();
528 offset1
= sFirstPart
.GetLength();
529 sBugIDPart
.Trim(_T(","));
530 while (sBugIDPart
.Find(',')>=0)
532 offset2
= offset1
+ sBugIDPart
.Find(',');
533 CHARRANGE range
= {(LONG
)offset1
, (LONG
)offset2
};
534 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
536 SecureZeroMemory(&format
, sizeof(CHARFORMAT2
));
537 format
.cbSize
= sizeof(CHARFORMAT2
);
538 format
.dwMask
= CFM_LINK
;
539 format
.dwEffects
= CFE_LINK
;
540 pWnd
->SendMessage(EM_SETCHARFORMAT
, SCF_SELECTION
, (LPARAM
)&format
);
541 sBugIDPart
= sBugIDPart
.Mid(sBugIDPart
.Find(',')+1);
542 offset1
= offset2
+ 1;
544 offset2
= offset1
+ sBugIDPart
.GetLength();
545 CHARRANGE range
= {(LONG
)offset1
, (LONG
)offset2
};
546 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
548 SecureZeroMemory(&format
, sizeof(CHARFORMAT2
));
549 format
.cbSize
= sizeof(CHARFORMAT2
);
550 format
.dwMask
= CFM_LINK
;
551 format
.dwEffects
= CFE_LINK
;
552 pWnd
->SendMessage(EM_SETCHARFORMAT
, SCF_SELECTION
, (LPARAM
)&format
);
558 std::set
<CString
> ProjectProperties::FindBugIDs(const CString
& msg
)
563 std::set
<CString
> bugIDs
;
565 // first use the checkre string to find bug ID's in the message
566 if (!sCheckRe
.IsEmpty())
568 if (!sBugIDRe
.IsEmpty())
570 // match with two regex strings (without grouping!)
573 const tr1::wregex
regCheck(sCheckRe
);
574 const tr1::wregex
regBugID(sBugIDRe
);
575 const tr1::wsregex_iterator end
;
577 for (tr1::wsregex_iterator
it(s
.begin(), s
.end(), regCheck
); it
!= end
; ++it
)
579 // (*it)[0] is the matched string
580 wstring matchedString
= (*it
)[0];
581 for (tr1::wsregex_iterator
it2(matchedString
.begin(), matchedString
.end(), regBugID
); it2
!= end
; ++it2
)
583 ATLTRACE(_T("matched id : %s\n"), (*it2
)[0].str().c_str());
584 bugIDs
.insert(CString((*it2
)[0].str().c_str()));
594 const tr1::wregex
regCheck(sCheckRe
);
595 const tr1::wsregex_iterator end
;
597 for (tr1::wsregex_iterator
it(s
.begin(), s
.end(), regCheck
); it
!= end
; ++it
)
599 const tr1::wsmatch match
= *it
;
600 // we define group 1 as the whole issue text and
601 // group 2 as the bug ID
602 if (match
.size() >= 2)
604 ATLTRACE(_T("matched id : %s\n"), wstring(match
[1]).c_str());
605 bugIDs
.insert(CString(wstring(match
[1]).c_str()));
612 else if ((!bFound
)&&(!sMessage
.IsEmpty()))
618 if (sMessage
.Find(_T("%BUGID%"))<0)
620 sFirstPart
= sMessage
.Left(sMessage
.Find(_T("%BUGID%")));
621 sLastPart
= sMessage
.Mid(sMessage
.Find(_T("%BUGID%"))+7);
623 sMsg
.TrimRight('\n');
624 if (sMsg
.ReverseFind('\n')>=0)
627 sBugLine
= sMsg
.Mid(sMsg
.ReverseFind('\n')+1);
630 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
636 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
638 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
640 if (sBugLine
.IsEmpty())
642 if (sMsg
.Find('\n')>=0)
643 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
644 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
646 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
650 if (sBugLine
.IsEmpty())
652 CString sBugIDPart
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
653 if (sBugIDPart
.IsEmpty())
655 //the bug id part can contain several bug id's, separated by commas
657 offset1
= sMsg
.GetLength() - sBugLine
.GetLength() + sFirstPart
.GetLength();
659 offset1
= sFirstPart
.GetLength();
660 sBugIDPart
.Trim(_T(","));
661 while (sBugIDPart
.Find(',')>=0)
663 offset2
= offset1
+ sBugIDPart
.Find(',');
664 CHARRANGE range
= {(LONG
)offset1
, (LONG
)offset2
};
665 bugIDs
.insert(msg
.Mid(range
.cpMin
, range
.cpMax
-range
.cpMin
));
666 sBugIDPart
= sBugIDPart
.Mid(sBugIDPart
.Find(',')+1);
667 offset1
= offset2
+ 1;
669 offset2
= offset1
+ sBugIDPart
.GetLength();
670 CHARRANGE range
= {(LONG
)offset1
, (LONG
)offset2
};
671 bugIDs
.insert(msg
.Mid(range
.cpMin
, range
.cpMax
-range
.cpMin
));
677 CString
ProjectProperties::FindBugID(const CString
& msg
)
681 std::set
<CString
> bugIDs
= FindBugIDs(msg
);
683 for (std::set
<CString
>::iterator it
= bugIDs
.begin(); it
!= bugIDs
.end(); ++it
)
692 CString
ProjectProperties::GetBugIDUrl(const CString
& sBugID
)
697 if (!sMessage
.IsEmpty() || !sCheckRe
.IsEmpty())
700 ret
.Replace(_T("%BUGID%"), sBugID
);
705 BOOL
ProjectProperties::CheckBugID(const CString
& sID
)
707 if (!sCheckRe
.IsEmpty()&&(!bNumber
)&&!sID
.IsEmpty())
709 CString sBugID
= sID
;
710 sBugID
.Replace(_T(", "), _T(","));
711 sBugID
.Replace(_T(" ,"), _T(","));
712 CString sMsg
= sMessage
;
713 sMsg
.Replace(_T("%BUGID%"), sBugID
);
714 return HasBugID(sMsg
);
718 // check if the revision actually _is_ a number
719 // or a list of numbers separated by colons
721 int len
= sID
.GetLength();
722 for (int i
=0; i
<len
; ++i
)
725 if ((c
< '0')&&(c
!= ','))
736 BOOL
ProjectProperties::HasBugID(const CString
& sMessage
)
738 if (!sCheckRe
.IsEmpty())
742 tr1::wregex
rx(sCheckRe
);
743 return tr1::regex_search((LPCTSTR
)sMessage
, rx
);
750 void ProjectProperties::InsertAutoProps(Git_config_t
*cfg
)
752 // every line is an autoprop
753 CString sPropsData
= sAutoProps
;
754 bool bEnableAutoProps
= false;
755 while (!sPropsData
.IsEmpty())
757 int pos
= sPropsData
.Find('\n');
758 CString sLine
= pos
>= 0 ? sPropsData
.Left(pos
) : sPropsData
;
760 if (!sLine
.IsEmpty())
762 // format is '*.something = property=value;property=value;....'
763 // find first '=' char
764 int equalpos
= sLine
.Find('=');
765 if ((equalpos
>= 0)&&(sLine
[0] != '#'))
767 CString key
= sLine
.Left(equalpos
);
768 CString value
= sLine
.Mid(equalpos
);
770 value
.Trim(_T(" ="));
771 Git_config_set(cfg
, Git_CONFIG_SECTION_AUTO_PROPS
, (LPCSTR
)CUnicodeUtils::GetUTF8(key
), (LPCSTR
)CUnicodeUtils::GetUTF8(value
));
772 bEnableAutoProps
= true;
776 sPropsData
= sPropsData
.Mid(pos
).Trim();
780 if (bEnableAutoProps
)
781 Git_config_set(cfg
, Git_CONFIG_SECTION_MISCELLANY
, Git_CONFIG_OPTION_ENABLE_AUTO_PROPS
, "yes");
785 bool ProjectProperties::AddAutoProps(const CTGitPath
& path
)
787 if (!path
.IsDirectory())
788 return true; // no error, but nothing to do
792 char buf
[1024] = {0};
794 GitProperties
props(path
, GitRev::REV_WC
, false);
795 if (!sLabel
.IsEmpty())
796 bRet
= props
.Add(BUGTRAQPROPNAME_LABEL
, WideToMultibyte((LPCTSTR
)sLabel
)) && bRet
;
797 if (!sMessage
.IsEmpty())
798 bRet
= props
.Add(BUGTRAQPROPNAME_MESSAGE
, WideToMultibyte((LPCTSTR
)sMessage
)) && bRet
;
800 bRet
= props
.Add(BUGTRAQPROPNAME_NUMBER
, "false") && bRet
;
801 if (!sCheckRe
.IsEmpty())
802 bRet
= props
.Add(BUGTRAQPROPNAME_LOGREGEX
, WideToMultibyte((LPCTSTR
)(sCheckRe
+ _T("\n") + sBugIDRe
))) && bRet
;
804 bRet
= props
.Add(BUGTRAQPROPNAME_URL
, WideToMultibyte((LPCTSTR
)sUrl
)) && bRet
;
806 bRet
= props
.Add(BUGTRAQPROPNAME_WARNIFNOISSUE
, "true") && bRet
;
808 bRet
= props
.Add(BUGTRAQPROPNAME_APPEND
, "false") && bRet
;
811 sprintf_s(buf
, sizeof(buf
), "%ld", nLogWidthMarker
);
812 bRet
= props
.Add(PROJECTPROPNAME_LOGWIDTHLINE
, buf
) && bRet
;
814 if (!sLogTemplate
.IsEmpty())
815 bRet
= props
.Add(PROJECTPROPNAME_LOGTEMPLATE
, WideToMultibyte((LPCTSTR
)sLogTemplate
)) && bRet
;
818 sprintf_s(buf
, sizeof(buf
), "%ld", nMinLogSize
);
819 bRet
= props
.Add(PROJECTPROPNAME_LOGMINSIZE
, buf
) && bRet
;
823 sprintf_s(buf
, sizeof(buf
), "%ld", nMinLockMsgSize
);
824 bRet
= props
.Add(PROJECTPROPNAME_LOCKMSGMINSIZE
, buf
) && bRet
;
826 if (!bFileListInEnglish
)
827 bRet
= props
.Add(PROJECTPROPNAME_LOGFILELISTLANG
, "false") && bRet
;
828 if (lProjectLanguage
)
830 sprintf_s(buf
, sizeof(buf
), "%ld", lProjectLanguage
);
831 bRet
= props
.Add(PROJECTPROPNAME_PROJECTLANGUAGE
, buf
) && bRet
;
833 if (!sFPPath
.IsEmpty())
834 bRet
= props
.Add(PROJECTPROPNAME_USERFILEPROPERTY
, WideToMultibyte((LPCTSTR
)sFPPath
)) && bRet
;
835 if (!sDPPath
.IsEmpty())
836 bRet
= props
.Add(PROJECTPROPNAME_USERDIRPROPERTY
, WideToMultibyte((LPCTSTR
)sDPPath
)) && bRet
;
837 if (!sWebViewerRev
.IsEmpty())
838 bRet
= props
.Add(PROJECTPROPNAME_WEBVIEWER_REV
, WideToMultibyte((LPCTSTR
)sWebViewerRev
)) && bRet
;
839 if (!sWebViewerPathRev
.IsEmpty())
840 bRet
= props
.Add(PROJECTPROPNAME_WEBVIEWER_PATHREV
, WideToMultibyte((LPCTSTR
)sWebViewerPathRev
)) && bRet
;
841 if (!sAutoProps
.IsEmpty())
842 bRet
= props
.Add(PROJECTPROPNAME_AUTOPROPS
, WideToMultibyte((LPCTSTR
)sAutoProps
)) && bRet
;
847 CString
ProjectProperties::GetLogSummary(const CString
& sMessage
)
851 if (!sLogSummaryRe
.IsEmpty())
855 const tr1::wregex
regSum(sLogSummaryRe
);
856 const tr1::wsregex_iterator end
;
857 wstring s
= sMessage
;
858 for (tr1::wsregex_iterator
it(s
.begin(), s
.end(), regSum
); it
!= end
; ++it
)
860 const tr1::wsmatch match
= *it
;
861 // we define the first group as the summary text
862 if ((*it
).size() >= 1)
864 ATLTRACE(_T("matched summary : %s\n"), wstring(match
[0]).c_str());
865 sRet
+= (CString(wstring(match
[1]).c_str()));
876 CString
ProjectProperties::MakeShortMessage(const CString
& message
)
878 bool bFoundShort
= true;
879 CString sShortMessage
= GetLogSummary(message
);
880 if (sShortMessage
.IsEmpty())
883 sShortMessage
= message
;
885 // Remove newlines and tabs 'cause those are not shown nicely in the list control
886 sShortMessage
.Replace(_T("\r"), _T(""));
887 sShortMessage
.Replace(_T("\t"), _T(" "));
889 // Suppose the first empty line separates 'summary' from the rest of the message.
890 int found
= sShortMessage
.Find(_T("\n\n"));
891 // To avoid too short 'short' messages
892 // (e.g. if the message looks something like "Bugfix:\n\n*done this\n*done that")
893 // only use the empty newline as a separator if it comes after at least 15 chars.
894 if ((!bFoundShort
)&&(found
>= 15))
896 sShortMessage
= sShortMessage
.Left(found
);
898 sShortMessage
.Replace('\n', ' ');
899 return sShortMessage
;
903 static class PropTest
908 CString msg
= _T("this is a test logmessage: issue 222\nIssue #456, #678, 901 #456");
909 CString sUrl
= _T("http://tortoiseGit.tigris.org/issues/show_bug.cgi?id=%BUGID%");
910 CString sCheckRe
= _T("[Ii]ssue #?(\\d+)(,? ?#?(\\d+))+");
911 CString sBugIDRe
= _T("(\\d+)");
912 ProjectProperties props
;
913 props
.sCheckRe
= _T("PAF-[0-9]+");
914 props
.sUrl
= _T("http://tortoiseGit.tigris.org/issues/show_bug.cgi?id=%BUGID%");
915 CString sRet
= props
.FindBugID(_T("This is a test for PAF-88"));
916 ATLASSERT(sRet
.IsEmpty());
917 props
.sCheckRe
= _T("[Ii]ssue #?(\\d+)");
918 sRet
= props
.FindBugID(_T("Testing issue #99"));
920 ATLASSERT(sRet
.Compare(_T("99"))==0);
921 props
.sCheckRe
= _T("[Ii]ssues?:?(\\s*(,|and)?\\s*#\\d+)+");
922 props
.sBugIDRe
= _T("(\\d+)");
923 props
.sUrl
= _T("http://tortoiseGit.tigris.org/issues/show_bug.cgi?id=%BUGID%");
924 sRet
= props
.FindBugID(_T("This is a test for Issue #7463,#666"));
925 ATLASSERT(props
.HasBugID(_T("This is a test for Issue #7463,#666")));
926 ATLASSERT(!props
.HasBugID(_T("This is a test for Issue 7463,666")));
928 ATLASSERT(sRet
.Compare(_T("666 7463"))==0);
929 props
.sCheckRe
= _T("^\\[(\\d+)\\].*");
930 props
.sUrl
= _T("http://tortoiseGit.tigris.org/issues/show_bug.cgi?id=%BUGID%");
931 sRet
= props
.FindBugID(_T("[000815] some stupid programming error fixed"));
933 ATLASSERT(sRet
.Compare(_T("000815"))==0);
934 props
.sCheckRe
= _T("\\[\\[(\\d+)\\]\\]\\]");
935 props
.sUrl
= _T("http://tortoiseGit.tigris.org/issues/show_bug.cgi?id=%BUGID%");
936 sRet
= props
.FindBugID(_T("test test [[000815]]] some stupid programming error fixed"));
938 ATLASSERT(sRet
.Compare(_T("000815"))==0);
939 ATLASSERT(props
.HasBugID(_T("test test [[000815]]] some stupid programming error fixed")));
940 ATLASSERT(!props
.HasBugID(_T("test test [000815]] some stupid programming error fixed")));
941 props
.sLogSummaryRe
= _T("\\[SUMMARY\\]:(.*)");
942 ATLASSERT(props
.GetLogSummary(_T("[SUMMARY]: this is my summary")).Compare(_T("this is my summary"))==0);