Update diff del rename ignore document.
[TortoiseGit.git] / src / TortoiseProc / ProjectProperties.cpp
blob3cf783083b012b117389716e6049808e46efc1d6
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.
19 #include "StdAfx.h"
20 #include "TortoiseProc.h"
21 #include "UnicodeUtils.h"
22 #include "ProjectProperties.h"
23 //#include "GitProperties.h"
24 #include "TGitPath.h"
25 #include <regex>
26 #include "git.h"
28 using namespace std;
31 ProjectProperties::ProjectProperties(void)
33 bNumber = TRUE;
34 bWarnIfNoIssue = FALSE;
35 nLogWidthMarker = 0;
36 nMinLogSize = 0;
37 nMinLockMsgSize = 0;
38 bFileListInEnglish = TRUE;
39 bAppend = TRUE;
40 lProjectLanguage = 0;
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]))
54 return TRUE;
57 return FALSE;
60 BOOL ProjectProperties::GetStringProps(CString &prop,TCHAR *key,bool bRemoveCR)
62 CString cmd,output;
63 output.Empty();
65 cmd.Format(_T("git.exe config %s"),key);
66 int start = 0;
67 if(g_Git.Run(cmd,&output,CP_ACP))
69 return FALSE;
71 if(bRemoveCR)
72 prop = output.Tokenize(_T("\n"),start);
73 else
74 prop = output;
76 return TRUE;
80 BOOL ProjectProperties::GetBOOLProps(BOOL &b,TCHAR *key)
82 CString str,low;
83 if(!GetStringProps(str,key))
84 return FALSE;
86 low=str.MakeLower();
87 if(low == _T("true"))
88 b=true;
89 else
90 b=false;
92 return true;
95 BOOL ProjectProperties::ReadProps(CTGitPath path)
97 CString sPropVal;
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);
109 sCheckRe = sPropVal;
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();
119 return TRUE;
122 #if 0
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();
146 for (;;)
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))
155 sLabel = sPropVal;
156 bFoundBugtraqLabel = TRUE;
158 if ((!bFoundBugtraqMessage)&&(sPropName.Compare(BUGTRAQPROPNAME_MESSAGE)==0))
160 sMessage = sPropVal;
161 bFoundBugtraqMessage = TRUE;
163 if ((!bFoundBugtraqNumber)&&(sPropName.Compare(BUGTRAQPROPNAME_NUMBER)==0))
165 CString val;
166 val = sPropVal;
167 val = val.Trim(_T(" \n\r\t"));
168 if ((val.CompareNoCase(_T("false"))==0)||(val.CompareNoCase(_T("no"))==0))
169 bNumber = FALSE;
170 else
171 bNumber = TRUE;
172 bFoundBugtraqNumber = TRUE;
174 if ((!bFoundBugtraqLogRe)&&(sPropName.Compare(BUGTRAQPROPNAME_LOGREGEX)==0))
176 sCheckRe = sPropVal;
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))
190 sUrl = sPropVal;
191 bFoundBugtraqURL = TRUE;
193 if ((!bFoundBugtraqWarnIssue)&&(sPropName.Compare(BUGTRAQPROPNAME_WARNIFNOISSUE)==0))
195 CString val;
196 val = sPropVal;
197 val = val.Trim(_T(" \n\r\t"));
198 if ((val.CompareNoCase(_T("true"))==0)||(val.CompareNoCase(_T("yes"))==0))
199 bWarnIfNoIssue = TRUE;
200 else
201 bWarnIfNoIssue = FALSE;
202 bFoundBugtraqWarnIssue = TRUE;
204 if ((!bFoundBugtraqAppend)&&(sPropName.Compare(BUGTRAQPROPNAME_APPEND)==0))
206 CString val;
207 val = sPropVal;
208 val = val.Trim(_T(" \n\r\t"));
209 if ((val.CompareNoCase(_T("true"))==0)||(val.CompareNoCase(_T("yes"))==0))
210 bAppend = TRUE;
211 else
212 bAppend = FALSE;
213 bFoundBugtraqAppend = TRUE;
215 if ((!bFoundLogWidth)&&(sPropName.Compare(PROJECTPROPNAME_LOGWIDTHLINE)==0))
217 CString val;
218 val = sPropVal;
219 if (!val.IsEmpty())
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))
234 CString val;
235 val = sPropVal;
236 if (!val.IsEmpty())
238 nMinLogSize = _ttoi(val);
240 bFoundMinLogSize = TRUE;
242 if ((!bFoundMinLockMsgSize)&&(sPropName.Compare(PROJECTPROPNAME_LOCKMSGMINSIZE)==0))
244 CString val;
245 val = sPropVal;
246 if (!val.IsEmpty())
248 nMinLockMsgSize = _ttoi(val);
250 bFoundMinLockMsgSize = TRUE;
252 if ((!bFoundFileListEnglish)&&(sPropName.Compare(PROJECTPROPNAME_LOGFILELISTLANG)==0))
254 CString val;
255 val = sPropVal;
256 val = val.Trim(_T(" \n\r\t"));
257 if ((val.CompareNoCase(_T("false"))==0)||(val.CompareNoCase(_T("no"))==0))
258 bFileListInEnglish = TRUE;
259 else
260 bFileListInEnglish = FALSE;
261 bFoundFileListEnglish = TRUE;
263 if ((!bFoundProjectLanguage)&&(sPropName.Compare(PROJECTPROPNAME_PROJECTLANGUAGE)==0))
265 CString val;
266 val = sPropVal;
267 if (!val.IsEmpty())
269 LPTSTR strEnd;
270 lProjectLanguage = _tcstol(val, &strEnd, 0);
272 bFoundProjectLanguage = TRUE;
274 if ((!bFoundUserFileProps)&&(sPropName.Compare(PROJECTPROPNAME_USERFILEPROPERTY)==0))
276 sFPPath = sPropVal;
277 sFPPath.Replace(_T("\r\n"), _T("\n"));
278 bFoundUserFileProps = TRUE;
280 if ((!bFoundUserDirProps)&&(sPropName.Compare(PROJECTPROPNAME_USERDIRPROPERTY)==0))
282 sDPPath = sPropVal;
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()))
309 return FALSE;
310 propsPath = path;
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)
320 return TRUE;
322 propsPath.Reset();
323 return FALSE;
326 #endif
327 return FALSE; //never reached
330 CString ProjectProperties::GetBugIDFromLog(CString& msg)
332 CString sBugID;
334 if (!sMessage.IsEmpty())
336 CString sBugLine;
337 CString sFirstPart;
338 CString sLastPart;
339 BOOL bTop = FALSE;
340 if (sMessage.Find(_T("%BUGID%"))<0)
341 return sBugID;
342 sFirstPart = sMessage.Left(sMessage.Find(_T("%BUGID%")));
343 sLastPart = sMessage.Mid(sMessage.Find(_T("%BUGID%"))+7);
344 msg.TrimRight('\n');
345 if (msg.ReverseFind('\n')>=0)
347 if (bAppend)
348 sBugLine = msg.Mid(msg.ReverseFind('\n')+1);
349 else
351 sBugLine = msg.Left(msg.Find('\n'));
352 bTop = TRUE;
355 else
357 if (bNumber)
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;
366 break;
369 if (bOnlyNumbers)
370 sBugLine = msg;
372 else
373 sBugLine = msg;
375 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
376 sBugLine.Empty();
377 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
378 sBugLine.Empty();
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)
384 sBugLine.Empty();
385 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
386 sBugLine.Empty();
387 bTop = TRUE;
389 if (sBugLine.IsEmpty())
390 return sBugID;
391 sBugID = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
392 if (bTop)
394 msg = msg.Mid(sBugLine.GetLength());
395 msg.TrimLeft('\n');
397 else
399 msg = msg.Left(msg.GetLength()-sBugLine.GetLength());
400 msg.TrimRight('\n');
403 return sBugID;
406 BOOL ProjectProperties::FindBugID(const CString& msg, CWnd * pWnd)
408 size_t offset1 = 0;
409 size_t offset2 = 0;
410 bool bFound = false;
412 if (sUrl.IsEmpty())
413 return FALSE;
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;
427 wstring s = msg;
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);
439 CHARFORMAT2 format;
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);
445 bFound = true;
449 catch (exception) {}
451 else
455 const tr1::wregex regCheck(sCheckRe);
456 const tr1::wsregex_iterator end;
457 wstring s = msg;
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);
468 CHARFORMAT2 format;
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);
474 bFound = true;
478 catch (exception) {}
481 else if ((!bFound)&&(!sMessage.IsEmpty()))
483 CString sBugLine;
484 CString sFirstPart;
485 CString sLastPart;
486 BOOL bTop = FALSE;
487 if (sMessage.Find(_T("%BUGID%"))<0)
488 return FALSE;
489 sFirstPart = sMessage.Left(sMessage.Find(_T("%BUGID%")));
490 sLastPart = sMessage.Mid(sMessage.Find(_T("%BUGID%"))+7);
491 CString sMsg = msg;
492 sMsg.TrimRight('\n');
493 if (sMsg.ReverseFind('\n')>=0)
495 if (bAppend)
496 sBugLine = sMsg.Mid(sMsg.ReverseFind('\n')+1);
497 else
499 sBugLine = sMsg.Left(sMsg.Find('\n'));
500 bTop = TRUE;
503 else
504 sBugLine = sMsg;
505 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
506 sBugLine.Empty();
507 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
508 sBugLine.Empty();
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)
514 sBugLine.Empty();
515 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
516 sBugLine.Empty();
517 bTop = TRUE;
519 if (sBugLine.IsEmpty())
520 return FALSE;
521 CString sBugIDPart = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
522 if (sBugIDPart.IsEmpty())
523 return FALSE;
524 //the bug id part can contain several bug id's, separated by commas
525 if (!bTop)
526 offset1 = sMsg.GetLength() - sBugLine.GetLength() + sFirstPart.GetLength();
527 else
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);
535 CHARFORMAT2 format;
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);
547 CHARFORMAT2 format;
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);
553 return TRUE;
555 return FALSE;
558 std::set<CString> ProjectProperties::FindBugIDs(const CString& msg)
560 size_t offset1 = 0;
561 size_t offset2 = 0;
562 bool bFound = false;
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;
576 wstring s = msg;
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()));
588 catch (exception) {}
590 else
594 const tr1::wregex regCheck(sCheckRe);
595 const tr1::wsregex_iterator end;
596 wstring s = msg;
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()));
609 catch (exception) {}
612 else if ((!bFound)&&(!sMessage.IsEmpty()))
614 CString sBugLine;
615 CString sFirstPart;
616 CString sLastPart;
617 BOOL bTop = FALSE;
618 if (sMessage.Find(_T("%BUGID%"))<0)
619 return bugIDs;
620 sFirstPart = sMessage.Left(sMessage.Find(_T("%BUGID%")));
621 sLastPart = sMessage.Mid(sMessage.Find(_T("%BUGID%"))+7);
622 CString sMsg = msg;
623 sMsg.TrimRight('\n');
624 if (sMsg.ReverseFind('\n')>=0)
626 if (bAppend)
627 sBugLine = sMsg.Mid(sMsg.ReverseFind('\n')+1);
628 else
630 sBugLine = sMsg.Left(sMsg.Find('\n'));
631 bTop = TRUE;
634 else
635 sBugLine = sMsg;
636 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
637 sBugLine.Empty();
638 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
639 sBugLine.Empty();
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)
645 sBugLine.Empty();
646 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
647 sBugLine.Empty();
648 bTop = TRUE;
650 if (sBugLine.IsEmpty())
651 return bugIDs;
652 CString sBugIDPart = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
653 if (sBugIDPart.IsEmpty())
654 return bugIDs;
655 //the bug id part can contain several bug id's, separated by commas
656 if (!bTop)
657 offset1 = sMsg.GetLength() - sBugLine.GetLength() + sFirstPart.GetLength();
658 else
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));
674 return bugIDs;
677 CString ProjectProperties::FindBugID(const CString& msg)
679 CString sRet;
681 std::set<CString> bugIDs = FindBugIDs(msg);
683 for (std::set<CString>::iterator it = bugIDs.begin(); it != bugIDs.end(); ++it)
685 sRet += *it;
686 sRet += _T(" ");
688 sRet.Trim();
689 return sRet;
692 CString ProjectProperties::GetBugIDUrl(const CString& sBugID)
694 CString ret;
695 if (sUrl.IsEmpty())
696 return ret;
697 if (!sMessage.IsEmpty() || !sCheckRe.IsEmpty())
699 ret = sUrl;
700 ret.Replace(_T("%BUGID%"), sBugID);
702 return ret;
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);
716 if (bNumber)
718 // check if the revision actually _is_ a number
719 // or a list of numbers separated by colons
720 TCHAR c = 0;
721 int len = sID.GetLength();
722 for (int i=0; i<len; ++i)
724 c = sID.GetAt(i);
725 if ((c < '0')&&(c != ','))
727 return FALSE;
729 if (c > '9')
730 return FALSE;
733 return TRUE;
736 BOOL ProjectProperties::HasBugID(const CString& sMessage)
738 if (!sCheckRe.IsEmpty())
742 tr1::wregex rx(sCheckRe);
743 return tr1::regex_search((LPCTSTR)sMessage, rx);
745 catch (exception) {}
747 return FALSE;
749 #if 0
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;
759 sLine.Trim();
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);
769 key.Trim(_T(" ="));
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;
775 if (pos >= 0)
776 sPropsData = sPropsData.Mid(pos).Trim();
777 else
778 sPropsData.Empty();
780 if (bEnableAutoProps)
781 Git_config_set(cfg, Git_CONFIG_SECTION_MISCELLANY, Git_CONFIG_OPTION_ENABLE_AUTO_PROPS, "yes");
783 #endif
785 bool ProjectProperties::AddAutoProps(const CTGitPath& path)
787 if (!path.IsDirectory())
788 return true; // no error, but nothing to do
790 bool bRet = true;
792 char buf[1024] = {0};
793 #if 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;
799 if (!bNumber)
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;
803 if (!sUrl.IsEmpty())
804 bRet = props.Add(BUGTRAQPROPNAME_URL, WideToMultibyte((LPCTSTR)sUrl)) && bRet;
805 if (bWarnIfNoIssue)
806 bRet = props.Add(BUGTRAQPROPNAME_WARNIFNOISSUE, "true") && bRet;
807 if (!bAppend)
808 bRet = props.Add(BUGTRAQPROPNAME_APPEND, "false") && bRet;
809 if (nLogWidthMarker)
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;
816 if (nMinLogSize)
818 sprintf_s(buf, sizeof(buf), "%ld", nMinLogSize);
819 bRet = props.Add(PROJECTPROPNAME_LOGMINSIZE, buf) && bRet;
821 if (nMinLockMsgSize)
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;
843 #endif
844 return bRet;
847 CString ProjectProperties::GetLogSummary(const CString& sMessage)
849 CString sRet;
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()));
869 catch (exception) {}
871 sRet.Trim();
873 return sRet;
876 CString ProjectProperties::MakeShortMessage(const CString& message)
878 bool bFoundShort = true;
879 CString sShortMessage = GetLogSummary(message);
880 if (sShortMessage.IsEmpty())
882 bFoundShort = false;
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;
902 #ifdef DEBUG
903 static class PropTest
905 public:
906 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"));
919 sRet.Trim();
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")));
927 sRet.Trim();
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"));
932 sRet.Trim();
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"));
937 sRet.Trim();
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);
944 } PropTest;
945 #endif