OSDN Git Service

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