OSDN Git Service

Show Bug ID link at log dialog
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / ProjectProperties.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2003-2008 - TortoiseGit\r
4 \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
9 \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
14 \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
18 //\r
19 #include "StdAfx.h"\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
25 #include <regex>\r
26 #include "git.h"\r
27 \r
28 using namespace std;\r
29 \r
30 \r
31 ProjectProperties::ProjectProperties(void)\r
32 {\r
33         bNumber = TRUE;\r
34         bWarnIfNoIssue = FALSE;\r
35         nLogWidthMarker = 0;\r
36         nMinLogSize = 0;\r
37         nMinLockMsgSize = 0;\r
38         bFileListInEnglish = TRUE;\r
39         bAppend = TRUE;\r
40         lProjectLanguage = 0;\r
41 }\r
42 \r
43 ProjectProperties::~ProjectProperties(void)\r
44 {\r
45 }\r
46 \r
47 \r
48 BOOL ProjectProperties::ReadPropsPathList(const CTGitPathList& pathList)\r
49 {\r
50         for(int nPath = 0; nPath < pathList.GetCount(); nPath++)\r
51         {\r
52                 if (ReadProps(pathList[nPath]))\r
53                 {\r
54                         return TRUE;\r
55                 }\r
56         }\r
57         return FALSE;\r
58 }\r
59 \r
60 BOOL ProjectProperties::GetStringProps(CString &prop,TCHAR *key,bool bRemoveCR)\r
61 {\r
62         CString cmd,output;\r
63         output.Empty();\r
64 \r
65         cmd.Format(_T("git.exe config %s"),key);\r
66         int start = 0;\r
67         if(g_Git.Run(cmd,&output,CP_ACP))\r
68         {\r
69                 return FALSE;\r
70         }\r
71         if(bRemoveCR)\r
72                 prop = output.Tokenize(_T("\n"),start); \r
73         else\r
74                 prop = output;\r
75 \r
76         return TRUE;\r
77 \r
78 }\r
79 \r
80 BOOL ProjectProperties::GetBOOLProps(BOOL &b,TCHAR *key)\r
81 {\r
82         CString str,low;\r
83         if(!GetStringProps(str,key))\r
84                 return FALSE;\r
85 \r
86         low=str.MakeLower();\r
87         if(low == _T("true"))\r
88                 b=true;\r
89         else\r
90                 b=false;\r
91 \r
92         return true;\r
93 \r
94 }\r
95 BOOL ProjectProperties::ReadProps(CTGitPath path)\r
96 {\r
97         CString sPropVal;\r
98 \r
99         GetStringProps(this->sLabel,BUGTRAQPROPNAME_LABEL);\r
100         GetStringProps(this->sMessage,BUGTRAQPROPNAME_MESSAGE);\r
101         GetStringProps(this->sUrl,BUGTRAQPROPNAME_URL);\r
102 \r
103         GetBOOLProps(this->bWarnIfNoIssue,BUGTRAQPROPNAME_WARNIFNOISSUE);\r
104         GetBOOLProps(this->bNumber,BUGTRAQPROPNAME_NUMBER);\r
105         GetBOOLProps(this->bAppend,BUGTRAQPROPNAME_APPEND);\r
106 \r
107         GetStringProps(sPropVal,BUGTRAQPROPNAME_LOGREGEX,false);\r
108 \r
109         sCheckRe = sPropVal;\r
110         if (sCheckRe.Find('\n')>=0)\r
111         {\r
112                 sBugIDRe = sCheckRe.Mid(sCheckRe.Find('\n')).Trim();\r
113                 sCheckRe = sCheckRe.Left(sCheckRe.Find('\n')).Trim();\r
114         }\r
115         if (!sCheckRe.IsEmpty())\r
116         {\r
117                 sCheckRe = sCheckRe.Trim();\r
118         }\r
119         return TRUE;\r
120 \r
121 \r
122 #if 0\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
142 \r
143         if (!path.IsDirectory())\r
144                 path = path.GetContainingDirectory();\r
145                 \r
146         for (;;)\r
147         {\r
148                 GitProperties props(path, GitRev::REV_WC, false);\r
149                 for (int i=0; i<props.GetCount(); ++i)\r
150                 {\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
154                         {\r
155                                 sLabel = sPropVal;\r
156                                 bFoundBugtraqLabel = TRUE;\r
157                         }\r
158                         if ((!bFoundBugtraqMessage)&&(sPropName.Compare(BUGTRAQPROPNAME_MESSAGE)==0))\r
159                         {\r
160                                 sMessage = sPropVal;\r
161                                 bFoundBugtraqMessage = TRUE;\r
162                         }\r
163                         if ((!bFoundBugtraqNumber)&&(sPropName.Compare(BUGTRAQPROPNAME_NUMBER)==0))\r
164                         {\r
165                                 CString val;\r
166                                 val = sPropVal;\r
167                                 val = val.Trim(_T(" \n\r\t"));\r
168                                 if ((val.CompareNoCase(_T("false"))==0)||(val.CompareNoCase(_T("no"))==0))\r
169                                         bNumber = FALSE;\r
170                                 else\r
171                                         bNumber = TRUE;\r
172                                 bFoundBugtraqNumber = TRUE;\r
173                         }\r
174                         if ((!bFoundBugtraqLogRe)&&(sPropName.Compare(BUGTRAQPROPNAME_LOGREGEX)==0))\r
175                         {\r
176                                 sCheckRe = sPropVal;\r
177                                 if (sCheckRe.Find('\n')>=0)\r
178                                 {\r
179                                         sBugIDRe = sCheckRe.Mid(sCheckRe.Find('\n')).Trim();\r
180                                         sCheckRe = sCheckRe.Left(sCheckRe.Find('\n')).Trim();\r
181                                 }\r
182                                 if (!sCheckRe.IsEmpty())\r
183                                 {\r
184                                         sCheckRe = sCheckRe.Trim();\r
185                                 }\r
186                                 bFoundBugtraqLogRe = TRUE;\r
187                         }\r
188                         if ((!bFoundBugtraqURL)&&(sPropName.Compare(BUGTRAQPROPNAME_URL)==0))\r
189                         {\r
190                                 sUrl = sPropVal;\r
191                                 bFoundBugtraqURL = TRUE;\r
192                         }\r
193                         if ((!bFoundBugtraqWarnIssue)&&(sPropName.Compare(BUGTRAQPROPNAME_WARNIFNOISSUE)==0))\r
194                         {\r
195                                 CString val;\r
196                                 val = sPropVal;\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
200                                 else\r
201                                         bWarnIfNoIssue = FALSE;\r
202                                 bFoundBugtraqWarnIssue = TRUE;\r
203                         }\r
204                         if ((!bFoundBugtraqAppend)&&(sPropName.Compare(BUGTRAQPROPNAME_APPEND)==0))\r
205                         {\r
206                                 CString val;\r
207                                 val = sPropVal;\r
208                                 val = val.Trim(_T(" \n\r\t"));\r
209                                 if ((val.CompareNoCase(_T("true"))==0)||(val.CompareNoCase(_T("yes"))==0))\r
210                                         bAppend = TRUE;\r
211                                 else\r
212                                         bAppend = FALSE;\r
213                                 bFoundBugtraqAppend = TRUE;\r
214                         }\r
215                         if ((!bFoundLogWidth)&&(sPropName.Compare(PROJECTPROPNAME_LOGWIDTHLINE)==0))\r
216                         {\r
217                                 CString val;\r
218                                 val = sPropVal;\r
219                                 if (!val.IsEmpty())\r
220                                 {\r
221                                         nLogWidthMarker = _ttoi(val);\r
222                                 }\r
223                                 bFoundLogWidth = TRUE;\r
224                         }\r
225                         if ((!bFoundLogTemplate)&&(sPropName.Compare(PROJECTPROPNAME_LOGTEMPLATE)==0))\r
226                         {\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
231                         }\r
232                         if ((!bFoundMinLogSize)&&(sPropName.Compare(PROJECTPROPNAME_LOGMINSIZE)==0))\r
233                         {\r
234                                 CString val;\r
235                                 val = sPropVal;\r
236                                 if (!val.IsEmpty())\r
237                                 {\r
238                                         nMinLogSize = _ttoi(val);\r
239                                 }\r
240                                 bFoundMinLogSize = TRUE;\r
241                         }\r
242                         if ((!bFoundMinLockMsgSize)&&(sPropName.Compare(PROJECTPROPNAME_LOCKMSGMINSIZE)==0))\r
243                         {\r
244                                 CString val;\r
245                                 val = sPropVal;\r
246                                 if (!val.IsEmpty())\r
247                                 {\r
248                                         nMinLockMsgSize = _ttoi(val);\r
249                                 }\r
250                                 bFoundMinLockMsgSize = TRUE;\r
251                         }\r
252                         if ((!bFoundFileListEnglish)&&(sPropName.Compare(PROJECTPROPNAME_LOGFILELISTLANG)==0))\r
253                         {\r
254                                 CString val;\r
255                                 val = sPropVal;\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
259                                 else\r
260                                         bFileListInEnglish = FALSE;\r
261                                 bFoundFileListEnglish = TRUE;\r
262                         }\r
263                         if ((!bFoundProjectLanguage)&&(sPropName.Compare(PROJECTPROPNAME_PROJECTLANGUAGE)==0))\r
264                         {\r
265                                 CString val;\r
266                                 val = sPropVal;\r
267                                 if (!val.IsEmpty())\r
268                                 {\r
269                                         LPTSTR strEnd;\r
270                                         lProjectLanguage = _tcstol(val, &strEnd, 0);\r
271                                 }\r
272                                 bFoundProjectLanguage = TRUE;\r
273                         }\r
274                         if ((!bFoundUserFileProps)&&(sPropName.Compare(PROJECTPROPNAME_USERFILEPROPERTY)==0))\r
275                         {\r
276                                 sFPPath = sPropVal;\r
277                                 sFPPath.Replace(_T("\r\n"), _T("\n"));\r
278                                 bFoundUserFileProps = TRUE;\r
279                         }\r
280                         if ((!bFoundUserDirProps)&&(sPropName.Compare(PROJECTPROPNAME_USERDIRPROPERTY)==0))\r
281                         {\r
282                                 sDPPath = sPropVal;\r
283                                 sDPPath.Replace(_T("\r\n"), _T("\n"));\r
284                                 bFoundUserDirProps = TRUE;\r
285                         }\r
286                         if ((!bFoundAutoProps)&&(sPropName.Compare(PROJECTPROPNAME_AUTOPROPS)==0))\r
287                         {\r
288                                 sAutoProps = sPropVal;\r
289                                 sAutoProps.Replace(_T("\r\n"), _T("\n"));\r
290                                 bFoundAutoProps = TRUE;\r
291                         }\r
292                         if ((!bFoundWebViewRev)&&(sPropName.Compare(PROJECTPROPNAME_WEBVIEWER_REV)==0))\r
293                         {\r
294                                 sWebViewerRev = sPropVal;\r
295                                 bFoundWebViewRev = TRUE;\r
296                         }\r
297                         if ((!bFoundWebViewPathRev)&&(sPropName.Compare(PROJECTPROPNAME_WEBVIEWER_PATHREV)==0))\r
298                         {\r
299                                 sWebViewerPathRev = sPropVal;\r
300                                 bFoundWebViewPathRev = TRUE;\r
301                         }\r
302                         if ((!bFoundLogSummary)&&(sPropName.Compare(PROJECTPROPNAME_LOGSUMMARY)==0))\r
303                         {\r
304                                 sLogSummaryRe = sPropVal;\r
305                                 bFoundLogSummary = TRUE;\r
306                         }\r
307                 }\r
308                 if (PathIsRoot(path.GetWinPath()))\r
309                         return FALSE;\r
310                 propsPath = path;\r
311                 path = path.GetContainingDirectory();\r
312                 if ((!path.HasAdminDir())||(path.IsEmpty()))\r
313                 {\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
319                         {\r
320                                 return TRUE;\r
321                         }\r
322                         propsPath.Reset();\r
323                         return FALSE;\r
324                 }\r
325         }\r
326 #endif\r
327         return FALSE;           //never reached\r
328 }\r
329 \r
330 CString ProjectProperties::GetBugIDFromLog(CString& msg)\r
331 {\r
332         CString sBugID;\r
333 \r
334         if (!sMessage.IsEmpty())\r
335         {\r
336                 CString sBugLine;\r
337                 CString sFirstPart;\r
338                 CString sLastPart;\r
339                 BOOL bTop = FALSE;\r
340                 if (sMessage.Find(_T("%BUGID%"))<0)\r
341                         return sBugID;\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
346                 {\r
347                         if (bAppend)\r
348                                 sBugLine = msg.Mid(msg.ReverseFind('\n')+1);\r
349                         else\r
350                         {\r
351                                 sBugLine = msg.Left(msg.Find('\n'));\r
352                                 bTop = TRUE;\r
353                         }\r
354                 }\r
355                 else\r
356                 {\r
357                         if (bNumber)\r
358                         {\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
362                                 {\r
363                                         if (!_istdigit(msg[i]))\r
364                                         {\r
365                                                 bOnlyNumbers = false;\r
366                                                 break;\r
367                                         }\r
368                                 }\r
369                                 if (bOnlyNumbers)\r
370                                         sBugLine = msg;\r
371                         }\r
372                         else\r
373                                 sBugLine = msg;\r
374                 }\r
375                 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)\r
376                         sBugLine.Empty();\r
377                 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)\r
378                         sBugLine.Empty();\r
379                 if (sBugLine.IsEmpty())\r
380                 {\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
384                                 sBugLine.Empty();\r
385                         if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)\r
386                                 sBugLine.Empty();\r
387                         bTop = TRUE;\r
388                 }\r
389                 if (sBugLine.IsEmpty())\r
390                         return sBugID;\r
391                 sBugID = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());\r
392                 if (bTop)\r
393                 {\r
394                         msg = msg.Mid(sBugLine.GetLength());\r
395                         msg.TrimLeft('\n');\r
396                 }\r
397                 else\r
398                 {\r
399                         msg = msg.Left(msg.GetLength()-sBugLine.GetLength());\r
400                         msg.TrimRight('\n');\r
401                 }\r
402         }\r
403         return sBugID;\r
404 }\r
405 \r
406 BOOL ProjectProperties::FindBugID(const CString& msg, CWnd * pWnd)\r
407 {\r
408         size_t offset1 = 0;\r
409         size_t offset2 = 0;\r
410         bool bFound = false;\r
411 \r
412         if (sUrl.IsEmpty())\r
413                 return FALSE;\r
414 \r
415         // first use the checkre string to find bug ID's in the message\r
416         if (!sCheckRe.IsEmpty())\r
417         {\r
418                 if (!sBugIDRe.IsEmpty())\r
419                 {\r
420 \r
421                         // match with two regex strings (without grouping!)\r
422                         try\r
423                         {\r
424                                 const tr1::wregex regCheck(sCheckRe);\r
425                                 const tr1::wregex regBugID(sBugIDRe);\r
426                                 const tr1::wsregex_iterator end;\r
427                                 wstring s = msg;\r
428                                 for (tr1::wsregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)\r
429                                 {\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
434                                         {\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
445                                                 bFound = true;\r
446                                         }\r
447                                 }\r
448                         }\r
449                         catch (exception) {}\r
450                 }\r
451                 else\r
452                 {\r
453                         try\r
454                         {\r
455                                 const tr1::wregex regCheck(sCheckRe);\r
456                                 const tr1::wsregex_iterator end;\r
457                                 wstring s = msg;\r
458                                 for (tr1::wsregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)\r
459                                 {\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
464                                         {\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
474                                                 bFound = true;\r
475                                         }\r
476                                 }\r
477                         }\r
478                         catch (exception) {}\r
479                 }\r
480         }\r
481         else if ((!bFound)&&(!sMessage.IsEmpty()))\r
482         {\r
483                 CString sBugLine;\r
484                 CString sFirstPart;\r
485                 CString sLastPart;\r
486                 BOOL bTop = FALSE;\r
487                 if (sMessage.Find(_T("%BUGID%"))<0)\r
488                         return FALSE;\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
494                 {\r
495                         if (bAppend)\r
496                                 sBugLine = sMsg.Mid(sMsg.ReverseFind('\n')+1);\r
497                         else\r
498                         {\r
499                                 sBugLine = sMsg.Left(sMsg.Find('\n'));\r
500                                 bTop = TRUE;\r
501                         }\r
502                 }\r
503                 else\r
504                         sBugLine = sMsg;\r
505                 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)\r
506                         sBugLine.Empty();\r
507                 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)\r
508                         sBugLine.Empty();\r
509                 if (sBugLine.IsEmpty())\r
510                 {\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
514                                 sBugLine.Empty();\r
515                         if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)\r
516                                 sBugLine.Empty();\r
517                         bTop = TRUE;\r
518                 }\r
519                 if (sBugLine.IsEmpty())\r
520                         return FALSE;\r
521                 CString sBugIDPart = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());\r
522                 if (sBugIDPart.IsEmpty())\r
523                         return FALSE;\r
524                 //the bug id part can contain several bug id's, separated by commas\r
525                 if (!bTop)\r
526                         offset1 = sMsg.GetLength() - sBugLine.GetLength() + sFirstPart.GetLength();\r
527                 else\r
528                         offset1 = sFirstPart.GetLength();\r
529                 sBugIDPart.Trim(_T(","));\r
530                 while (sBugIDPart.Find(',')>=0)\r
531                 {\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
543                 }\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
553                 return TRUE;\r
554         }\r
555         return FALSE;\r
556 }\r
557 \r
558 std::set<CString> ProjectProperties::FindBugIDs(const CString& msg)\r
559 {\r
560         size_t offset1 = 0;\r
561         size_t offset2 = 0;\r
562         bool bFound = false;\r
563         std::set<CString> bugIDs;\r
564 \r
565         // first use the checkre string to find bug ID's in the message\r
566         if (!sCheckRe.IsEmpty())\r
567         {\r
568                 if (!sBugIDRe.IsEmpty())\r
569                 {\r
570                         // match with two regex strings (without grouping!)\r
571                         try\r
572                         {\r
573                                 const tr1::wregex regCheck(sCheckRe);\r
574                                 const tr1::wregex regBugID(sBugIDRe);\r
575                                 const tr1::wsregex_iterator end;\r
576                                 wstring s = msg;\r
577                                 for (tr1::wsregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)\r
578                                 {\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
582                                         {\r
583                                                 ATLTRACE(_T("matched id : %s\n"), (*it2)[0].str().c_str());\r
584                                                 bugIDs.insert(CString((*it2)[0].str().c_str()));\r
585                                         }\r
586                                 }\r
587                         }\r
588                         catch (exception) {}\r
589                 }\r
590                 else\r
591                 {\r
592                         try\r
593                         {\r
594                                 const tr1::wregex regCheck(sCheckRe);\r
595                                 const tr1::wsregex_iterator end;\r
596                                 wstring s = msg;\r
597                                 for (tr1::wsregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)\r
598                                 {\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
603                                         {\r
604                                                 ATLTRACE(_T("matched id : %s\n"), wstring(match[1]).c_str());\r
605                                                 bugIDs.insert(CString(wstring(match[1]).c_str()));\r
606                                         }\r
607                                 }\r
608                         }\r
609                         catch (exception) {}\r
610                 }\r
611         }\r
612         else if ((!bFound)&&(!sMessage.IsEmpty()))\r
613         {\r
614                 CString sBugLine;\r
615                 CString sFirstPart;\r
616                 CString sLastPart;\r
617                 BOOL bTop = FALSE;\r
618                 if (sMessage.Find(_T("%BUGID%"))<0)\r
619                         return bugIDs;\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
625                 {\r
626                         if (bAppend)\r
627                                 sBugLine = sMsg.Mid(sMsg.ReverseFind('\n')+1);\r
628                         else\r
629                         {\r
630                                 sBugLine = sMsg.Left(sMsg.Find('\n'));\r
631                                 bTop = TRUE;\r
632                         }\r
633                 }\r
634                 else\r
635                         sBugLine = sMsg;\r
636                 if (sBugLine.Left(sFirstPart.GetLength()).Compare(sFirstPart)!=0)\r
637                         sBugLine.Empty();\r
638                 if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)\r
639                         sBugLine.Empty();\r
640                 if (sBugLine.IsEmpty())\r
641                 {\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
645                                 sBugLine.Empty();\r
646                         if (sBugLine.Right(sLastPart.GetLength()).Compare(sLastPart)!=0)\r
647                                 sBugLine.Empty();\r
648                         bTop = TRUE;\r
649                 }\r
650                 if (sBugLine.IsEmpty())\r
651                         return bugIDs;\r
652                 CString sBugIDPart = sBugLine.Mid(sFirstPart.GetLength(), sBugLine.GetLength() - sFirstPart.GetLength() - sLastPart.GetLength());\r
653                 if (sBugIDPart.IsEmpty())\r
654                         return bugIDs;\r
655                 //the bug id part can contain several bug id's, separated by commas\r
656                 if (!bTop)\r
657                         offset1 = sMsg.GetLength() - sBugLine.GetLength() + sFirstPart.GetLength();\r
658                 else\r
659                         offset1 = sFirstPart.GetLength();\r
660                 sBugIDPart.Trim(_T(","));\r
661                 while (sBugIDPart.Find(',')>=0)\r
662                 {\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
668                 }\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
672         }\r
673 \r
674         return bugIDs;\r
675 }\r
676 \r
677 CString ProjectProperties::FindBugID(const CString& msg)\r
678 {\r
679         CString sRet;\r
680 \r
681         std::set<CString> bugIDs = FindBugIDs(msg);\r
682 \r
683         for (std::set<CString>::iterator it = bugIDs.begin(); it != bugIDs.end(); ++it)\r
684         {\r
685                 sRet += *it;\r
686                 sRet += _T(" ");\r
687         }\r
688         sRet.Trim();\r
689         return sRet;\r
690 }\r
691 \r
692 CString ProjectProperties::GetBugIDUrl(const CString& sBugID)\r
693 {\r
694         CString ret;\r
695         if (sUrl.IsEmpty())\r
696                 return ret;\r
697         if (!sMessage.IsEmpty() || !sCheckRe.IsEmpty())\r
698         {\r
699                 ret = sUrl;\r
700                 ret.Replace(_T("%BUGID%"), sBugID);\r
701         }\r
702         return ret;\r
703 }\r
704 \r
705 BOOL ProjectProperties::CheckBugID(const CString& sID)\r
706 {\r
707         if (!sCheckRe.IsEmpty()&&(!bNumber)&&!sID.IsEmpty())\r
708         {\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
715         }\r
716         if (bNumber)\r
717         {\r
718                 // check if the revision actually _is_ a number\r
719                 // or a list of numbers separated by colons\r
720                 TCHAR c = 0;\r
721                 int len = sID.GetLength();\r
722                 for (int i=0; i<len; ++i)\r
723                 {\r
724                         c = sID.GetAt(i);\r
725                         if ((c < '0')&&(c != ','))\r
726                         {\r
727                                 return FALSE;\r
728                         }\r
729                         if (c > '9')\r
730                                 return FALSE;\r
731                 }\r
732         }\r
733         return TRUE;\r
734 }\r
735 \r
736 BOOL ProjectProperties::HasBugID(const CString& sMessage)\r
737 {\r
738         if (!sCheckRe.IsEmpty())\r
739         {\r
740                 try\r
741                 {\r
742                         tr1::wregex rx(sCheckRe);\r
743                         return tr1::regex_search((LPCTSTR)sMessage, rx);\r
744                 }\r
745                 catch (exception) {}\r
746         }\r
747         return FALSE;\r
748 }\r
749 #if 0\r
750 void ProjectProperties::InsertAutoProps(Git_config_t *cfg)\r
751 {\r
752         // every line is an autoprop\r
753         CString sPropsData = sAutoProps;\r
754         bool bEnableAutoProps = false;\r
755         while (!sPropsData.IsEmpty())\r
756         {\r
757                 int pos = sPropsData.Find('\n');\r
758                 CString sLine = pos >= 0 ? sPropsData.Left(pos) : sPropsData;\r
759                 sLine.Trim();\r
760                 if (!sLine.IsEmpty())\r
761                 {\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
766                         {\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
773                         }\r
774                 }\r
775                 if (pos >= 0)\r
776                         sPropsData = sPropsData.Mid(pos).Trim();\r
777                 else\r
778                         sPropsData.Empty();\r
779         }\r
780         if (bEnableAutoProps)\r
781                 Git_config_set(cfg, Git_CONFIG_SECTION_MISCELLANY, Git_CONFIG_OPTION_ENABLE_AUTO_PROPS, "yes");\r
782 }\r
783 #endif\r
784 \r
785 bool ProjectProperties::AddAutoProps(const CTGitPath& path)\r
786 {\r
787         if (!path.IsDirectory())\r
788                 return true;    // no error, but nothing to do\r
789 \r
790         bool bRet = true;\r
791 \r
792         char buf[1024] = {0};\r
793 #if 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
799         if (!bNumber)\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
807         if (!bAppend)\r
808                 bRet = props.Add(BUGTRAQPROPNAME_APPEND, "false") && bRet;\r
809         if (nLogWidthMarker)\r
810         {\r
811                 sprintf_s(buf, sizeof(buf), "%ld", nLogWidthMarker);\r
812                 bRet = props.Add(PROJECTPROPNAME_LOGWIDTHLINE, buf) && bRet;\r
813         }\r
814         if (!sLogTemplate.IsEmpty())\r
815                 bRet = props.Add(PROJECTPROPNAME_LOGTEMPLATE, WideToMultibyte((LPCTSTR)sLogTemplate)) && bRet;\r
816         if (nMinLogSize)\r
817         {\r
818                 sprintf_s(buf, sizeof(buf), "%ld", nMinLogSize);\r
819                 bRet = props.Add(PROJECTPROPNAME_LOGMINSIZE, buf) && bRet;\r
820         }\r
821         if (nMinLockMsgSize)\r
822         {\r
823                 sprintf_s(buf, sizeof(buf), "%ld", nMinLockMsgSize);\r
824                 bRet = props.Add(PROJECTPROPNAME_LOCKMSGMINSIZE, buf) && bRet;\r
825         }\r
826         if (!bFileListInEnglish)\r
827                 bRet = props.Add(PROJECTPROPNAME_LOGFILELISTLANG, "false") && bRet;\r
828         if (lProjectLanguage)\r
829         {\r
830                 sprintf_s(buf, sizeof(buf), "%ld", lProjectLanguage);\r
831                 bRet = props.Add(PROJECTPROPNAME_PROJECTLANGUAGE, buf) && bRet;\r
832         }\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
843 #endif\r
844         return bRet;\r
845 }\r
846 \r
847 CString ProjectProperties::GetLogSummary(const CString& sMessage)\r
848 {\r
849         CString sRet;\r
850 \r
851         if (!sLogSummaryRe.IsEmpty())\r
852         {\r
853                 try\r
854                 {\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
859                         {\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
863                                 {\r
864                                         ATLTRACE(_T("matched summary : %s\n"), wstring(match[0]).c_str());\r
865                                         sRet += (CString(wstring(match[1]).c_str()));\r
866                                 }\r
867                         }\r
868                 }\r
869                 catch (exception) {}\r
870         }\r
871         sRet.Trim();\r
872 \r
873         return sRet;\r
874 }\r
875 \r
876 CString ProjectProperties::MakeShortMessage(const CString& message)\r
877 {\r
878         bool bFoundShort = true;\r
879         CString sShortMessage = GetLogSummary(message);\r
880         if (sShortMessage.IsEmpty())\r
881         {\r
882                 bFoundShort = false;\r
883                 sShortMessage = message;\r
884         }\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
888         \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
895         {\r
896                 sShortMessage = sShortMessage.Left(found);\r
897         }\r
898         sShortMessage.Replace('\n', ' ');\r
899         return sShortMessage;\r
900 }\r
901 \r
902 #ifdef DEBUG\r
903 static class PropTest\r
904 {\r
905 public:\r
906         PropTest()\r
907         {\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
919                 sRet.Trim();\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
927                 sRet.Trim();\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
932                 sRet.Trim();\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
937                 sRet.Trim();\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
943         }\r
944 } PropTest;\r
945 #endif\r
946 \r
947 \r
948 \r