OSDN Git Service

Update version number to 1.2.1.0
[tortoisegit/TortoiseGitJp.git] / src / TortoiseMerge / Patch.cpp
1 // TortoiseMerge - a Diff/Patch program\r
2 \r
3 // Copyright (C) 2004-2009 - TortoiseSVN\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 "Resource.h"\r
21 #include "UnicodeUtils.h"\r
22 #include "DirFileEnum.h"\r
23 #include "TortoiseMerge.h"\r
24 #include "svn_wc.h"\r
25 #include "GitAdminDir.h"\r
26 #include "Patch.h"\r
27 \r
28 #ifdef _DEBUG\r
29 #define new DEBUG_NEW\r
30 #undef THIS_FILE\r
31 static char THIS_FILE[] = __FILE__;\r
32 #endif\r
33 \r
34 CPatch::CPatch(void)\r
35 {\r
36         m_nStrip = 0;\r
37         m_IsGitPatch = false;\r
38 }\r
39 \r
40 CPatch::~CPatch(void)\r
41 {\r
42         FreeMemory();\r
43 }\r
44 \r
45 void CPatch::FreeMemory()\r
46 {\r
47         for (int i=0; i<m_arFileDiffs.GetCount(); i++)\r
48         {\r
49                 Chunks * chunks = m_arFileDiffs.GetAt(i);\r
50                 for (int j=0; j<chunks->chunks.GetCount(); j++)\r
51                 {\r
52                         delete chunks->chunks.GetAt(j);\r
53                 }\r
54                 chunks->chunks.RemoveAll();\r
55                 delete chunks;\r
56         }\r
57         m_arFileDiffs.RemoveAll();\r
58 }\r
59 \r
60 BOOL CPatch::ParserGitPatch(CFileTextLines &PatchLines,int nIndex)\r
61 {\r
62         CString sLine;\r
63         EOL ending = EOL_NOENDING;\r
64 \r
65         int state = 0;\r
66         Chunks * chunks = NULL;\r
67         Chunk * chunk = NULL;\r
68         int nAddLineCount = 0;\r
69         int nRemoveLineCount = 0;\r
70         int nContextLineCount = 0;\r
71         for ( ;nIndex<PatchLines.GetCount(); nIndex++)\r
72         {\r
73                 sLine = PatchLines.GetAt(nIndex);\r
74                 ending = PatchLines.GetLineEnding(nIndex);\r
75                 if (ending != EOL_NOENDING)\r
76                         ending = EOL_AUTOLINE;\r
77                 \r
78                 switch (state)\r
79                 {\r
80                         case 0: \r
81                         {\r
82                                 // diff --git\r
83                                 if( sLine.Find(_T("diff --git"))==0)\r
84                                 {\r
85                                         if (chunks)\r
86                                         {\r
87                                                 //this is a new file diff, so add the last one to \r
88                                                 //our array.\r
89                                                 m_arFileDiffs.Add(chunks);\r
90                                         }\r
91                                         chunks = new Chunks();\r
92 \r
93                                 }\r
94                                 \r
95                                 //index\r
96                                 if( sLine.Find(_T("index"))==0 )\r
97                                 {\r
98                                         int dotstart=sLine.Find(_T(".."));\r
99                                         if(dotstart>=0)\r
100                                         {\r
101                                                 chunks->sRevision = sLine.Mid(dotstart-7,7);\r
102                                                 chunks->sRevision2 = sLine.Mid(dotstart+2,7);\r
103                                         }\r
104                                 }\r
105 \r
106                                 //---\r
107                                 if( sLine.Find(_T("--- "))==0 )\r
108                                 {\r
109                                         if (sLine.Left(3).Compare(_T("---"))!=0)\r
110                                         {\r
111                                                 //no starting "---" found\r
112                                                 //seems to be either garbage or just\r
113                                                 //a binary file. So start over...\r
114                                                 state = 0;\r
115                                                 nIndex--;\r
116                                                 if (chunks)\r
117                                                 {\r
118                                                         delete chunks;\r
119                                                         chunks = NULL;\r
120                                                 }\r
121                                                 break;\r
122                                         }\r
123                                 \r
124                                         sLine = sLine.Mid(3);   //remove the "---"\r
125                                         sLine =sLine.Trim();\r
126                                         //at the end of the filepath there's a revision number...\r
127                                         int bracket = sLine.ReverseFind('(');\r
128                                         if (bracket < 0)\r
129                                         // some patch files can have another '(' char, especially ones created in Chinese OS\r
130                                                 bracket = sLine.ReverseFind(0xff08);\r
131                                 \r
132                                         if (bracket < 0)\r
133                                         {\r
134                                                 if (chunks->sFilePath.IsEmpty())\r
135                                                         chunks->sFilePath = sLine.Trim();\r
136                                         }\r
137                                         else\r
138                                                 chunks->sFilePath = sLine.Left(bracket-1).Trim();\r
139                                         \r
140                                         if (chunks->sFilePath.Find('\t')>=0)\r
141                                         {\r
142                                                 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));\r
143                                         }\r
144                                         if( chunks->sFilePath.Find(_T("a/")) == 0 )\r
145                                                 chunks->sFilePath=chunks->sFilePath.Mid(2);\r
146 \r
147                                         chunks->sFilePath.Replace(_T('/'),_T('\\'));\r
148                                 }\r
149                                 \r
150                                 // +++\r
151                                 if( sLine.Find(_T("+++ ")) == 0 )\r
152                                 {\r
153                                         sLine = sLine.Mid(3);   //remove the "---"\r
154                                         sLine =sLine.Trim();\r
155                                 \r
156                                         //at the end of the filepath there's a revision number...\r
157                                         int bracket = sLine.ReverseFind('(');\r
158                                         if (bracket < 0)\r
159                                         // some patch files can have another '(' char, especially ones created in Chinese OS\r
160                                                 bracket = sLine.ReverseFind(0xff08);\r
161 \r
162                                         if (bracket < 0)\r
163                                                 chunks->sFilePath2 = sLine.Trim();\r
164                                         else\r
165                                                 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();\r
166                                         if (chunks->sFilePath2.Find('\t')>=0)\r
167                                         {\r
168                                                 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));\r
169                                         }\r
170                                         if( chunks->sFilePath2.Find(_T("a/")) == 0 )\r
171                                                 chunks->sFilePath2=chunks->sFilePath2.Mid(2);\r
172 \r
173                                         chunks->sFilePath2.Replace(_T('/'),_T('\\'));\r
174                                 }\r
175                                 \r
176                                 //@@ -xxx,xxx +xxx,xxx @@\r
177                                 if( sLine.Find(_T("@@")) == 0 )\r
178                                 {\r
179                                         sLine = sLine.Mid(2);\r
180                                         sLine = sLine.Trim();\r
181                                         chunk = new Chunk();\r
182                                         CString sRemove = sLine.Left(sLine.Find(' '));\r
183                                         CString sAdd = sLine.Mid(sLine.Find(' '));\r
184                                         chunk->lRemoveStart = (-_ttol(sRemove));\r
185                                         if (sRemove.Find(',')>=0)\r
186                                         {\r
187                                                 sRemove = sRemove.Mid(sRemove.Find(',')+1);\r
188                                                 chunk->lRemoveLength = _ttol(sRemove);\r
189                                         }\r
190                                         else\r
191                                         {\r
192                                                 chunk->lRemoveStart = 0;\r
193                                                 chunk->lRemoveLength = (-_ttol(sRemove));\r
194                                         }\r
195                                         chunk->lAddStart = _ttol(sAdd);\r
196                                         if (sAdd.Find(',')>=0)\r
197                                         {\r
198                                                 sAdd = sAdd.Mid(sAdd.Find(',')+1);\r
199                                                 chunk->lAddLength = _ttol(sAdd);\r
200                                         }\r
201                                         else\r
202                                         {\r
203                                                 chunk->lAddStart = 1;\r
204                                                 chunk->lAddLength = _ttol(sAdd);\r
205                                         }\r
206                                         \r
207                                         state =5;\r
208                                 }\r
209                         } \r
210                 break;\r
211                 \r
212                 \r
213                 case 5: //[ |+|-] <sourceline>\r
214                         {\r
215                                 //this line is either a context line (with a ' ' in front)\r
216                                 //a line added (with a '+' in front)\r
217                                 //or a removed line (with a '-' in front)\r
218                                 TCHAR type;\r
219                                 if (sLine.IsEmpty())\r
220                                         type = ' ';\r
221                                 else\r
222                                         type = sLine.GetAt(0);\r
223                                 if (type == ' ')\r
224                                 {\r
225                                         //it's a context line - we don't use them here right now\r
226                                         //but maybe in the future the patch algorithm can be\r
227                                         //extended to use those in case the file to patch has\r
228                                         //already changed and no base file is around...\r
229                                         chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));\r
230                                         chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);\r
231                                         chunk->arEOLs.push_back(ending);\r
232                                         nContextLineCount++;\r
233                                 }\r
234                                 else if (type == '\\')\r
235                                 {\r
236                                         //it's a context line (sort of): \r
237                                         //warnings start with a '\' char (e.g. "\ No newline at end of file")\r
238                                         //so just ignore this...\r
239                                 }\r
240                                 else if (type == '-')\r
241                                 {\r
242                                         //a removed line\r
243                                         chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));\r
244                                         chunk->arLinesStates.Add(PATCHSTATE_REMOVED);\r
245                                         chunk->arEOLs.push_back(ending);\r
246                                         nRemoveLineCount++;\r
247                                 }\r
248                                 else if (type == '+')\r
249                                 {\r
250                                         //an added line\r
251                                         chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));\r
252                                         chunk->arLinesStates.Add(PATCHSTATE_ADDED);\r
253                                         chunk->arEOLs.push_back(ending);\r
254                                         nAddLineCount++;\r
255                                 }\r
256                                 else\r
257                                 {\r
258                                         //none of those lines! what the hell happened here?\r
259                                         m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);\r
260                                         goto errorcleanup;\r
261                                 }\r
262                                 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&\r
263                                         chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))\r
264                                 {\r
265                                         //chunk is finished\r
266                                         if (chunks)\r
267                                                 chunks->chunks.Add(chunk);\r
268                                         else\r
269                                                 delete chunk;\r
270                                         chunk = NULL;\r
271                                         nAddLineCount = 0;\r
272                                         nContextLineCount = 0;\r
273                                         nRemoveLineCount = 0;\r
274                                         state = 0;\r
275                                 }\r
276                         } \r
277                 break;\r
278                 default:\r
279                         ASSERT(FALSE);\r
280                 } // switch (state) \r
281         } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++) \r
282         if (chunk)\r
283         {\r
284                 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);\r
285                 goto errorcleanup;\r
286         }\r
287         if (chunks)\r
288                 m_arFileDiffs.Add(chunks);\r
289         return TRUE;\r
290 \r
291 errorcleanup:\r
292         if (chunk)\r
293                 delete chunk;\r
294         if (chunks)\r
295         {\r
296                 for (int i=0; i<chunks->chunks.GetCount(); i++)\r
297                 {\r
298                         delete chunks->chunks.GetAt(i);\r
299                 }\r
300                 chunks->chunks.RemoveAll();\r
301                 delete chunks;\r
302         }\r
303         FreeMemory();\r
304         return FALSE;\r
305 }\r
306 \r
307 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)\r
308 {\r
309         CString sLine;\r
310         EOL ending = EOL_NOENDING;\r
311         INT_PTR nIndex = 0;\r
312         INT_PTR nLineCount = 0;\r
313         g_crasher.AddFile((LPCSTR)(LPCTSTR)filename, (LPCSTR)(LPCTSTR)_T("unified diff file"));\r
314 \r
315         CFileTextLines PatchLines;\r
316         if (!PatchLines.Load(filename))\r
317         {\r
318                 m_sErrorMessage = PatchLines.GetErrorString();\r
319                 return FALSE;\r
320         }\r
321         m_UnicodeType = PatchLines.GetUnicodeType();\r
322         FreeMemory();\r
323         nLineCount = PatchLines.GetCount();\r
324         //now we got all the lines of the patch file\r
325         //in our array - parsing can start...\r
326 \r
327         for(nIndex=0;PatchLines.GetCount();nIndex++)\r
328         {\r
329                 sLine = PatchLines.GetAt(nIndex);\r
330                 if(sLine.Left(10).Compare(_T("diff --git")) == 0)\r
331                 {\r
332                         this->m_IsGitPatch=true;\r
333                         break;\r
334                 }\r
335         }\r
336 \r
337         //first, skip possible garbage at the beginning\r
338         //garbage is finished when a line starts with "Index: "\r
339         //and the next line consists of only "=" characters\r
340         if( !m_IsGitPatch )\r
341         {\r
342                 for (nIndex=0; nIndex<PatchLines.GetCount(); nIndex++)\r
343                 {\r
344                         sLine = PatchLines.GetAt(nIndex);\r
345 \r
346                         if (sLine.Left(4).Compare(_T("--- "))==0)\r
347                                 break;\r
348                         if ((nIndex+1)<PatchLines.GetCount())\r
349                         {\r
350                                 sLine = PatchLines.GetAt(nIndex+1);\r
351 \r
352                                 if(sLine.IsEmpty()&&m_IsGitPatch)\r
353                                         continue;\r
354 \r
355                                 sLine.Replace(_T("="), _T(""));\r
356                                 if (sLine.IsEmpty())\r
357                                         break;\r
358                         }\r
359                 }\r
360         }\r
361 \r
362         if ((PatchLines.GetCount()-nIndex) < 2)\r
363         {\r
364                 //no file entry found.\r
365                 m_sErrorMessage.LoadString(IDS_ERR_PATCH_NOINDEX);\r
366                 return FALSE;\r
367         }\r
368 \r
369         if( m_IsGitPatch )\r
370                 return ParserGitPatch(PatchLines,nIndex);\r
371 \r
372         //from this point on we have the real unified diff data\r
373         int state = 0;\r
374         Chunks * chunks = NULL;\r
375         Chunk * chunk = NULL;\r
376         int nAddLineCount = 0;\r
377         int nRemoveLineCount = 0;\r
378         int nContextLineCount = 0;\r
379         for ( ;nIndex<PatchLines.GetCount(); nIndex++)\r
380         {\r
381                 sLine = PatchLines.GetAt(nIndex);\r
382                 ending = PatchLines.GetLineEnding(nIndex);\r
383                 if (ending != EOL_NOENDING)\r
384                         ending = EOL_AUTOLINE;\r
385                 if (state == 0)\r
386                 {\r
387                         if ((sLine.Left(4).Compare(_T("--- "))==0)&&((sLine.Find('\t') >= 0)||this->m_IsGitPatch))\r
388                         {\r
389                                 state = 2;\r
390                                 if (chunks)\r
391                                 {\r
392                                         //this is a new file diff, so add the last one to \r
393                                         //our array.\r
394                                         m_arFileDiffs.Add(chunks);\r
395                                 }\r
396                                 chunks = new Chunks();\r
397                                 \r
398                                 int nTab = sLine.Find('\t');\r
399 \r
400                                 int filestart = 4;\r
401                                 if(m_IsGitPatch)\r
402                                 {\r
403                                         nTab=sLine.GetLength();\r
404                                         filestart = 6;\r
405                                 }\r
406 \r
407                                 if (nTab >= 0)\r
408                                 {\r
409                                         chunks->sFilePath = sLine.Mid(filestart, nTab-filestart).Trim();\r
410                                 }\r
411                         }\r
412                 }\r
413                 switch (state)\r
414                 {\r
415                 case 0: //Index: <filepath>\r
416                         {\r
417                                 CString nextLine;\r
418                                 if ((nIndex+1)<PatchLines.GetCount())\r
419                                 {\r
420                                         nextLine = PatchLines.GetAt(nIndex+1);\r
421                                         if (!nextLine.IsEmpty())\r
422                                         {\r
423                                                 nextLine.Replace(_T("="), _T(""));\r
424                                                 if (nextLine.IsEmpty())\r
425                                                 {\r
426                                                         if (chunks)\r
427                                                         {\r
428                                                                 //this is a new file diff, so add the last one to \r
429                                                                 //our array.\r
430                                                                 m_arFileDiffs.Add(chunks);\r
431                                                         }\r
432                                                         chunks = new Chunks();\r
433                                                         int nColon = sLine.Find(':');\r
434                                                         if (nColon >= 0)\r
435                                                         {\r
436                                                                 chunks->sFilePath = sLine.Mid(nColon+1).Trim();\r
437                                                                 if (chunks->sFilePath.Find('\t')>=0)\r
438                                                                         chunks->sFilePath.Left(chunks->sFilePath.Find('\t')).TrimRight();\r
439                                                                 if (chunks->sFilePath.Right(9).Compare(_T("(deleted)"))==0)\r
440                                                                         chunks->sFilePath.Left(chunks->sFilePath.GetLength()-9).TrimRight();\r
441                                                                 if (chunks->sFilePath.Right(7).Compare(_T("(added)"))==0)\r
442                                                                         chunks->sFilePath.Left(chunks->sFilePath.GetLength()-7).TrimRight();\r
443                                                         }\r
444                                                         state++;\r
445                                                 }\r
446                                         }\r
447                                 }\r
448                                 if (state == 0)\r
449                                 {\r
450                                         if (nIndex > 0)\r
451                                         {\r
452                                                 nIndex--;\r
453                                                 state = 4;\r
454                                                 if (chunks == NULL)\r
455                                                 {\r
456                                                         //the line\r
457                                                         //Index: <filepath>\r
458                                                         //was not found at the start of a file diff!\r
459                                                         break;\r
460                                                 }\r
461                                         }\r
462                                 }\r
463                         } \r
464                 break;\r
465                 case 1: //====================\r
466                         {\r
467                                 sLine.Replace(_T("="), _T(""));\r
468                                 if (sLine.IsEmpty())\r
469                                 {\r
470                                         // if the next line is already the start of the chunk,\r
471                                         // then the patch/diff file was not created by svn. But we\r
472                                         // still try to use it\r
473                                         if (PatchLines.GetCount() > (nIndex + 1))\r
474                                         {\r
475 \r
476                                                 if (PatchLines.GetAt(nIndex+1).Left(2).Compare(_T("@@"))==0)\r
477                                                 {\r
478                                                         state += 2;\r
479                                                 }\r
480                                         }\r
481                                         state++;\r
482                                 }\r
483                                 else\r
484                                 {\r
485                                         //the line\r
486                                         //=========================\r
487                                         //was not found\r
488                                         m_sErrorMessage.Format(IDS_ERR_PATCH_NOEQUATIONCHARLINE, nIndex);\r
489                                         goto errorcleanup;\r
490                                 }\r
491                         }\r
492                 break;\r
493                 case 2: //--- <filepath>\r
494                         {\r
495                                 if (sLine.Left(3).Compare(_T("---"))!=0)\r
496                                 {\r
497                                         //no starting "---" found\r
498                                         //seems to be either garbage or just\r
499                                         //a binary file. So start over...\r
500                                         state = 0;\r
501                                         nIndex--;\r
502                                         if (chunks)\r
503                                         {\r
504                                                 delete chunks;\r
505                                                 chunks = NULL;\r
506                                         }\r
507                                         break;\r
508                                 }\r
509                                 sLine = sLine.Mid(3);   //remove the "---"\r
510                                 sLine =sLine.Trim();\r
511                                 //at the end of the filepath there's a revision number...\r
512                                 int bracket = sLine.ReverseFind('(');\r
513                                 if (bracket < 0)\r
514                                         // some patch files can have another '(' char, especially ones created in Chinese OS\r
515                                         bracket = sLine.ReverseFind(0xff08);\r
516                                 CString num = sLine.Mid(bracket);               //num = "(revision xxxxx)"\r
517                                 num = num.Mid(num.Find(' '));\r
518                                 num = num.Trim(_T(" )"));\r
519                                 // here again, check for the Chinese bracket\r
520                                 num = num.Trim(0xff09);\r
521                                 chunks->sRevision = num;\r
522                                 if (bracket < 0)\r
523                                 {\r
524                                         if (chunks->sFilePath.IsEmpty())\r
525                                                 chunks->sFilePath = sLine.Trim();\r
526                                 }\r
527                                 else\r
528                                         chunks->sFilePath = sLine.Left(bracket-1).Trim();\r
529                                 if (chunks->sFilePath.Find('\t')>=0)\r
530                                 {\r
531                                         chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));\r
532                                 }\r
533                                 state++;\r
534                         }\r
535                 break;\r
536                 case 3: //+++ <filepath>\r
537                         {\r
538                                 if (sLine.Left(3).Compare(_T("+++"))!=0)\r
539                                 {\r
540                                         //no starting "+++" found\r
541                                         m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);\r
542                                         goto errorcleanup;\r
543                                 }\r
544                                 sLine = sLine.Mid(3);   //remove the "---"\r
545                                 sLine =sLine.Trim();\r
546                                 //at the end of the filepath there's a revision number...\r
547                                 int bracket = sLine.ReverseFind('(');\r
548                                 if (bracket < 0)\r
549                                         // some patch files can have another '(' char, especially ones created in Chinese OS\r
550                                         bracket = sLine.ReverseFind(0xff08);\r
551                                 CString num = sLine.Mid(bracket);               //num = "(revision xxxxx)"\r
552                                 num = num.Mid(num.Find(' '));\r
553                                 num = num.Trim(_T(" )"));\r
554                                 // here again, check for the Chinese bracket\r
555                                 num = num.Trim(0xff09);\r
556                                 chunks->sRevision2 = num;\r
557                                 if (bracket < 0)\r
558                                         chunks->sFilePath2 = sLine.Trim();\r
559                                 else\r
560                                         chunks->sFilePath2 = sLine.Left(bracket-1).Trim();\r
561                                 if (chunks->sFilePath2.Find('\t')>=0)\r
562                                 {\r
563                                         chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));\r
564                                 }\r
565                                 state++;\r
566                         }\r
567                 break;\r
568                 case 4: //@@ -xxx,xxx +xxx,xxx @@\r
569                         {\r
570                                 //start of a new chunk\r
571                                 if (sLine.Left(2).Compare(_T("@@"))!=0)\r
572                                 {\r
573                                         //chunk doesn't start with "@@"\r
574                                         //so there's garbage in between two file diffs\r
575                                         state = 0;\r
576                                         if (chunk)\r
577                                         {\r
578                                                 delete chunk;\r
579                                                 chunk = 0;\r
580                                                 if (chunks)\r
581                                                 {\r
582                                                         for (int i=0; i<chunks->chunks.GetCount(); i++)\r
583                                                         {\r
584                                                                 delete chunks->chunks.GetAt(i);\r
585                                                         }\r
586                                                         chunks->chunks.RemoveAll();\r
587                                                         delete chunks;\r
588                                                         chunks = NULL;\r
589                                                 }\r
590                                         }\r
591                                         break;          //skip the garbage\r
592                                 }\r
593                                 sLine = sLine.Mid(2);\r
594                                 sLine = sLine.Trim();\r
595                                 chunk = new Chunk();\r
596                                 CString sRemove = sLine.Left(sLine.Find(' '));\r
597                                 CString sAdd = sLine.Mid(sLine.Find(' '));\r
598                                 chunk->lRemoveStart = (-_ttol(sRemove));\r
599                                 if (sRemove.Find(',')>=0)\r
600                                 {\r
601                                         sRemove = sRemove.Mid(sRemove.Find(',')+1);\r
602                                         chunk->lRemoveLength = _ttol(sRemove);\r
603                                 }\r
604                                 else\r
605                                 {\r
606                                         chunk->lRemoveStart = 0;\r
607                                         chunk->lRemoveLength = (-_ttol(sRemove));\r
608                                 }\r
609                                 chunk->lAddStart = _ttol(sAdd);\r
610                                 if (sAdd.Find(',')>=0)\r
611                                 {\r
612                                         sAdd = sAdd.Mid(sAdd.Find(',')+1);\r
613                                         chunk->lAddLength = _ttol(sAdd);\r
614                                 }\r
615                                 else\r
616                                 {\r
617                                         chunk->lAddStart = 1;\r
618                                         chunk->lAddLength = _ttol(sAdd);\r
619                                 }\r
620                                 state++;\r
621                         }\r
622                 break;\r
623                 case 5: //[ |+|-] <sourceline>\r
624                         {\r
625                                 //this line is either a context line (with a ' ' in front)\r
626                                 //a line added (with a '+' in front)\r
627                                 //or a removed line (with a '-' in front)\r
628                                 TCHAR type;\r
629                                 if (sLine.IsEmpty())\r
630                                         type = ' ';\r
631                                 else\r
632                                         type = sLine.GetAt(0);\r
633                                 if (type == ' ')\r
634                                 {\r
635                                         //it's a context line - we don't use them here right now\r
636                                         //but maybe in the future the patch algorithm can be\r
637                                         //extended to use those in case the file to patch has\r
638                                         //already changed and no base file is around...\r
639                                         chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));\r
640                                         chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);\r
641                                         chunk->arEOLs.push_back(ending);\r
642                                         nContextLineCount++;\r
643                                 }\r
644                                 else if (type == '\\')\r
645                                 {\r
646                                         //it's a context line (sort of): \r
647                                         //warnings start with a '\' char (e.g. "\ No newline at end of file")\r
648                                         //so just ignore this...\r
649                                 }\r
650                                 else if (type == '-')\r
651                                 {\r
652                                         //a removed line\r
653                                         chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));\r
654                                         chunk->arLinesStates.Add(PATCHSTATE_REMOVED);\r
655                                         chunk->arEOLs.push_back(ending);\r
656                                         nRemoveLineCount++;\r
657                                 }\r
658                                 else if (type == '+')\r
659                                 {\r
660                                         //an added line\r
661                                         chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));\r
662                                         chunk->arLinesStates.Add(PATCHSTATE_ADDED);\r
663                                         chunk->arEOLs.push_back(ending);\r
664                                         nAddLineCount++;\r
665                                 }\r
666                                 else\r
667                                 {\r
668                                         //none of those lines! what the hell happened here?\r
669                                         m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);\r
670                                         goto errorcleanup;\r
671                                 }\r
672                                 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&\r
673                                         chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))\r
674                                 {\r
675                                         //chunk is finished\r
676                                         if (chunks)\r
677                                                 chunks->chunks.Add(chunk);\r
678                                         else\r
679                                                 delete chunk;\r
680                                         chunk = NULL;\r
681                                         nAddLineCount = 0;\r
682                                         nContextLineCount = 0;\r
683                                         nRemoveLineCount = 0;\r
684                                         state = 0;\r
685                                 }\r
686                         } \r
687                 break;\r
688                 default:\r
689                         ASSERT(FALSE);\r
690                 } // switch (state) \r
691         } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++) \r
692         if (chunk)\r
693         {\r
694                 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);\r
695                 goto errorcleanup;\r
696         }\r
697         if (chunks)\r
698                 m_arFileDiffs.Add(chunks);\r
699         return TRUE;\r
700 errorcleanup:\r
701         if (chunk)\r
702                 delete chunk;\r
703         if (chunks)\r
704         {\r
705                 for (int i=0; i<chunks->chunks.GetCount(); i++)\r
706                 {\r
707                         delete chunks->chunks.GetAt(i);\r
708                 }\r
709                 chunks->chunks.RemoveAll();\r
710                 delete chunks;\r
711         }\r
712         FreeMemory();\r
713         return FALSE;\r
714 }\r
715 \r
716 CString CPatch::GetFilename(int nIndex)\r
717 {\r
718         if (nIndex < 0)\r
719                 return _T("");\r
720         if (nIndex < m_arFileDiffs.GetCount())\r
721         {\r
722                 Chunks * c = m_arFileDiffs.GetAt(nIndex);\r
723                 CString filepath = Strip(c->sFilePath);\r
724                 return filepath;\r
725         }\r
726         return _T("");\r
727 }\r
728 \r
729 CString CPatch::GetRevision(int nIndex)\r
730 {\r
731         if (nIndex < 0)\r
732                 return 0;\r
733         if (nIndex < m_arFileDiffs.GetCount())\r
734         {\r
735                 Chunks * c = m_arFileDiffs.GetAt(nIndex);\r
736                 return c->sRevision;\r
737         }\r
738         return 0;\r
739 }\r
740 \r
741 CString CPatch::GetFilename2(int nIndex)\r
742 {\r
743         if (nIndex < 0)\r
744                 return _T("");\r
745         if (nIndex < m_arFileDiffs.GetCount())\r
746         {\r
747                 Chunks * c = m_arFileDiffs.GetAt(nIndex);\r
748                 CString filepath = Strip(c->sFilePath2);\r
749                 return filepath;\r
750         }\r
751         return _T("");\r
752 }\r
753 \r
754 CString CPatch::GetRevision2(int nIndex)\r
755 {\r
756         if (nIndex < 0)\r
757                 return 0;\r
758         if (nIndex < m_arFileDiffs.GetCount())\r
759         {\r
760                 Chunks * c = m_arFileDiffs.GetAt(nIndex);\r
761                 return c->sRevision2;\r
762         } \r
763         return 0;\r
764 }\r
765 \r
766 BOOL CPatch::PatchFile(const CString& sPath, const CString& sSavePath, const CString& sBaseFile)\r
767 {\r
768         if (PathIsDirectory(sPath))\r
769         {\r
770                 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);\r
771                 return FALSE;\r
772         }\r
773         // find the entry in the patch file which matches the full path given in sPath.\r
774         int nIndex = -1;\r
775         // use the longest path that matches\r
776         int nMaxMatch = 0;\r
777         for (int i=0; i<GetNumberOfFiles(); i++)\r
778         {\r
779                 CString temppath = sPath;\r
780                 CString temp = GetFilename(i);\r
781                 temppath.Replace('/', '\\');\r
782                 temp.Replace('/', '\\');\r
783                 if (temppath.Mid(temppath.GetLength()-temp.GetLength()-1, 1).CompareNoCase(_T("\\"))==0)\r
784                 {\r
785                         temppath = temppath.Right(temp.GetLength());\r
786                         if ((temp.CompareNoCase(temppath)==0))\r
787                         {\r
788                                 if (nMaxMatch < temp.GetLength())\r
789                                 {\r
790                                         nMaxMatch = temp.GetLength();\r
791                                         nIndex = i;\r
792                                 }\r
793                         }\r
794                 }\r
795                 else if (temppath.CompareNoCase(temp)==0)\r
796                 {\r
797                         if ((nIndex < 0)&&(! temp.IsEmpty()))\r
798                         {\r
799                                 nIndex = i;\r
800                         }\r
801                 }\r
802         }\r
803         if (nIndex < 0)\r
804         {\r
805                 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);\r
806                 return FALSE;\r
807         }\r
808 \r
809         CString sLine;\r
810         CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;\r
811         if (PathFileExists(sPatchFile))\r
812         {\r
813                 g_crasher.AddFile((LPCSTR)(LPCTSTR)sPatchFile, (LPCSTR)(LPCTSTR)_T("File to patch"));\r
814         }\r
815         CFileTextLines PatchLines;\r
816         CFileTextLines PatchLinesResult;\r
817         PatchLines.Load(sPatchFile);\r
818         PatchLinesResult = PatchLines;  //.Copy(PatchLines);\r
819         PatchLines.CopySettings(&PatchLinesResult);\r
820 \r
821         Chunks * chunks = m_arFileDiffs.GetAt(nIndex);\r
822 \r
823         for (int i=0; i<chunks->chunks.GetCount(); i++)\r
824         {\r
825                 Chunk * chunk = chunks->chunks.GetAt(i);\r
826                 LONG lRemoveLine = chunk->lRemoveStart;\r
827                 LONG lAddLine = chunk->lAddStart;\r
828                 for (int j=0; j<chunk->arLines.GetCount(); j++)\r
829                 {\r
830                         CString sPatchLine = chunk->arLines.GetAt(j);\r
831                         EOL ending = chunk->arEOLs[j];\r
832                         if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))\r
833                         {\r
834                                 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))\r
835                                 {\r
836                                         // convert the UTF-8 contents in CString sPatchLine into a CStringA\r
837                                         sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));\r
838                                 }\r
839                         }\r
840                         int nPatchState = (int)chunk->arLinesStates.GetAt(j);\r
841                         switch (nPatchState)\r
842                         {\r
843                         case PATCHSTATE_REMOVED:\r
844                                 {\r
845                                         if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))\r
846                                         {\r
847                                                 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);\r
848                                                 return FALSE; \r
849                                         }\r
850                                         if (lAddLine == 0)\r
851                                                 lAddLine = 1;\r
852                                         if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))\r
853                                         {\r
854                                                 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));\r
855                                                 return FALSE; \r
856                                         }\r
857                                         if (lAddLine > PatchLines.GetCount())\r
858                                         {\r
859                                                 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));\r
860                                                 return FALSE; \r
861                                         }\r
862                                         PatchLines.RemoveAt(lAddLine-1);\r
863                                 } \r
864                                 break;\r
865                         case PATCHSTATE_ADDED:\r
866                                 {\r
867                                         if (lAddLine == 0)\r
868                                                 lAddLine = 1;\r
869                                         PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);\r
870                                         lAddLine++;\r
871                                 }\r
872                                 break;\r
873                         case PATCHSTATE_CONTEXT:\r
874                                 {\r
875                                         if (lAddLine > PatchLines.GetCount())\r
876                                         {\r
877                                                 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);\r
878                                                 return FALSE; \r
879                                         }\r
880                                         if (lAddLine == 0)\r
881                                                 lAddLine++;\r
882                                         if (lRemoveLine == 0)\r
883                                                 lRemoveLine++;\r
884                                         if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&\r
885                                                 (!HasExpandedKeyWords(sPatchLine)) &&\r
886                                                 (lRemoveLine <= PatchLines.GetCount()) &&\r
887                                                 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))\r
888                                         {\r
889                                                 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))\r
890                                                         lAddLine++;\r
891                                                 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))\r
892                                                         lAddLine += 2;\r
893                                                 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))\r
894                                                         lRemoveLine++;\r
895                                                 else\r
896                                                 {\r
897                                                         m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));\r
898                                                         return FALSE; \r
899                                                 }\r
900                                         } \r
901                                         lAddLine++;\r
902                                         lRemoveLine++;\r
903                                 }\r
904                                 break;\r
905                         default:\r
906                                 ASSERT(FALSE);\r
907                                 break;\r
908                         } // switch (nPatchState) \r
909                 } // for (j=0; j<chunk->arLines.GetCount(); j++) \r
910         } // for (int i=0; i<chunks->chunks.GetCount(); i++) \r
911         if (!sSavePath.IsEmpty())\r
912         {\r
913                 PatchLines.Save(sSavePath, false);\r
914         }\r
915         return TRUE;\r
916 }\r
917 \r
918 BOOL CPatch::HasExpandedKeyWords(const CString& line)\r
919 {\r
920         if (line.Find(_T("$LastChangedDate"))>=0)\r
921                 return TRUE;\r
922         if (line.Find(_T("$Date"))>=0)\r
923                 return TRUE;\r
924         if (line.Find(_T("$LastChangedRevision"))>=0)\r
925                 return TRUE;\r
926         if (line.Find(_T("$Rev"))>=0)\r
927                 return TRUE;\r
928         if (line.Find(_T("$LastChangedBy"))>=0)\r
929                 return TRUE;\r
930         if (line.Find(_T("$Author"))>=0)\r
931                 return TRUE;\r
932         if (line.Find(_T("$HeadURL"))>=0)\r
933                 return TRUE;\r
934         if (line.Find(_T("$URL"))>=0)\r
935                 return TRUE;\r
936         if (line.Find(_T("$Id"))>=0)\r
937                 return TRUE;\r
938         return FALSE;\r
939 }\r
940 \r
941 CString CPatch::CheckPatchPath(const CString& path)\r
942 {\r
943         //first check if the path already matches\r
944         if (CountMatches(path) > (GetNumberOfFiles()/3))\r
945                 return path;\r
946         //now go up the tree and try again\r
947         CString upperpath = path;\r
948         while (upperpath.ReverseFind('\\')>0)\r
949         {\r
950                 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));\r
951                 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))\r
952                         return upperpath;\r
953         }\r
954         //still no match found. So try sub folders\r
955         bool isDir = false;\r
956         CString subpath;\r
957         CDirFileEnum filefinder(path);\r
958         while (filefinder.NextFile(subpath, &isDir))\r
959         {\r
960                 if (!isDir)\r
961                         continue;\r
962                 if (g_GitAdminDir.IsAdminDirPath(subpath))\r
963                         continue;\r
964                 if (CountMatches(subpath) > (GetNumberOfFiles()/3))\r
965                         return subpath;\r
966         }\r
967         \r
968         // if a patch file only contains newly added files\r
969         // we can't really find the correct path.\r
970         // But: we can compare paths strings without the filenames\r
971         // and check if at least those match\r
972         upperpath = path;\r
973         while (upperpath.ReverseFind('\\')>0)\r
974         {\r
975                 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));\r
976                 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))\r
977                         return upperpath;\r
978         }\r
979         \r
980         return path;\r
981 }\r
982 \r
983 int CPatch::CountMatches(const CString& path)\r
984 {\r
985         int matches = 0;\r
986         for (int i=0; i<GetNumberOfFiles(); ++i)\r
987         {\r
988                 CString temp = GetFilename(i);\r
989                 temp.Replace('/', '\\');\r
990                 if (PathIsRelative(temp))\r
991                         temp = path + _T("\\")+ temp;\r
992                 if (PathFileExists(temp))\r
993                         matches++;\r
994         }\r
995         return matches;\r
996 }\r
997 \r
998 int CPatch::CountDirMatches(const CString& path)\r
999 {\r
1000         int matches = 0;\r
1001         for (int i=0; i<GetNumberOfFiles(); ++i)\r
1002         {\r
1003                 CString temp = GetFilename(i);\r
1004                 temp.Replace('/', '\\');\r
1005                 if (PathIsRelative(temp))\r
1006                         temp = path + _T("\\")+ temp;\r
1007                 // remove the filename\r
1008                 temp = temp.Left(temp.ReverseFind('\\'));\r
1009                 if (PathFileExists(temp))\r
1010                         matches++;\r
1011         }\r
1012         return matches;\r
1013 }\r
1014 \r
1015 BOOL CPatch::StripPrefixes(const CString& path)\r
1016 {\r
1017         int nSlashesMax = 0;\r
1018         for (int i=0; i<GetNumberOfFiles(); i++)\r
1019         {\r
1020                 CString filename = GetFilename(i);\r
1021                 filename.Replace('/','\\');\r
1022                 int nSlashes = filename.Replace('\\','/');\r
1023                 nSlashesMax = max(nSlashesMax,nSlashes);\r
1024         }\r
1025 \r
1026         for (int nStrip=1;nStrip<nSlashesMax;nStrip++)\r
1027         {\r
1028                 m_nStrip = nStrip;\r
1029                 if ( CountMatches(path) > GetNumberOfFiles()/3 )\r
1030                 {\r
1031                         // Use current m_nStrip\r
1032                         return TRUE;\r
1033                 }\r
1034         }\r
1035 \r
1036         // Stripping doesn't help so reset it again\r
1037         m_nStrip = 0;\r
1038         return FALSE;\r
1039 }\r
1040 \r
1041 CString CPatch::Strip(const CString& filename)\r
1042 {\r
1043         CString s = filename;\r
1044         if ( m_nStrip>0 )\r
1045         {\r
1046                 // Remove windows drive letter "c:"\r
1047                 if ( s.GetLength()>2 && s[1]==':')\r
1048                 {\r
1049                         s = s.Mid(2);\r
1050                 }\r
1051 \r
1052                 for (int nStrip=1;nStrip<=m_nStrip;nStrip++)\r
1053                 {\r
1054                         // "/home/ts/my-working-copy/dir/file.txt"\r
1055                         //  "home/ts/my-working-copy/dir/file.txt"\r
1056                         //       "ts/my-working-copy/dir/file.txt"\r
1057                         //          "my-working-copy/dir/file.txt"\r
1058                         //                          "dir/file.txt"\r
1059                         s = s.Mid(s.FindOneOf(_T("/\\"))+1);\r
1060                 }\r
1061         }\r
1062         return s;\r
1063 }\r
1064 \r
1065 CString CPatch::RemoveUnicodeBOM(const CString& str)\r
1066 {\r
1067         if (str.GetLength()==0)\r
1068                 return str;\r
1069         if (str[0] == 0xFEFF)\r
1070                 return str.Mid(1);\r
1071         return str;\r
1072 }\r