OSDN Git Service

Merge from feature_merge branch. Build TortoiseMerge successfully.
[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::OpenUnifiedDiffFile(const CString& filename)\r
61 {\r
62         CString sLine;\r
63         EOL ending = EOL_NOENDING;\r
64         INT_PTR nIndex = 0;\r
65         INT_PTR nLineCount = 0;\r
66         g_crasher.AddFile((LPCSTR)(LPCTSTR)filename, (LPCSTR)(LPCTSTR)_T("unified diff file"));\r
67 \r
68         CFileTextLines PatchLines;\r
69         if (!PatchLines.Load(filename))\r
70         {\r
71                 m_sErrorMessage = PatchLines.GetErrorString();\r
72                 return FALSE;\r
73         }\r
74         m_UnicodeType = PatchLines.GetUnicodeType();\r
75         FreeMemory();\r
76         nLineCount = PatchLines.GetCount();\r
77         //now we got all the lines of the patch file\r
78         //in our array - parsing can start...\r
79 \r
80         for(nIndex=0;PatchLines.GetCount();nIndex++)\r
81         {\r
82                 sLine = PatchLines.GetAt(nIndex);\r
83                 if(sLine.Left(10).Compare(_T("diff --git")) == 0)\r
84                 {\r
85                         this->m_IsGitPatch=true;\r
86                         break;\r
87                 }\r
88         }\r
89 \r
90         //first, skip possible garbage at the beginning\r
91         //garbage is finished when a line starts with "Index: "\r
92         //and the next line consists of only "=" characters\r
93         for (nIndex=0; nIndex<PatchLines.GetCount(); nIndex++)\r
94         {\r
95                 sLine = PatchLines.GetAt(nIndex);\r
96                 if (sLine.Left(4).Compare(_T("--- "))==0)\r
97                         break;\r
98                 if ((nIndex+1)<PatchLines.GetCount())\r
99                 {\r
100                         sLine = PatchLines.GetAt(nIndex+1);\r
101 \r
102                         if(sLine.IsEmpty()&&m_IsGitPatch)\r
103                                 continue;\r
104 \r
105                         sLine.Replace(_T("="), _T(""));\r
106                         if (sLine.IsEmpty())\r
107                                 break;\r
108                 }\r
109         }\r
110         if ((PatchLines.GetCount()-nIndex) < 2)\r
111         {\r
112                 //no file entry found.\r
113                 m_sErrorMessage.LoadString(IDS_ERR_PATCH_NOINDEX);\r
114                 return FALSE;\r
115         }\r
116 \r
117         //from this point on we have the real unified diff data\r
118         int state = 0;\r
119         Chunks * chunks = NULL;\r
120         Chunk * chunk = NULL;\r
121         int nAddLineCount = 0;\r
122         int nRemoveLineCount = 0;\r
123         int nContextLineCount = 0;\r
124         for ( ;nIndex<PatchLines.GetCount(); nIndex++)\r
125         {\r
126                 sLine = PatchLines.GetAt(nIndex);\r
127                 ending = PatchLines.GetLineEnding(nIndex);\r
128                 if (ending != EOL_NOENDING)\r
129                         ending = EOL_AUTOLINE;\r
130                 if (state == 0)\r
131                 {\r
132                         if ((sLine.Left(4).Compare(_T("--- "))==0)&&((sLine.Find('\t') >= 0)||this->m_IsGitPatch))\r
133                         {\r
134                                 state = 2;\r
135                                 if (chunks)\r
136                                 {\r
137                                         //this is a new file diff, so add the last one to \r
138                                         //our array.\r
139                                         m_arFileDiffs.Add(chunks);\r
140                                 }\r
141                                 chunks = new Chunks();\r
142                                 \r
143                                 int nTab = sLine.Find('\t');\r
144 \r
145                                 int filestart = 4;\r
146                                 if(m_IsGitPatch)\r
147                                 {\r
148                                         nTab=sLine.GetLength();\r
149                                         filestart = 6;\r
150                                 }\r
151 \r
152                                 if (nTab >= 0)\r
153                                 {\r
154                                         chunks->sFilePath = sLine.Mid(filestart, nTab-filestart).Trim();\r
155                                 }\r
156                         }\r
157                 }\r
158                 switch (state)\r
159                 {\r
160                 case 0: //Index: <filepath>\r
161                         {\r
162                                 CString nextLine;\r
163                                 if ((nIndex+1)<PatchLines.GetCount())\r
164                                 {\r
165                                         nextLine = PatchLines.GetAt(nIndex+1);\r
166                                         if (!nextLine.IsEmpty())\r
167                                         {\r
168                                                 nextLine.Replace(_T("="), _T(""));\r
169                                                 if (nextLine.IsEmpty())\r
170                                                 {\r
171                                                         if (chunks)\r
172                                                         {\r
173                                                                 //this is a new file diff, so add the last one to \r
174                                                                 //our array.\r
175                                                                 m_arFileDiffs.Add(chunks);\r
176                                                         }\r
177                                                         chunks = new Chunks();\r
178                                                         int nColon = sLine.Find(':');\r
179                                                         if (nColon >= 0)\r
180                                                         {\r
181                                                                 chunks->sFilePath = sLine.Mid(nColon+1).Trim();\r
182                                                                 if (chunks->sFilePath.Find('\t')>=0)\r
183                                                                         chunks->sFilePath.Left(chunks->sFilePath.Find('\t')).TrimRight();\r
184                                                                 if (chunks->sFilePath.Right(9).Compare(_T("(deleted)"))==0)\r
185                                                                         chunks->sFilePath.Left(chunks->sFilePath.GetLength()-9).TrimRight();\r
186                                                                 if (chunks->sFilePath.Right(7).Compare(_T("(added)"))==0)\r
187                                                                         chunks->sFilePath.Left(chunks->sFilePath.GetLength()-7).TrimRight();\r
188                                                         }\r
189                                                         state++;\r
190                                                 }\r
191                                         }\r
192                                 }\r
193                                 if (state == 0)\r
194                                 {\r
195                                         if (nIndex > 0)\r
196                                         {\r
197                                                 nIndex--;\r
198                                                 state = 4;\r
199                                                 if (chunks == NULL)\r
200                                                 {\r
201                                                         //the line\r
202                                                         //Index: <filepath>\r
203                                                         //was not found at the start of a file diff!\r
204                                                         break;\r
205                                                 }\r
206                                         }\r
207                                 }\r
208                         } \r
209                 break;\r
210                 case 1: //====================\r
211                         {\r
212                                 sLine.Replace(_T("="), _T(""));\r
213                                 if (sLine.IsEmpty())\r
214                                 {\r
215                                         // if the next line is already the start of the chunk,\r
216                                         // then the patch/diff file was not created by svn. But we\r
217                                         // still try to use it\r
218                                         if (PatchLines.GetCount() > (nIndex + 1))\r
219                                         {\r
220 \r
221                                                 if (PatchLines.GetAt(nIndex+1).Left(2).Compare(_T("@@"))==0)\r
222                                                 {\r
223                                                         state += 2;\r
224                                                 }\r
225                                         }\r
226                                         state++;\r
227                                 }\r
228                                 else\r
229                                 {\r
230                                         //the line\r
231                                         //=========================\r
232                                         //was not found\r
233                                         m_sErrorMessage.Format(IDS_ERR_PATCH_NOEQUATIONCHARLINE, nIndex);\r
234                                         goto errorcleanup;\r
235                                 }\r
236                         }\r
237                 break;\r
238                 case 2: //--- <filepath>\r
239                         {\r
240                                 if (sLine.Left(3).Compare(_T("---"))!=0)\r
241                                 {\r
242                                         //no starting "---" found\r
243                                         //seems to be either garbage or just\r
244                                         //a binary file. So start over...\r
245                                         state = 0;\r
246                                         nIndex--;\r
247                                         if (chunks)\r
248                                         {\r
249                                                 delete chunks;\r
250                                                 chunks = NULL;\r
251                                         }\r
252                                         break;\r
253                                 }\r
254                                 sLine = sLine.Mid(3);   //remove the "---"\r
255                                 sLine =sLine.Trim();\r
256                                 //at the end of the filepath there's a revision number...\r
257                                 int bracket = sLine.ReverseFind('(');\r
258                                 if (bracket < 0)\r
259                                         // some patch files can have another '(' char, especially ones created in Chinese OS\r
260                                         bracket = sLine.ReverseFind(0xff08);\r
261                                 CString num = sLine.Mid(bracket);               //num = "(revision xxxxx)"\r
262                                 num = num.Mid(num.Find(' '));\r
263                                 num = num.Trim(_T(" )"));\r
264                                 // here again, check for the Chinese bracket\r
265                                 num = num.Trim(0xff09);\r
266                                 chunks->sRevision = num;\r
267                                 if (bracket < 0)\r
268                                 {\r
269                                         if (chunks->sFilePath.IsEmpty())\r
270                                                 chunks->sFilePath = sLine.Trim();\r
271                                 }\r
272                                 else\r
273                                         chunks->sFilePath = sLine.Left(bracket-1).Trim();\r
274                                 if (chunks->sFilePath.Find('\t')>=0)\r
275                                 {\r
276                                         chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));\r
277                                 }\r
278                                 state++;\r
279                         }\r
280                 break;\r
281                 case 3: //+++ <filepath>\r
282                         {\r
283                                 if (sLine.Left(3).Compare(_T("+++"))!=0)\r
284                                 {\r
285                                         //no starting "+++" found\r
286                                         m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);\r
287                                         goto errorcleanup;\r
288                                 }\r
289                                 sLine = sLine.Mid(3);   //remove the "---"\r
290                                 sLine =sLine.Trim();\r
291                                 //at the end of the filepath there's a revision number...\r
292                                 int bracket = sLine.ReverseFind('(');\r
293                                 if (bracket < 0)\r
294                                         // some patch files can have another '(' char, especially ones created in Chinese OS\r
295                                         bracket = sLine.ReverseFind(0xff08);\r
296                                 CString num = sLine.Mid(bracket);               //num = "(revision xxxxx)"\r
297                                 num = num.Mid(num.Find(' '));\r
298                                 num = num.Trim(_T(" )"));\r
299                                 // here again, check for the Chinese bracket\r
300                                 num = num.Trim(0xff09);\r
301                                 chunks->sRevision2 = num;\r
302                                 if (bracket < 0)\r
303                                         chunks->sFilePath2 = sLine.Trim();\r
304                                 else\r
305                                         chunks->sFilePath2 = sLine.Left(bracket-1).Trim();\r
306                                 if (chunks->sFilePath2.Find('\t')>=0)\r
307                                 {\r
308                                         chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));\r
309                                 }\r
310                                 state++;\r
311                         }\r
312                 break;\r
313                 case 4: //@@ -xxx,xxx +xxx,xxx @@\r
314                         {\r
315                                 //start of a new chunk\r
316                                 if (sLine.Left(2).Compare(_T("@@"))!=0)\r
317                                 {\r
318                                         //chunk doesn't start with "@@"\r
319                                         //so there's garbage in between two file diffs\r
320                                         state = 0;\r
321                                         if (chunk)\r
322                                         {\r
323                                                 delete chunk;\r
324                                                 chunk = 0;\r
325                                                 if (chunks)\r
326                                                 {\r
327                                                         for (int i=0; i<chunks->chunks.GetCount(); i++)\r
328                                                         {\r
329                                                                 delete chunks->chunks.GetAt(i);\r
330                                                         }\r
331                                                         chunks->chunks.RemoveAll();\r
332                                                         delete chunks;\r
333                                                         chunks = NULL;\r
334                                                 }\r
335                                         }\r
336                                         break;          //skip the garbage\r
337                                 }\r
338                                 sLine = sLine.Mid(2);\r
339                                 sLine = sLine.Trim();\r
340                                 chunk = new Chunk();\r
341                                 CString sRemove = sLine.Left(sLine.Find(' '));\r
342                                 CString sAdd = sLine.Mid(sLine.Find(' '));\r
343                                 chunk->lRemoveStart = (-_ttol(sRemove));\r
344                                 if (sRemove.Find(',')>=0)\r
345                                 {\r
346                                         sRemove = sRemove.Mid(sRemove.Find(',')+1);\r
347                                         chunk->lRemoveLength = _ttol(sRemove);\r
348                                 }\r
349                                 else\r
350                                 {\r
351                                         chunk->lRemoveStart = 0;\r
352                                         chunk->lRemoveLength = (-_ttol(sRemove));\r
353                                 }\r
354                                 chunk->lAddStart = _ttol(sAdd);\r
355                                 if (sAdd.Find(',')>=0)\r
356                                 {\r
357                                         sAdd = sAdd.Mid(sAdd.Find(',')+1);\r
358                                         chunk->lAddLength = _ttol(sAdd);\r
359                                 }\r
360                                 else\r
361                                 {\r
362                                         chunk->lAddStart = 1;\r
363                                         chunk->lAddLength = _ttol(sAdd);\r
364                                 }\r
365                                 state++;\r
366                         }\r
367                 break;\r
368                 case 5: //[ |+|-] <sourceline>\r
369                         {\r
370                                 //this line is either a context line (with a ' ' in front)\r
371                                 //a line added (with a '+' in front)\r
372                                 //or a removed line (with a '-' in front)\r
373                                 TCHAR type;\r
374                                 if (sLine.IsEmpty())\r
375                                         type = ' ';\r
376                                 else\r
377                                         type = sLine.GetAt(0);\r
378                                 if (type == ' ')\r
379                                 {\r
380                                         //it's a context line - we don't use them here right now\r
381                                         //but maybe in the future the patch algorithm can be\r
382                                         //extended to use those in case the file to patch has\r
383                                         //already changed and no base file is around...\r
384                                         chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));\r
385                                         chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);\r
386                                         chunk->arEOLs.push_back(ending);\r
387                                         nContextLineCount++;\r
388                                 }\r
389                                 else if (type == '\\')\r
390                                 {\r
391                                         //it's a context line (sort of): \r
392                                         //warnings start with a '\' char (e.g. "\ No newline at end of file")\r
393                                         //so just ignore this...\r
394                                 }\r
395                                 else if (type == '-')\r
396                                 {\r
397                                         //a removed line\r
398                                         chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));\r
399                                         chunk->arLinesStates.Add(PATCHSTATE_REMOVED);\r
400                                         chunk->arEOLs.push_back(ending);\r
401                                         nRemoveLineCount++;\r
402                                 }\r
403                                 else if (type == '+')\r
404                                 {\r
405                                         //an added line\r
406                                         chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));\r
407                                         chunk->arLinesStates.Add(PATCHSTATE_ADDED);\r
408                                         chunk->arEOLs.push_back(ending);\r
409                                         nAddLineCount++;\r
410                                 }\r
411                                 else\r
412                                 {\r
413                                         //none of those lines! what the hell happened here?\r
414                                         m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);\r
415                                         goto errorcleanup;\r
416                                 }\r
417                                 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&\r
418                                         chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))\r
419                                 {\r
420                                         //chunk is finished\r
421                                         if (chunks)\r
422                                                 chunks->chunks.Add(chunk);\r
423                                         else\r
424                                                 delete chunk;\r
425                                         chunk = NULL;\r
426                                         nAddLineCount = 0;\r
427                                         nContextLineCount = 0;\r
428                                         nRemoveLineCount = 0;\r
429                                         state = 0;\r
430                                 }\r
431                         } \r
432                 break;\r
433                 default:\r
434                         ASSERT(FALSE);\r
435                 } // switch (state) \r
436         } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++) \r
437         if (chunk)\r
438         {\r
439                 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);\r
440                 goto errorcleanup;\r
441         }\r
442         if (chunks)\r
443                 m_arFileDiffs.Add(chunks);\r
444         return TRUE;\r
445 errorcleanup:\r
446         if (chunk)\r
447                 delete chunk;\r
448         if (chunks)\r
449         {\r
450                 for (int i=0; i<chunks->chunks.GetCount(); i++)\r
451                 {\r
452                         delete chunks->chunks.GetAt(i);\r
453                 }\r
454                 chunks->chunks.RemoveAll();\r
455                 delete chunks;\r
456         }\r
457         FreeMemory();\r
458         return FALSE;\r
459 }\r
460 \r
461 CString CPatch::GetFilename(int nIndex)\r
462 {\r
463         if (nIndex < 0)\r
464                 return _T("");\r
465         if (nIndex < m_arFileDiffs.GetCount())\r
466         {\r
467                 Chunks * c = m_arFileDiffs.GetAt(nIndex);\r
468                 CString filepath = Strip(c->sFilePath);\r
469                 return filepath;\r
470         }\r
471         return _T("");\r
472 }\r
473 \r
474 CString CPatch::GetRevision(int nIndex)\r
475 {\r
476         if (nIndex < 0)\r
477                 return 0;\r
478         if (nIndex < m_arFileDiffs.GetCount())\r
479         {\r
480                 Chunks * c = m_arFileDiffs.GetAt(nIndex);\r
481                 return c->sRevision;\r
482         }\r
483         return 0;\r
484 }\r
485 \r
486 CString CPatch::GetFilename2(int nIndex)\r
487 {\r
488         if (nIndex < 0)\r
489                 return _T("");\r
490         if (nIndex < m_arFileDiffs.GetCount())\r
491         {\r
492                 Chunks * c = m_arFileDiffs.GetAt(nIndex);\r
493                 CString filepath = Strip(c->sFilePath2);\r
494                 return filepath;\r
495         }\r
496         return _T("");\r
497 }\r
498 \r
499 CString CPatch::GetRevision2(int nIndex)\r
500 {\r
501         if (nIndex < 0)\r
502                 return 0;\r
503         if (nIndex < m_arFileDiffs.GetCount())\r
504         {\r
505                 Chunks * c = m_arFileDiffs.GetAt(nIndex);\r
506                 return c->sRevision2;\r
507         } \r
508         return 0;\r
509 }\r
510 \r
511 BOOL CPatch::PatchFile(const CString& sPath, const CString& sSavePath, const CString& sBaseFile)\r
512 {\r
513         if (PathIsDirectory(sPath))\r
514         {\r
515                 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);\r
516                 return FALSE;\r
517         }\r
518         // find the entry in the patch file which matches the full path given in sPath.\r
519         int nIndex = -1;\r
520         // use the longest path that matches\r
521         int nMaxMatch = 0;\r
522         for (int i=0; i<GetNumberOfFiles(); i++)\r
523         {\r
524                 CString temppath = sPath;\r
525                 CString temp = GetFilename(i);\r
526                 temppath.Replace('/', '\\');\r
527                 temp.Replace('/', '\\');\r
528                 if (temppath.Mid(temppath.GetLength()-temp.GetLength()-1, 1).CompareNoCase(_T("\\"))==0)\r
529                 {\r
530                         temppath = temppath.Right(temp.GetLength());\r
531                         if ((temp.CompareNoCase(temppath)==0))\r
532                         {\r
533                                 if (nMaxMatch < temp.GetLength())\r
534                                 {\r
535                                         nMaxMatch = temp.GetLength();\r
536                                         nIndex = i;\r
537                                 }\r
538                         }\r
539                 }\r
540                 else if (temppath.CompareNoCase(temp)==0)\r
541                 {\r
542                         if ((nIndex < 0)&&(! temp.IsEmpty()))\r
543                         {\r
544                                 nIndex = i;\r
545                         }\r
546                 }\r
547         }\r
548         if (nIndex < 0)\r
549         {\r
550                 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);\r
551                 return FALSE;\r
552         }\r
553 \r
554         CString sLine;\r
555         CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;\r
556         if (PathFileExists(sPatchFile))\r
557         {\r
558                 g_crasher.AddFile((LPCSTR)(LPCTSTR)sPatchFile, (LPCSTR)(LPCTSTR)_T("File to patch"));\r
559         }\r
560         CFileTextLines PatchLines;\r
561         CFileTextLines PatchLinesResult;\r
562         PatchLines.Load(sPatchFile);\r
563         PatchLinesResult = PatchLines;  //.Copy(PatchLines);\r
564         PatchLines.CopySettings(&PatchLinesResult);\r
565 \r
566         Chunks * chunks = m_arFileDiffs.GetAt(nIndex);\r
567 \r
568         for (int i=0; i<chunks->chunks.GetCount(); i++)\r
569         {\r
570                 Chunk * chunk = chunks->chunks.GetAt(i);\r
571                 LONG lRemoveLine = chunk->lRemoveStart;\r
572                 LONG lAddLine = chunk->lAddStart;\r
573                 for (int j=0; j<chunk->arLines.GetCount(); j++)\r
574                 {\r
575                         CString sPatchLine = chunk->arLines.GetAt(j);\r
576                         EOL ending = chunk->arEOLs[j];\r
577                         if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))\r
578                         {\r
579                                 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))\r
580                                 {\r
581                                         // convert the UTF-8 contents in CString sPatchLine into a CStringA\r
582                                         sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));\r
583                                 }\r
584                         }\r
585                         int nPatchState = (int)chunk->arLinesStates.GetAt(j);\r
586                         switch (nPatchState)\r
587                         {\r
588                         case PATCHSTATE_REMOVED:\r
589                                 {\r
590                                         if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))\r
591                                         {\r
592                                                 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);\r
593                                                 return FALSE; \r
594                                         }\r
595                                         if (lAddLine == 0)\r
596                                                 lAddLine = 1;\r
597                                         if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))\r
598                                         {\r
599                                                 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));\r
600                                                 return FALSE; \r
601                                         }\r
602                                         if (lAddLine > PatchLines.GetCount())\r
603                                         {\r
604                                                 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));\r
605                                                 return FALSE; \r
606                                         }\r
607                                         PatchLines.RemoveAt(lAddLine-1);\r
608                                 } \r
609                                 break;\r
610                         case PATCHSTATE_ADDED:\r
611                                 {\r
612                                         if (lAddLine == 0)\r
613                                                 lAddLine = 1;\r
614                                         PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);\r
615                                         lAddLine++;\r
616                                 }\r
617                                 break;\r
618                         case PATCHSTATE_CONTEXT:\r
619                                 {\r
620                                         if (lAddLine > PatchLines.GetCount())\r
621                                         {\r
622                                                 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);\r
623                                                 return FALSE; \r
624                                         }\r
625                                         if (lAddLine == 0)\r
626                                                 lAddLine++;\r
627                                         if (lRemoveLine == 0)\r
628                                                 lRemoveLine++;\r
629                                         if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&\r
630                                                 (!HasExpandedKeyWords(sPatchLine)) &&\r
631                                                 (lRemoveLine <= PatchLines.GetCount()) &&\r
632                                                 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))\r
633                                         {\r
634                                                 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))\r
635                                                         lAddLine++;\r
636                                                 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))\r
637                                                         lAddLine += 2;\r
638                                                 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))\r
639                                                         lRemoveLine++;\r
640                                                 else\r
641                                                 {\r
642                                                         m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));\r
643                                                         return FALSE; \r
644                                                 }\r
645                                         } \r
646                                         lAddLine++;\r
647                                         lRemoveLine++;\r
648                                 }\r
649                                 break;\r
650                         default:\r
651                                 ASSERT(FALSE);\r
652                                 break;\r
653                         } // switch (nPatchState) \r
654                 } // for (j=0; j<chunk->arLines.GetCount(); j++) \r
655         } // for (int i=0; i<chunks->chunks.GetCount(); i++) \r
656         if (!sSavePath.IsEmpty())\r
657         {\r
658                 PatchLines.Save(sSavePath, false);\r
659         }\r
660         return TRUE;\r
661 }\r
662 \r
663 BOOL CPatch::HasExpandedKeyWords(const CString& line)\r
664 {\r
665         if (line.Find(_T("$LastChangedDate"))>=0)\r
666                 return TRUE;\r
667         if (line.Find(_T("$Date"))>=0)\r
668                 return TRUE;\r
669         if (line.Find(_T("$LastChangedRevision"))>=0)\r
670                 return TRUE;\r
671         if (line.Find(_T("$Rev"))>=0)\r
672                 return TRUE;\r
673         if (line.Find(_T("$LastChangedBy"))>=0)\r
674                 return TRUE;\r
675         if (line.Find(_T("$Author"))>=0)\r
676                 return TRUE;\r
677         if (line.Find(_T("$HeadURL"))>=0)\r
678                 return TRUE;\r
679         if (line.Find(_T("$URL"))>=0)\r
680                 return TRUE;\r
681         if (line.Find(_T("$Id"))>=0)\r
682                 return TRUE;\r
683         return FALSE;\r
684 }\r
685 \r
686 CString CPatch::CheckPatchPath(const CString& path)\r
687 {\r
688         //first check if the path already matches\r
689         if (CountMatches(path) > (GetNumberOfFiles()/3))\r
690                 return path;\r
691         //now go up the tree and try again\r
692         CString upperpath = path;\r
693         while (upperpath.ReverseFind('\\')>0)\r
694         {\r
695                 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));\r
696                 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))\r
697                         return upperpath;\r
698         }\r
699         //still no match found. So try sub folders\r
700         bool isDir = false;\r
701         CString subpath;\r
702         CDirFileEnum filefinder(path);\r
703         while (filefinder.NextFile(subpath, &isDir))\r
704         {\r
705                 if (!isDir)\r
706                         continue;\r
707                 if (g_GitAdminDir.IsAdminDirPath(subpath))\r
708                         continue;\r
709                 if (CountMatches(subpath) > (GetNumberOfFiles()/3))\r
710                         return subpath;\r
711         }\r
712         \r
713         // if a patch file only contains newly added files\r
714         // we can't really find the correct path.\r
715         // But: we can compare paths strings without the filenames\r
716         // and check if at least those match\r
717         upperpath = path;\r
718         while (upperpath.ReverseFind('\\')>0)\r
719         {\r
720                 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));\r
721                 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))\r
722                         return upperpath;\r
723         }\r
724         \r
725         return path;\r
726 }\r
727 \r
728 int CPatch::CountMatches(const CString& path)\r
729 {\r
730         int matches = 0;\r
731         for (int i=0; i<GetNumberOfFiles(); ++i)\r
732         {\r
733                 CString temp = GetFilename(i);\r
734                 temp.Replace('/', '\\');\r
735                 if (PathIsRelative(temp))\r
736                         temp = path + _T("\\")+ temp;\r
737                 if (PathFileExists(temp))\r
738                         matches++;\r
739         }\r
740         return matches;\r
741 }\r
742 \r
743 int CPatch::CountDirMatches(const CString& path)\r
744 {\r
745         int matches = 0;\r
746         for (int i=0; i<GetNumberOfFiles(); ++i)\r
747         {\r
748                 CString temp = GetFilename(i);\r
749                 temp.Replace('/', '\\');\r
750                 if (PathIsRelative(temp))\r
751                         temp = path + _T("\\")+ temp;\r
752                 // remove the filename\r
753                 temp = temp.Left(temp.ReverseFind('\\'));\r
754                 if (PathFileExists(temp))\r
755                         matches++;\r
756         }\r
757         return matches;\r
758 }\r
759 \r
760 BOOL CPatch::StripPrefixes(const CString& path)\r
761 {\r
762         int nSlashesMax = 0;\r
763         for (int i=0; i<GetNumberOfFiles(); i++)\r
764         {\r
765                 CString filename = GetFilename(i);\r
766                 filename.Replace('/','\\');\r
767                 int nSlashes = filename.Replace('\\','/');\r
768                 nSlashesMax = max(nSlashesMax,nSlashes);\r
769         }\r
770 \r
771         for (int nStrip=1;nStrip<nSlashesMax;nStrip++)\r
772         {\r
773                 m_nStrip = nStrip;\r
774                 if ( CountMatches(path) > GetNumberOfFiles()/3 )\r
775                 {\r
776                         // Use current m_nStrip\r
777                         return TRUE;\r
778                 }\r
779         }\r
780 \r
781         // Stripping doesn't help so reset it again\r
782         m_nStrip = 0;\r
783         return FALSE;\r
784 }\r
785 \r
786 CString CPatch::Strip(const CString& filename)\r
787 {\r
788         CString s = filename;\r
789         if ( m_nStrip>0 )\r
790         {\r
791                 // Remove windows drive letter "c:"\r
792                 if ( s.GetLength()>2 && s[1]==':')\r
793                 {\r
794                         s = s.Mid(2);\r
795                 }\r
796 \r
797                 for (int nStrip=1;nStrip<=m_nStrip;nStrip++)\r
798                 {\r
799                         // "/home/ts/my-working-copy/dir/file.txt"\r
800                         //  "home/ts/my-working-copy/dir/file.txt"\r
801                         //       "ts/my-working-copy/dir/file.txt"\r
802                         //          "my-working-copy/dir/file.txt"\r
803                         //                          "dir/file.txt"\r
804                         s = s.Mid(s.FindOneOf(_T("/\\"))+1);\r
805                 }\r
806         }\r
807         return s;\r
808 }\r
809 \r
810 CString CPatch::RemoveUnicodeBOM(const CString& str)\r
811 {\r
812         if (str.GetLength()==0)\r
813                 return str;\r
814         if (str[0] == 0xFEFF)\r
815                 return str.Mid(1);\r
816         return str;\r
817 }\r