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"
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 output
= g_Git
.GetConfigValue(key
,CP_UTF8
,NULL
, bRemoveCR
);
78 BOOL
ProjectProperties::GetBOOLProps(BOOL
&b
,TCHAR
*key
)
81 if(!GetStringProps(str
,key
))
93 BOOL
ProjectProperties::ReadProps(CTGitPath path
)
97 GetStringProps(this->sLabel
,BUGTRAQPROPNAME_LABEL
);
98 GetStringProps(this->sMessage
,BUGTRAQPROPNAME_MESSAGE
);
99 GetStringProps(this->sUrl
,BUGTRAQPROPNAME_URL
);
101 GetBOOLProps(this->bWarnIfNoIssue
,BUGTRAQPROPNAME_WARNIFNOISSUE
);
102 GetBOOLProps(this->bNumber
,BUGTRAQPROPNAME_NUMBER
);
103 GetBOOLProps(this->bAppend
,BUGTRAQPROPNAME_APPEND
);
105 GetStringProps(sPropVal
,BUGTRAQPROPNAME_LOGREGEX
,false);
108 if (sCheckRe
.Find('\n')>=0)
110 sBugIDRe
= sCheckRe
.Mid(sCheckRe
.Find('\n')).Trim();
111 sCheckRe
= sCheckRe
.Left(sCheckRe
.Find('\n')).Trim();
113 if (!sCheckRe
.IsEmpty())
115 sCheckRe
= sCheckRe
.Trim();
121 BOOL bFoundBugtraqLabel
= FALSE
;
122 BOOL bFoundBugtraqMessage
= FALSE
;
123 BOOL bFoundBugtraqNumber
= FALSE
;
124 BOOL bFoundBugtraqLogRe
= FALSE
;
125 BOOL bFoundBugtraqURL
= FALSE
;
126 BOOL bFoundBugtraqWarnIssue
= FALSE
;
127 BOOL bFoundBugtraqAppend
= FALSE
;
128 BOOL bFoundLogWidth
= FALSE
;
129 BOOL bFoundLogTemplate
= FALSE
;
130 BOOL bFoundMinLogSize
= FALSE
;
131 BOOL bFoundMinLockMsgSize
= FALSE
;
132 BOOL bFoundFileListEnglish
= FALSE
;
133 BOOL bFoundProjectLanguage
= FALSE
;
134 BOOL bFoundUserFileProps
= FALSE
;
135 BOOL bFoundUserDirProps
= FALSE
;
136 BOOL bFoundWebViewRev
= FALSE
;
137 BOOL bFoundWebViewPathRev
= FALSE
;
138 BOOL bFoundAutoProps
= FALSE
;
139 BOOL bFoundLogSummary
= FALSE
;
141 if (!path
.IsDirectory())
142 path
= path
.GetContainingDirectory();
146 GitProperties
props(path
, GitRev::REV_WC
, false);
147 for (int i
=0; i
<props
.GetCount(); ++i
)
149 CString sPropName
= props
.GetItemName(i
).c_str();
150 CString sPropVal
= CUnicodeUtils::GetUnicode(((char *)props
.GetItemValue(i
).c_str()));
151 if ((!bFoundBugtraqLabel
)&&(sPropName
.Compare(BUGTRAQPROPNAME_LABEL
)==0))
154 bFoundBugtraqLabel
= TRUE
;
156 if ((!bFoundBugtraqMessage
)&&(sPropName
.Compare(BUGTRAQPROPNAME_MESSAGE
)==0))
159 bFoundBugtraqMessage
= TRUE
;
161 if ((!bFoundBugtraqNumber
)&&(sPropName
.Compare(BUGTRAQPROPNAME_NUMBER
)==0))
165 val
= val
.Trim(_T(" \n\r\t"));
166 if ((val
.CompareNoCase(_T("false"))==0)||(val
.CompareNoCase(_T("no"))==0))
170 bFoundBugtraqNumber
= TRUE
;
172 if ((!bFoundBugtraqLogRe
)&&(sPropName
.Compare(BUGTRAQPROPNAME_LOGREGEX
)==0))
175 if (sCheckRe
.Find('\n')>=0)
177 sBugIDRe
= sCheckRe
.Mid(sCheckRe
.Find('\n')).Trim();
178 sCheckRe
= sCheckRe
.Left(sCheckRe
.Find('\n')).Trim();
180 if (!sCheckRe
.IsEmpty())
182 sCheckRe
= sCheckRe
.Trim();
184 bFoundBugtraqLogRe
= TRUE
;
186 if ((!bFoundBugtraqURL
)&&(sPropName
.Compare(BUGTRAQPROPNAME_URL
)==0))
189 bFoundBugtraqURL
= TRUE
;
191 if ((!bFoundBugtraqWarnIssue
)&&(sPropName
.Compare(BUGTRAQPROPNAME_WARNIFNOISSUE
)==0))
195 val
= val
.Trim(_T(" \n\r\t"));
196 if ((val
.CompareNoCase(_T("true"))==0)||(val
.CompareNoCase(_T("yes"))==0))
197 bWarnIfNoIssue
= TRUE
;
199 bWarnIfNoIssue
= FALSE
;
200 bFoundBugtraqWarnIssue
= TRUE
;
202 if ((!bFoundBugtraqAppend
)&&(sPropName
.Compare(BUGTRAQPROPNAME_APPEND
)==0))
206 val
= val
.Trim(_T(" \n\r\t"));
207 if ((val
.CompareNoCase(_T("true"))==0)||(val
.CompareNoCase(_T("yes"))==0))
211 bFoundBugtraqAppend
= TRUE
;
213 if ((!bFoundLogWidth
)&&(sPropName
.Compare(PROJECTPROPNAME_LOGWIDTHLINE
)==0))
219 nLogWidthMarker
= _ttoi(val
);
221 bFoundLogWidth
= TRUE
;
223 if ((!bFoundLogTemplate
)&&(sPropName
.Compare(PROJECTPROPNAME_LOGTEMPLATE
)==0))
225 sLogTemplate
= sPropVal
;
226 sLogTemplate
.Remove('\r');
227 sLogTemplate
.Replace(_T("\n"), _T("\r\n"));
228 bFoundLogTemplate
= TRUE
;
230 if ((!bFoundMinLogSize
)&&(sPropName
.Compare(PROJECTPROPNAME_LOGMINSIZE
)==0))
236 nMinLogSize
= _ttoi(val
);
238 bFoundMinLogSize
= TRUE
;
240 if ((!bFoundMinLockMsgSize
)&&(sPropName
.Compare(PROJECTPROPNAME_LOCKMSGMINSIZE
)==0))
246 nMinLockMsgSize
= _ttoi(val
);
248 bFoundMinLockMsgSize
= TRUE
;
250 if ((!bFoundFileListEnglish
)&&(sPropName
.Compare(PROJECTPROPNAME_LOGFILELISTLANG
)==0))
254 val
= val
.Trim(_T(" \n\r\t"));
255 if ((val
.CompareNoCase(_T("false"))==0)||(val
.CompareNoCase(_T("no"))==0))
256 bFileListInEnglish
= TRUE
;
258 bFileListInEnglish
= FALSE
;
259 bFoundFileListEnglish
= TRUE
;
261 if ((!bFoundProjectLanguage
)&&(sPropName
.Compare(PROJECTPROPNAME_PROJECTLANGUAGE
)==0))
268 lProjectLanguage
= _tcstol(val
, &strEnd
, 0);
270 bFoundProjectLanguage
= TRUE
;
272 if ((!bFoundUserFileProps
)&&(sPropName
.Compare(PROJECTPROPNAME_USERFILEPROPERTY
)==0))
275 sFPPath
.Replace(_T("\r\n"), _T("\n"));
276 bFoundUserFileProps
= TRUE
;
278 if ((!bFoundUserDirProps
)&&(sPropName
.Compare(PROJECTPROPNAME_USERDIRPROPERTY
)==0))
281 sDPPath
.Replace(_T("\r\n"), _T("\n"));
282 bFoundUserDirProps
= TRUE
;
284 if ((!bFoundAutoProps
)&&(sPropName
.Compare(PROJECTPROPNAME_AUTOPROPS
)==0))
286 sAutoProps
= sPropVal
;
287 sAutoProps
.Replace(_T("\r\n"), _T("\n"));
288 bFoundAutoProps
= TRUE
;
290 if ((!bFoundWebViewRev
)&&(sPropName
.Compare(PROJECTPROPNAME_WEBVIEWER_REV
)==0))
292 sWebViewerRev
= sPropVal
;
293 bFoundWebViewRev
= TRUE
;
295 if ((!bFoundWebViewPathRev
)&&(sPropName
.Compare(PROJECTPROPNAME_WEBVIEWER_PATHREV
)==0))
297 sWebViewerPathRev
= sPropVal
;
298 bFoundWebViewPathRev
= TRUE
;
300 if ((!bFoundLogSummary
)&&(sPropName
.Compare(PROJECTPROPNAME_LOGSUMMARY
)==0))
302 sLogSummaryRe
= sPropVal
;
303 bFoundLogSummary
= TRUE
;
306 if (PathIsRoot(path
.GetWinPath()))
309 path
= path
.GetContainingDirectory();
310 if ((!path
.HasAdminDir())||(path
.IsEmpty()))
312 if (bFoundBugtraqLabel
| bFoundBugtraqMessage
| bFoundBugtraqNumber
313 | bFoundBugtraqURL
| bFoundBugtraqWarnIssue
| bFoundLogWidth
314 | bFoundLogTemplate
| bFoundBugtraqLogRe
| bFoundMinLockMsgSize
315 | bFoundUserFileProps
| bFoundUserDirProps
| bFoundAutoProps
316 | bFoundWebViewRev
| bFoundWebViewPathRev
| bFoundLogSummary
)
325 return FALSE
; //never reached
328 CString
ProjectProperties::GetBugIDFromLog(CString
& msg
)
332 if (!sMessage
.IsEmpty())
338 if (sMessage
.Find(_T("%BUGID%"))<0)
340 sFirstPart
= sMessage
.Left(sMessage
.Find(_T("%BUGID%")));
341 sLastPart
= sMessage
.Mid(sMessage
.Find(_T("%BUGID%"))+7);
343 if (msg
.ReverseFind('\n')>=0)
346 sBugLine
= msg
.Mid(msg
.ReverseFind('\n')+1);
349 sBugLine
= msg
.Left(msg
.Find('\n'));
357 // find out if the message consists only of numbers
358 bool bOnlyNumbers
= true;
359 for (int i
=0; i
<msg
.GetLength(); ++i
)
361 if (!_istdigit(msg
[i
]))
363 bOnlyNumbers
= false;
373 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
375 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
377 if (sBugLine
.IsEmpty())
379 if (msg
.Find('\n')>=0)
380 sBugLine
= msg
.Left(msg
.Find('\n'));
381 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
383 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
387 if (sBugLine
.IsEmpty())
389 sBugID
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
392 msg
= msg
.Mid(sBugLine
.GetLength());
397 msg
= msg
.Left(msg
.GetLength()-sBugLine
.GetLength());
404 BOOL
ProjectProperties::FindBugID(const CString
& msg
, CWnd
* pWnd
)
413 // first use the checkre string to find bug ID's in the message
414 if (!sCheckRe
.IsEmpty())
416 if (!sBugIDRe
.IsEmpty())
419 // match with two regex strings (without grouping!)
422 const tr1::wregex
regCheck(sCheckRe
);
423 const tr1::wregex
regBugID(sBugIDRe
);
424 const tr1::wsregex_iterator end
;
426 for (tr1::wsregex_iterator
it(s
.begin(), s
.end(), regCheck
); it
!= end
; ++it
)
428 // (*it)[0] is the matched string
429 wstring matchedString
= (*it
)[0];
430 ptrdiff_t matchpos
= it
->position(0);
431 for (tr1::wsregex_iterator
it2(matchedString
.begin(), matchedString
.end(), regBugID
); it2
!= end
; ++it2
)
433 ATLTRACE(_T("matched id : %s\n"), (*it2
)[0].str().c_str());
434 ptrdiff_t matchposID
= it2
->position(0);
435 CHARRANGE range
= {(LONG
)(matchpos
+matchposID
), (LONG
)(matchpos
+matchposID
+(*it2
)[0].str().size())};
436 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
438 SecureZeroMemory(&format
, sizeof(CHARFORMAT2
));
439 format
.cbSize
= sizeof(CHARFORMAT2
);
440 format
.dwMask
= CFM_LINK
;
441 format
.dwEffects
= CFE_LINK
;
442 pWnd
->SendMessage(EM_SETCHARFORMAT
, SCF_SELECTION
, (LPARAM
)&format
);
453 const tr1::wregex
regCheck(sCheckRe
);
454 const tr1::wsregex_iterator end
;
456 for (tr1::wsregex_iterator
it(s
.begin(), s
.end(), regCheck
); it
!= end
; ++it
)
458 const tr1::wsmatch match
= *it
;
459 // we define group 1 as the whole issue text and
460 // group 2 as the bug ID
461 if (match
.size() >= 2)
463 ATLTRACE(_T("matched id : %s\n"), wstring(match
[1]).c_str());
464 CHARRANGE range
= {(LONG
)(match
[1].first
-s
.begin()), (LONG
)(match
[1].second
-s
.begin())};
465 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
467 SecureZeroMemory(&format
, sizeof(CHARFORMAT2
));
468 format
.cbSize
= sizeof(CHARFORMAT2
);
469 format
.dwMask
= CFM_LINK
;
470 format
.dwEffects
= CFE_LINK
;
471 pWnd
->SendMessage(EM_SETCHARFORMAT
, SCF_SELECTION
, (LPARAM
)&format
);
479 else if ((!bFound
)&&(!sMessage
.IsEmpty()))
485 if (sMessage
.Find(_T("%BUGID%"))<0)
487 sFirstPart
= sMessage
.Left(sMessage
.Find(_T("%BUGID%")));
488 sLastPart
= sMessage
.Mid(sMessage
.Find(_T("%BUGID%"))+7);
490 sMsg
.TrimRight('\n');
491 if (sMsg
.ReverseFind('\n')>=0)
494 sBugLine
= sMsg
.Mid(sMsg
.ReverseFind('\n')+1);
497 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
503 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
505 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
507 if (sBugLine
.IsEmpty())
509 if (sMsg
.Find('\n')>=0)
510 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
511 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
513 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
517 if (sBugLine
.IsEmpty())
519 CString sBugIDPart
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
520 if (sBugIDPart
.IsEmpty())
522 //the bug id part can contain several bug id's, separated by commas
524 offset1
= sMsg
.GetLength() - sBugLine
.GetLength() + sFirstPart
.GetLength();
526 offset1
= sFirstPart
.GetLength();
527 sBugIDPart
.Trim(_T(","));
528 while (sBugIDPart
.Find(',')>=0)
530 offset2
= offset1
+ sBugIDPart
.Find(',');
531 CHARRANGE range
= {(LONG
)offset1
, (LONG
)offset2
};
532 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
534 SecureZeroMemory(&format
, sizeof(CHARFORMAT2
));
535 format
.cbSize
= sizeof(CHARFORMAT2
);
536 format
.dwMask
= CFM_LINK
;
537 format
.dwEffects
= CFE_LINK
;
538 pWnd
->SendMessage(EM_SETCHARFORMAT
, SCF_SELECTION
, (LPARAM
)&format
);
539 sBugIDPart
= sBugIDPart
.Mid(sBugIDPart
.Find(',')+1);
540 offset1
= offset2
+ 1;
542 offset2
= offset1
+ sBugIDPart
.GetLength();
543 CHARRANGE range
= {(LONG
)offset1
, (LONG
)offset2
};
544 pWnd
->SendMessage(EM_EXSETSEL
, NULL
, (LPARAM
)&range
);
546 SecureZeroMemory(&format
, sizeof(CHARFORMAT2
));
547 format
.cbSize
= sizeof(CHARFORMAT2
);
548 format
.dwMask
= CFM_LINK
;
549 format
.dwEffects
= CFE_LINK
;
550 pWnd
->SendMessage(EM_SETCHARFORMAT
, SCF_SELECTION
, (LPARAM
)&format
);
556 std::set
<CString
> ProjectProperties::FindBugIDs(const CString
& msg
)
561 std::set
<CString
> bugIDs
;
563 // first use the checkre string to find bug ID's in the message
564 if (!sCheckRe
.IsEmpty())
566 if (!sBugIDRe
.IsEmpty())
568 // match with two regex strings (without grouping!)
571 const tr1::wregex
regCheck(sCheckRe
);
572 const tr1::wregex
regBugID(sBugIDRe
);
573 const tr1::wsregex_iterator end
;
575 for (tr1::wsregex_iterator
it(s
.begin(), s
.end(), regCheck
); it
!= end
; ++it
)
577 // (*it)[0] is the matched string
578 wstring matchedString
= (*it
)[0];
579 for (tr1::wsregex_iterator
it2(matchedString
.begin(), matchedString
.end(), regBugID
); it2
!= end
; ++it2
)
581 ATLTRACE(_T("matched id : %s\n"), (*it2
)[0].str().c_str());
582 bugIDs
.insert(CString((*it2
)[0].str().c_str()));
592 const tr1::wregex
regCheck(sCheckRe
);
593 const tr1::wsregex_iterator end
;
595 for (tr1::wsregex_iterator
it(s
.begin(), s
.end(), regCheck
); it
!= end
; ++it
)
597 const tr1::wsmatch match
= *it
;
598 // we define group 1 as the whole issue text and
599 // group 2 as the bug ID
600 if (match
.size() >= 2)
602 ATLTRACE(_T("matched id : %s\n"), wstring(match
[1]).c_str());
603 bugIDs
.insert(CString(wstring(match
[1]).c_str()));
610 else if ((!bFound
)&&(!sMessage
.IsEmpty()))
616 if (sMessage
.Find(_T("%BUGID%"))<0)
618 sFirstPart
= sMessage
.Left(sMessage
.Find(_T("%BUGID%")));
619 sLastPart
= sMessage
.Mid(sMessage
.Find(_T("%BUGID%"))+7);
621 sMsg
.TrimRight('\n');
622 if (sMsg
.ReverseFind('\n')>=0)
625 sBugLine
= sMsg
.Mid(sMsg
.ReverseFind('\n')+1);
628 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
634 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
636 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
638 if (sBugLine
.IsEmpty())
640 if (sMsg
.Find('\n')>=0)
641 sBugLine
= sMsg
.Left(sMsg
.Find('\n'));
642 if (sBugLine
.Left(sFirstPart
.GetLength()).Compare(sFirstPart
)!=0)
644 if (sBugLine
.Right(sLastPart
.GetLength()).Compare(sLastPart
)!=0)
648 if (sBugLine
.IsEmpty())
650 CString sBugIDPart
= sBugLine
.Mid(sFirstPart
.GetLength(), sBugLine
.GetLength() - sFirstPart
.GetLength() - sLastPart
.GetLength());
651 if (sBugIDPart
.IsEmpty())
653 //the bug id part can contain several bug id's, separated by commas
655 offset1
= sMsg
.GetLength() - sBugLine
.GetLength() + sFirstPart
.GetLength();
657 offset1
= sFirstPart
.GetLength();
658 sBugIDPart
.Trim(_T(","));
659 while (sBugIDPart
.Find(',')>=0)
661 offset2
= offset1
+ sBugIDPart
.Find(',');
662 CHARRANGE range
= {(LONG
)offset1
, (LONG
)offset2
};
663 bugIDs
.insert(msg
.Mid(range
.cpMin
, range
.cpMax
-range
.cpMin
));
664 sBugIDPart
= sBugIDPart
.Mid(sBugIDPart
.Find(',')+1);
665 offset1
= offset2
+ 1;
667 offset2
= offset1
+ sBugIDPart
.GetLength();
668 CHARRANGE range
= {(LONG
)offset1
, (LONG
)offset2
};
669 bugIDs
.insert(msg
.Mid(range
.cpMin
, range
.cpMax
-range
.cpMin
));
675 CString
ProjectProperties::FindBugID(const CString
& msg
)
679 std::set
<CString
> bugIDs
= FindBugIDs(msg
);
681 for (std::set
<CString
>::iterator it
= bugIDs
.begin(); it
!= bugIDs
.end(); ++it
)
690 CString
ProjectProperties::GetBugIDUrl(const CString
& sBugID
)
695 if (!sMessage
.IsEmpty() || !sCheckRe
.IsEmpty())
698 ret
.Replace(_T("%BUGID%"), sBugID
);
703 BOOL
ProjectProperties::CheckBugID(const CString
& sID
)
705 if (!sCheckRe
.IsEmpty()&&(!bNumber
)&&!sID
.IsEmpty())
707 CString sBugID
= sID
;
708 sBugID
.Replace(_T(", "), _T(","));
709 sBugID
.Replace(_T(" ,"), _T(","));
710 CString sMsg
= sMessage
;
711 sMsg
.Replace(_T("%BUGID%"), sBugID
);
712 return HasBugID(sMsg
);
716 // check if the revision actually _is_ a number
717 // or a list of numbers separated by colons
719 int len
= sID
.GetLength();
720 for (int i
=0; i
<len
; ++i
)
723 if ((c
< '0')&&(c
!= ','))
734 BOOL
ProjectProperties::HasBugID(const CString
& sMessage
)
736 if (!sCheckRe
.IsEmpty())
740 tr1::wregex
rx(sCheckRe
);
741 return tr1::regex_search((LPCTSTR
)sMessage
, rx
);
748 void ProjectProperties::InsertAutoProps(Git_config_t
*cfg
)
750 // every line is an autoprop
751 CString sPropsData
= sAutoProps
;
752 bool bEnableAutoProps
= false;
753 while (!sPropsData
.IsEmpty())
755 int pos
= sPropsData
.Find('\n');
756 CString sLine
= pos
>= 0 ? sPropsData
.Left(pos
) : sPropsData
;
758 if (!sLine
.IsEmpty())
760 // format is '*.something = property=value;property=value;....'
761 // find first '=' char
762 int equalpos
= sLine
.Find('=');
763 if ((equalpos
>= 0)&&(sLine
[0] != '#'))
765 CString key
= sLine
.Left(equalpos
);
766 CString value
= sLine
.Mid(equalpos
);
768 value
.Trim(_T(" ="));
769 Git_config_set(cfg
, Git_CONFIG_SECTION_AUTO_PROPS
, (LPCSTR
)CUnicodeUtils::GetUTF8(key
), (LPCSTR
)CUnicodeUtils::GetUTF8(value
));
770 bEnableAutoProps
= true;
774 sPropsData
= sPropsData
.Mid(pos
).Trim();
778 if (bEnableAutoProps
)
779 Git_config_set(cfg
, Git_CONFIG_SECTION_MISCELLANY
, Git_CONFIG_OPTION_ENABLE_AUTO_PROPS
, "yes");
783 bool ProjectProperties::AddAutoProps(const CTGitPath
& path
)
785 if (!path
.IsDirectory())
786 return true; // no error, but nothing to do
791 char buf
[1024] = {0};
792 GitProperties
props(path
, GitRev::REV_WC
, false);
793 if (!sLabel
.IsEmpty())
794 bRet
= props
.Add(BUGTRAQPROPNAME_LABEL
, WideToMultibyte((LPCTSTR
)sLabel
)) && bRet
;
795 if (!sMessage
.IsEmpty())
796 bRet
= props
.Add(BUGTRAQPROPNAME_MESSAGE
, WideToMultibyte((LPCTSTR
)sMessage
)) && bRet
;
798 bRet
= props
.Add(BUGTRAQPROPNAME_NUMBER
, "false") && bRet
;
799 if (!sCheckRe
.IsEmpty())
800 bRet
= props
.Add(BUGTRAQPROPNAME_LOGREGEX
, WideToMultibyte((LPCTSTR
)(sCheckRe
+ _T("\n") + sBugIDRe
))) && bRet
;
802 bRet
= props
.Add(BUGTRAQPROPNAME_URL
, WideToMultibyte((LPCTSTR
)sUrl
)) && bRet
;
804 bRet
= props
.Add(BUGTRAQPROPNAME_WARNIFNOISSUE
, "true") && bRet
;
806 bRet
= props
.Add(BUGTRAQPROPNAME_APPEND
, "false") && bRet
;
809 sprintf_s(buf
, sizeof(buf
), "%ld", nLogWidthMarker
);
810 bRet
= props
.Add(PROJECTPROPNAME_LOGWIDTHLINE
, buf
) && bRet
;
812 if (!sLogTemplate
.IsEmpty())
813 bRet
= props
.Add(PROJECTPROPNAME_LOGTEMPLATE
, WideToMultibyte((LPCTSTR
)sLogTemplate
)) && bRet
;
816 sprintf_s(buf
, sizeof(buf
), "%ld", nMinLogSize
);
817 bRet
= props
.Add(PROJECTPROPNAME_LOGMINSIZE
, buf
) && bRet
;
821 sprintf_s(buf
, sizeof(buf
), "%ld", nMinLockMsgSize
);
822 bRet
= props
.Add(PROJECTPROPNAME_LOCKMSGMINSIZE
, buf
) && bRet
;
824 if (!bFileListInEnglish
)
825 bRet
= props
.Add(PROJECTPROPNAME_LOGFILELISTLANG
, "false") && bRet
;
826 if (lProjectLanguage
)
828 sprintf_s(buf
, sizeof(buf
), "%ld", lProjectLanguage
);
829 bRet
= props
.Add(PROJECTPROPNAME_PROJECTLANGUAGE
, buf
) && bRet
;
831 if (!sFPPath
.IsEmpty())
832 bRet
= props
.Add(PROJECTPROPNAME_USERFILEPROPERTY
, WideToMultibyte((LPCTSTR
)sFPPath
)) && bRet
;
833 if (!sDPPath
.IsEmpty())
834 bRet
= props
.Add(PROJECTPROPNAME_USERDIRPROPERTY
, WideToMultibyte((LPCTSTR
)sDPPath
)) && bRet
;
835 if (!sWebViewerRev
.IsEmpty())
836 bRet
= props
.Add(PROJECTPROPNAME_WEBVIEWER_REV
, WideToMultibyte((LPCTSTR
)sWebViewerRev
)) && bRet
;
837 if (!sWebViewerPathRev
.IsEmpty())
838 bRet
= props
.Add(PROJECTPROPNAME_WEBVIEWER_PATHREV
, WideToMultibyte((LPCTSTR
)sWebViewerPathRev
)) && bRet
;
839 if (!sAutoProps
.IsEmpty())
840 bRet
= props
.Add(PROJECTPROPNAME_AUTOPROPS
, WideToMultibyte((LPCTSTR
)sAutoProps
)) && bRet
;
845 CString
ProjectProperties::GetLogSummary(const CString
& sMessage
)
849 if (!sLogSummaryRe
.IsEmpty())
853 const tr1::wregex
regSum(sLogSummaryRe
);
854 const tr1::wsregex_iterator end
;
855 wstring s
= sMessage
;
856 for (tr1::wsregex_iterator
it(s
.begin(), s
.end(), regSum
); it
!= end
; ++it
)
858 const tr1::wsmatch match
= *it
;
859 // we define the first group as the summary text
860 if ((*it
).size() >= 1)
862 ATLTRACE(_T("matched summary : %s\n"), wstring(match
[0]).c_str());
863 sRet
+= (CString(wstring(match
[1]).c_str()));
874 CString
ProjectProperties::MakeShortMessage(const CString
& message
)
876 bool bFoundShort
= true;
877 CString sShortMessage
= GetLogSummary(message
);
878 if (sShortMessage
.IsEmpty())
881 sShortMessage
= message
;
883 // Remove newlines and tabs 'cause those are not shown nicely in the list control
884 sShortMessage
.Remove('\r');
885 sShortMessage
.Replace(_T('\t'), _T(' '));
887 // Suppose the first empty line separates 'summary' from the rest of the message.
888 int found
= sShortMessage
.Find(_T("\n\n"));
889 // To avoid too short 'short' messages
890 // (e.g. if the message looks something like "Bugfix:\n\n*done this\n*done that")
891 // only use the empty newline as a separator if it comes after at least 15 chars.
892 if ((!bFoundShort
)&&(found
>= 15))
894 sShortMessage
= sShortMessage
.Left(found
);
896 sShortMessage
.Replace('\n', ' ');
897 return sShortMessage
;
901 static class PropTest
906 CString msg
= _T("this is a test logmessage: issue 222\nIssue #456, #678, 901 #456");
907 CString sUrl
= _T("http://tortoiseGit.tigris.org/issues/show_bug.cgi?id=%BUGID%");
908 CString sCheckRe
= _T("[Ii]ssue #?(\\d+)(,? ?#?(\\d+))+");
909 CString sBugIDRe
= _T("(\\d+)");
910 ProjectProperties props
;
911 props
.sCheckRe
= _T("PAF-[0-9]+");
912 props
.sUrl
= _T("http://tortoiseGit.tigris.org/issues/show_bug.cgi?id=%BUGID%");
913 CString sRet
= props
.FindBugID(_T("This is a test for PAF-88"));
914 ATLASSERT(sRet
.IsEmpty());
915 props
.sCheckRe
= _T("[Ii]ssue #?(\\d+)");
916 sRet
= props
.FindBugID(_T("Testing issue #99"));
918 ATLASSERT(sRet
.Compare(_T("99"))==0);
919 props
.sCheckRe
= _T("[Ii]ssues?:?(\\s*(,|and)?\\s*#\\d+)+");
920 props
.sBugIDRe
= _T("(\\d+)");
921 props
.sUrl
= _T("http://tortoiseGit.tigris.org/issues/show_bug.cgi?id=%BUGID%");
922 sRet
= props
.FindBugID(_T("This is a test for Issue #7463,#666"));
923 ATLASSERT(props
.HasBugID(_T("This is a test for Issue #7463,#666")));
924 ATLASSERT(!props
.HasBugID(_T("This is a test for Issue 7463,666")));
926 ATLASSERT(sRet
.Compare(_T("666 7463"))==0);
927 props
.sCheckRe
= _T("^\\[(\\d+)\\].*");
928 props
.sUrl
= _T("http://tortoiseGit.tigris.org/issues/show_bug.cgi?id=%BUGID%");
929 sRet
= props
.FindBugID(_T("[000815] some stupid programming error fixed"));
931 ATLASSERT(sRet
.Compare(_T("000815"))==0);
932 props
.sCheckRe
= _T("\\[\\[(\\d+)\\]\\]\\]");
933 props
.sUrl
= _T("http://tortoiseGit.tigris.org/issues/show_bug.cgi?id=%BUGID%");
934 sRet
= props
.FindBugID(_T("test test [[000815]]] some stupid programming error fixed"));
936 ATLASSERT(sRet
.Compare(_T("000815"))==0);
937 ATLASSERT(props
.HasBugID(_T("test test [[000815]]] some stupid programming error fixed")));
938 ATLASSERT(!props
.HasBugID(_T("test test [000815]] some stupid programming error fixed")));
939 props
.sLogSummaryRe
= _T("\\[SUMMARY\\]:(.*)");
940 ATLASSERT(props
.GetLogSummary(_T("[SUMMARY]: this is my summary")).Compare(_T("this is my summary"))==0);