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