some spaces-tabs code cleanup
[TortoiseGit.git] / src / TortoiseProc / ProjectProperties.cpp
blob508d4bb204bed28cfc29afbbc9871b39e09a17d4
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.
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 output = g_Git.GetConfigValue(key,CP_UTF8,NULL, bRemoveCR);
67 if(output.IsEmpty())
69 return FALSE;
72 prop = output;
74 return TRUE;
78 BOOL ProjectProperties::GetBOOLProps(BOOL &b,TCHAR *key)
80 CString str,low;
81 if(!GetStringProps(str,key))
82 return FALSE;
84 low=str.MakeLower();
85 if(low == _T("true"))
86 b=true;
87 else
88 b=false;
90 return true;
93 BOOL ProjectProperties::ReadProps(CTGitPath path)
95 CString sPropVal;
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);
107 sCheckRe = sPropVal;
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();
117 return TRUE;
120 #if 0
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();
144 for (;;)
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))
153 sLabel = sPropVal;
154 bFoundBugtraqLabel = TRUE;
156 if ((!bFoundBugtraqMessage)&&(sPropName.Compare(BUGTRAQPROPNAME_MESSAGE)==0))
158 sMessage = sPropVal;
159 bFoundBugtraqMessage = TRUE;
161 if ((!bFoundBugtraqNumber)&&(sPropName.Compare(BUGTRAQPROPNAME_NUMBER)==0))
163 CString val;
164 val = sPropVal;
165 val = val.Trim(_T(" \n\r\t"));
166 if ((val.CompareNoCase(_T("false"))==0)||(val.CompareNoCase(_T("no"))==0))
167 bNumber = FALSE;
168 else
169 bNumber = TRUE;
170 bFoundBugtraqNumber = TRUE;
172 if ((!bFoundBugtraqLogRe)&&(sPropName.Compare(BUGTRAQPROPNAME_LOGREGEX)==0))
174 sCheckRe = sPropVal;
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))
188 sUrl = sPropVal;
189 bFoundBugtraqURL = TRUE;
191 if ((!bFoundBugtraqWarnIssue)&&(sPropName.Compare(BUGTRAQPROPNAME_WARNIFNOISSUE)==0))
193 CString val;
194 val = sPropVal;
195 val = val.Trim(_T(" \n\r\t"));
196 if ((val.CompareNoCase(_T("true"))==0)||(val.CompareNoCase(_T("yes"))==0))
197 bWarnIfNoIssue = TRUE;
198 else
199 bWarnIfNoIssue = FALSE;
200 bFoundBugtraqWarnIssue = TRUE;
202 if ((!bFoundBugtraqAppend)&&(sPropName.Compare(BUGTRAQPROPNAME_APPEND)==0))
204 CString val;
205 val = sPropVal;
206 val = val.Trim(_T(" \n\r\t"));
207 if ((val.CompareNoCase(_T("true"))==0)||(val.CompareNoCase(_T("yes"))==0))
208 bAppend = TRUE;
209 else
210 bAppend = FALSE;
211 bFoundBugtraqAppend = TRUE;
213 if ((!bFoundLogWidth)&&(sPropName.Compare(PROJECTPROPNAME_LOGWIDTHLINE)==0))
215 CString val;
216 val = sPropVal;
217 if (!val.IsEmpty())
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))
232 CString val;
233 val = sPropVal;
234 if (!val.IsEmpty())
236 nMinLogSize = _ttoi(val);
238 bFoundMinLogSize = TRUE;
240 if ((!bFoundMinLockMsgSize)&&(sPropName.Compare(PROJECTPROPNAME_LOCKMSGMINSIZE)==0))
242 CString val;
243 val = sPropVal;
244 if (!val.IsEmpty())
246 nMinLockMsgSize = _ttoi(val);
248 bFoundMinLockMsgSize = TRUE;
250 if ((!bFoundFileListEnglish)&&(sPropName.Compare(PROJECTPROPNAME_LOGFILELISTLANG)==0))
252 CString val;
253 val = sPropVal;
254 val = val.Trim(_T(" \n\r\t"));
255 if ((val.CompareNoCase(_T("false"))==0)||(val.CompareNoCase(_T("no"))==0))
256 bFileListInEnglish = TRUE;
257 else
258 bFileListInEnglish = FALSE;
259 bFoundFileListEnglish = TRUE;
261 if ((!bFoundProjectLanguage)&&(sPropName.Compare(PROJECTPROPNAME_PROJECTLANGUAGE)==0))
263 CString val;
264 val = sPropVal;
265 if (!val.IsEmpty())
267 LPTSTR strEnd;
268 lProjectLanguage = _tcstol(val, &strEnd, 0);
270 bFoundProjectLanguage = TRUE;
272 if ((!bFoundUserFileProps)&&(sPropName.Compare(PROJECTPROPNAME_USERFILEPROPERTY)==0))
274 sFPPath = sPropVal;
275 sFPPath.Replace(_T("\r\n"), _T("\n"));
276 bFoundUserFileProps = TRUE;
278 if ((!bFoundUserDirProps)&&(sPropName.Compare(PROJECTPROPNAME_USERDIRPROPERTY)==0))
280 sDPPath = sPropVal;
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()))
307 return FALSE;
308 propsPath = path;
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)
318 return TRUE;
320 propsPath.Reset();
321 return FALSE;
324 #endif
325 return FALSE; //never reached
328 CString ProjectProperties::GetBugIDFromLog(CString& msg)
330 CString sBugID;
332 if (!sMessage.IsEmpty())
334 CString sBugLine;
335 CString sFirstPart;
336 CString sLastPart;
337 BOOL bTop = FALSE;
338 if (sMessage.Find(_T("%BUGID%"))<0)
339 return sBugID;
340 sFirstPart = sMessage.Left(sMessage.Find(_T("%BUGID%")));
341 sLastPart = sMessage.Mid(sMessage.Find(_T("%BUGID%"))+7);
342 msg.TrimRight('\n');
343 if (msg.ReverseFind('\n')>=0)
345 if (bAppend)
346 sBugLine = msg.Mid(msg.ReverseFind('\n')+1);
347 else
349 sBugLine = msg.Left(msg.Find('\n'));
350 bTop = TRUE;
353 else
355 if (bNumber)
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;
364 break;
367 if (bOnlyNumbers)
368 sBugLine = msg;
370 else
371 sBugLine = msg;
373 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
374 sBugLine.Empty();
375 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
376 sBugLine.Empty();
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)
382 sBugLine.Empty();
383 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
384 sBugLine.Empty();
385 bTop = TRUE;
387 if (sBugLine.IsEmpty())
388 return sBugID;
389 sBugID = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
390 if (bTop)
392 msg = msg.Mid(sBugLine.GetLength());
393 msg.TrimLeft('\n');
395 else
397 msg = msg.Left(msg.GetLength()-sBugLine.GetLength());
398 msg.TrimRight('\n');
401 return sBugID;
404 BOOL ProjectProperties::FindBugID(const CString& msg, CWnd * pWnd)
406 size_t offset1 = 0;
407 size_t offset2 = 0;
408 bool bFound = false;
410 if (sUrl.IsEmpty())
411 return FALSE;
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;
425 wstring s = msg;
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);
437 CHARFORMAT2 format;
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);
443 bFound = true;
447 catch (exception) {}
449 else
453 const tr1::wregex regCheck(sCheckRe);
454 const tr1::wsregex_iterator end;
455 wstring s = msg;
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);
466 CHARFORMAT2 format;
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);
472 bFound = true;
476 catch (exception) {}
479 else if ((!bFound)&&(!sMessage.IsEmpty()))
481 CString sBugLine;
482 CString sFirstPart;
483 CString sLastPart;
484 BOOL bTop = FALSE;
485 if (sMessage.Find(_T("%BUGID%"))<0)
486 return FALSE;
487 sFirstPart = sMessage.Left(sMessage.Find(_T("%BUGID%")));
488 sLastPart = sMessage.Mid(sMessage.Find(_T("%BUGID%"))+7);
489 CString sMsg = msg;
490 sMsg.TrimRight('\n');
491 if (sMsg.ReverseFind('\n')>=0)
493 if (bAppend)
494 sBugLine = sMsg.Mid(sMsg.ReverseFind('\n')+1);
495 else
497 sBugLine = sMsg.Left(sMsg.Find('\n'));
498 bTop = TRUE;
501 else
502 sBugLine = sMsg;
503 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
504 sBugLine.Empty();
505 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
506 sBugLine.Empty();
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)
512 sBugLine.Empty();
513 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
514 sBugLine.Empty();
515 bTop = TRUE;
517 if (sBugLine.IsEmpty())
518 return FALSE;
519 CString sBugIDPart = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
520 if (sBugIDPart.IsEmpty())
521 return FALSE;
522 //the bug id part can contain several bug id's, separated by commas
523 if (!bTop)
524 offset1 = sMsg.GetLength() - sBugLine.GetLength() + sFirstPart.GetLength();
525 else
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);
533 CHARFORMAT2 format;
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);
545 CHARFORMAT2 format;
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);
551 return TRUE;
553 return FALSE;
556 std::set<CString> ProjectProperties::FindBugIDs(const CString& msg)
558 size_t offset1 = 0;
559 size_t offset2 = 0;
560 bool bFound = false;
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;
574 wstring s = msg;
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()));
586 catch (exception) {}
588 else
592 const tr1::wregex regCheck(sCheckRe);
593 const tr1::wsregex_iterator end;
594 wstring s = msg;
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()));
607 catch (exception) {}
610 else if ((!bFound)&&(!sMessage.IsEmpty()))
612 CString sBugLine;
613 CString sFirstPart;
614 CString sLastPart;
615 BOOL bTop = FALSE;
616 if (sMessage.Find(_T("%BUGID%"))<0)
617 return bugIDs;
618 sFirstPart = sMessage.Left(sMessage.Find(_T("%BUGID%")));
619 sLastPart = sMessage.Mid(sMessage.Find(_T("%BUGID%"))+7);
620 CString sMsg = msg;
621 sMsg.TrimRight('\n');
622 if (sMsg.ReverseFind('\n')>=0)
624 if (bAppend)
625 sBugLine = sMsg.Mid(sMsg.ReverseFind('\n')+1);
626 else
628 sBugLine = sMsg.Left(sMsg.Find('\n'));
629 bTop = TRUE;
632 else
633 sBugLine = sMsg;
634 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)
635 sBugLine.Empty();
636 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
637 sBugLine.Empty();
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)
643 sBugLine.Empty();
644 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)
645 sBugLine.Empty();
646 bTop = TRUE;
648 if (sBugLine.IsEmpty())
649 return bugIDs;
650 CString sBugIDPart = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());
651 if (sBugIDPart.IsEmpty())
652 return bugIDs;
653 //the bug id part can contain several bug id's, separated by commas
654 if (!bTop)
655 offset1 = sMsg.GetLength() - sBugLine.GetLength() + sFirstPart.GetLength();
656 else
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));
672 return bugIDs;
675 CString ProjectProperties::FindBugID(const CString& msg)
677 CString sRet;
679 std::set<CString> bugIDs = FindBugIDs(msg);
681 for (std::set<CString>::iterator it = bugIDs.begin(); it != bugIDs.end(); ++it)
683 sRet += *it;
684 sRet += _T(" ");
686 sRet.Trim();
687 return sRet;
690 CString ProjectProperties::GetBugIDUrl(const CString& sBugID)
692 CString ret;
693 if (sUrl.IsEmpty())
694 return ret;
695 if (!sMessage.IsEmpty() || !sCheckRe.IsEmpty())
697 ret = sUrl;
698 ret.Replace(_T("%BUGID%"), sBugID);
700 return ret;
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);
714 if (bNumber)
716 // check if the revision actually _is_ a number
717 // or a list of numbers separated by colons
718 TCHAR c = 0;
719 int len = sID.GetLength();
720 for (int i=0; i<len; ++i)
722 c = sID.GetAt(i);
723 if ((c < '0')&&(c != ','))
725 return FALSE;
727 if (c > '9')
728 return FALSE;
731 return TRUE;
734 BOOL ProjectProperties::HasBugID(const CString& sMessage)
736 if (!sCheckRe.IsEmpty())
740 tr1::wregex rx(sCheckRe);
741 return tr1::regex_search((LPCTSTR)sMessage, rx);
743 catch (exception) {}
745 return FALSE;
747 #if 0
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;
757 sLine.Trim();
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);
767 key.Trim(_T(" ="));
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;
773 if (pos >= 0)
774 sPropsData = sPropsData.Mid(pos).Trim();
775 else
776 sPropsData.Empty();
778 if (bEnableAutoProps)
779 Git_config_set(cfg, Git_CONFIG_SECTION_MISCELLANY, Git_CONFIG_OPTION_ENABLE_AUTO_PROPS, "yes");
781 #endif
783 bool ProjectProperties::AddAutoProps(const CTGitPath& path)
785 if (!path.IsDirectory())
786 return true; // no error, but nothing to do
788 bool bRet = true;
790 #if 0
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;
797 if (!bNumber)
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;
801 if (!sUrl.IsEmpty())
802 bRet = props.Add(BUGTRAQPROPNAME_URL, WideToMultibyte((LPCTSTR)sUrl)) && bRet;
803 if (bWarnIfNoIssue)
804 bRet = props.Add(BUGTRAQPROPNAME_WARNIFNOISSUE, "true") && bRet;
805 if (!bAppend)
806 bRet = props.Add(BUGTRAQPROPNAME_APPEND, "false") && bRet;
807 if (nLogWidthMarker)
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;
814 if (nMinLogSize)
816 sprintf_s(buf, sizeof(buf), "%ld", nMinLogSize);
817 bRet = props.Add(PROJECTPROPNAME_LOGMINSIZE, buf) && bRet;
819 if (nMinLockMsgSize)
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;
841 #endif
842 return bRet;
845 CString ProjectProperties::GetLogSummary(const CString& sMessage)
847 CString sRet;
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()));
867 catch (exception) {}
869 sRet.Trim();
871 return sRet;
874 CString ProjectProperties::MakeShortMessage(const CString& message)
876 bool bFoundShort = true;
877 CString sShortMessage = GetLogSummary(message);
878 if (sShortMessage.IsEmpty())
880 bFoundShort = false;
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;
900 #ifdef DEBUG
901 static class PropTest
903 public:
904 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"));
917 sRet.Trim();
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")));
925 sRet.Trim();
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"));
930 sRet.Trim();
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"));
935 sRet.Trim();
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);
942 } PropTest;
943 #endif