Add lock for tgitcache and fix ignore entry miss
[TortoiseGit.git] / src / TortoiseMerge / Patch.cpp
bloba4728a0110b0ed5cfe1c5f3dcd6c0c833c5786f6
1 // TortoiseMerge - a Diff/Patch program
3 // Copyright (C) 2004-2009 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "StdAfx.h"
20 #include "Resource.h"
21 #include "UnicodeUtils.h"
22 #include "DirFileEnum.h"
23 #include "TortoiseMerge.h"
24 #include "svn_wc.h"
25 #include "GitAdminDir.h"
26 #include "Patch.h"
28 #ifdef _DEBUG
29 #define new DEBUG_NEW
30 #undef THIS_FILE
31 static char THIS_FILE[] = __FILE__;
32 #endif
34 CPatch::CPatch(void)
36 m_nStrip = 0;
37 m_IsGitPatch = false;
40 CPatch::~CPatch(void)
42 FreeMemory();
45 void CPatch::FreeMemory()
47 for (int i=0; i<m_arFileDiffs.GetCount(); i++)
49 Chunks * chunks = m_arFileDiffs.GetAt(i);
50 for (int j=0; j<chunks->chunks.GetCount(); j++)
52 delete chunks->chunks.GetAt(j);
54 chunks->chunks.RemoveAll();
55 delete chunks;
57 m_arFileDiffs.RemoveAll();
60 BOOL CPatch::ParserGitPatch(CFileTextLines &PatchLines,int nIndex)
62 CString sLine;
63 EOL ending = EOL_NOENDING;
65 int state = 0;
66 Chunks * chunks = NULL;
67 Chunk * chunk = NULL;
68 int nAddLineCount = 0;
69 int nRemoveLineCount = 0;
70 int nContextLineCount = 0;
71 for ( ;nIndex<PatchLines.GetCount(); nIndex++)
73 sLine = PatchLines.GetAt(nIndex);
74 ending = PatchLines.GetLineEnding(nIndex);
75 if (ending != EOL_NOENDING)
76 ending = EOL_AUTOLINE;
78 switch (state)
80 case 0:
82 // diff --git
83 if( sLine.Find(_T("diff --git"))==0)
85 if (chunks)
87 //this is a new file diff, so add the last one to
88 //our array.
89 m_arFileDiffs.Add(chunks);
91 chunks = new Chunks();
95 //index
96 if( sLine.Find(_T("index"))==0 )
98 int dotstart=sLine.Find(_T(".."));
99 if(dotstart>=0)
101 chunks->sRevision = sLine.Mid(dotstart-7,7);
102 chunks->sRevision2 = sLine.Mid(dotstart+2,7);
106 //---
107 if( sLine.Find(_T("--- "))==0 )
109 if (sLine.Left(3).Compare(_T("---"))!=0)
111 //no starting "---" found
112 //seems to be either garbage or just
113 //a binary file. So start over...
114 state = 0;
115 nIndex--;
116 if (chunks)
118 delete chunks;
119 chunks = NULL;
121 break;
124 sLine = sLine.Mid(3); //remove the "---"
125 sLine =sLine.Trim();
126 //at the end of the filepath there's a revision number...
127 int bracket = sLine.ReverseFind('(');
128 if (bracket < 0)
129 // some patch files can have another '(' char, especially ones created in Chinese OS
130 bracket = sLine.ReverseFind(0xff08);
132 if (bracket < 0)
134 if (chunks->sFilePath.IsEmpty())
135 chunks->sFilePath = sLine.Trim();
137 else
138 chunks->sFilePath = sLine.Left(bracket-1).Trim();
140 if (chunks->sFilePath.Find('\t')>=0)
142 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
144 if( chunks->sFilePath.Find(_T("a/")) == 0 )
145 chunks->sFilePath=chunks->sFilePath.Mid(2);
147 if( chunks->sFilePath.Find(_T("b/")) == 0 )
148 chunks->sFilePath=chunks->sFilePath.Mid(2);
151 chunks->sFilePath.Replace(_T('/'),_T('\\'));
153 if(chunks->sFilePath == _T("\\dev\\null"))
154 chunks->sFilePath = _T("NUL");
157 // +++
158 if( sLine.Find(_T("+++ ")) == 0 )
160 sLine = sLine.Mid(3); //remove the "---"
161 sLine =sLine.Trim();
163 //at the end of the filepath there's a revision number...
164 int bracket = sLine.ReverseFind('(');
165 if (bracket < 0)
166 // some patch files can have another '(' char, especially ones created in Chinese OS
167 bracket = sLine.ReverseFind(0xff08);
169 if (bracket < 0)
170 chunks->sFilePath2 = sLine.Trim();
171 else
172 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
173 if (chunks->sFilePath2.Find('\t')>=0)
175 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
177 if( chunks->sFilePath2.Find(_T("a/")) == 0 )
178 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
180 if( chunks->sFilePath2.Find(_T("b/")) == 0 )
181 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
183 chunks->sFilePath2.Replace(_T('/'),_T('\\'));
185 if(chunks->sFilePath2 == _T("\\dev\\null"))
186 chunks->sFilePath2 = _T("NUL");
189 //@@ -xxx,xxx +xxx,xxx @@
190 if( sLine.Find(_T("@@")) == 0 )
192 sLine = sLine.Mid(2);
193 sLine = sLine.Trim();
194 chunk = new Chunk();
195 CString sRemove = sLine.Left(sLine.Find(' '));
196 CString sAdd = sLine.Mid(sLine.Find(' '));
197 chunk->lRemoveStart = (-_ttol(sRemove));
198 if (sRemove.Find(',')>=0)
200 sRemove = sRemove.Mid(sRemove.Find(',')+1);
201 chunk->lRemoveLength = _ttol(sRemove);
203 else
205 chunk->lRemoveStart = 0;
206 chunk->lRemoveLength = (-_ttol(sRemove));
208 chunk->lAddStart = _ttol(sAdd);
209 if (sAdd.Find(',')>=0)
211 sAdd = sAdd.Mid(sAdd.Find(',')+1);
212 chunk->lAddLength = _ttol(sAdd);
214 else
216 chunk->lAddStart = 1;
217 chunk->lAddLength = _ttol(sAdd);
220 state =5;
223 break;
226 case 5: //[ |+|-] <sourceline>
228 //this line is either a context line (with a ' ' in front)
229 //a line added (with a '+' in front)
230 //or a removed line (with a '-' in front)
231 TCHAR type;
232 if (sLine.IsEmpty())
233 type = ' ';
234 else
235 type = sLine.GetAt(0);
236 if (type == ' ')
238 //it's a context line - we don't use them here right now
239 //but maybe in the future the patch algorithm can be
240 //extended to use those in case the file to patch has
241 //already changed and no base file is around...
242 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
243 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
244 chunk->arEOLs.push_back(ending);
245 nContextLineCount++;
247 else if (type == '\\')
249 //it's a context line (sort of):
250 //warnings start with a '\' char (e.g. "\ No newline at end of file")
251 //so just ignore this...
253 else if (type == '-')
255 //a removed line
256 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
257 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
258 chunk->arEOLs.push_back(ending);
259 nRemoveLineCount++;
261 else if (type == '+')
263 //an added line
264 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
265 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
266 chunk->arEOLs.push_back(ending);
267 nAddLineCount++;
269 else
271 //none of those lines! what the hell happened here?
272 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
273 goto errorcleanup;
275 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
276 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
278 //chunk is finished
279 if (chunks)
280 chunks->chunks.Add(chunk);
281 else
282 delete chunk;
283 chunk = NULL;
284 nAddLineCount = 0;
285 nContextLineCount = 0;
286 nRemoveLineCount = 0;
287 state = 0;
290 break;
291 default:
292 ASSERT(FALSE);
293 } // switch (state)
294 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
295 if (chunk)
297 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
298 goto errorcleanup;
300 if (chunks)
301 m_arFileDiffs.Add(chunks);
302 return TRUE;
304 errorcleanup:
305 if (chunk)
306 delete chunk;
307 if (chunks)
309 for (int i=0; i<chunks->chunks.GetCount(); i++)
311 delete chunks->chunks.GetAt(i);
313 chunks->chunks.RemoveAll();
314 delete chunks;
316 FreeMemory();
317 return FALSE;
320 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)
322 CString sLine;
323 EOL ending = EOL_NOENDING;
324 INT_PTR nIndex = 0;
325 INT_PTR nLineCount = 0;
326 g_crasher.AddFile((LPCSTR)(LPCTSTR)filename, (LPCSTR)(LPCTSTR)_T("unified diff file"));
328 CFileTextLines PatchLines;
329 if (!PatchLines.Load(filename))
331 m_sErrorMessage = PatchLines.GetErrorString();
332 return FALSE;
334 m_UnicodeType = PatchLines.GetUnicodeType();
335 FreeMemory();
336 nLineCount = PatchLines.GetCount();
337 //now we got all the lines of the patch file
338 //in our array - parsing can start...
340 for(nIndex=0;PatchLines.GetCount();nIndex++)
342 sLine = PatchLines.GetAt(nIndex);
343 if(sLine.Left(10).Compare(_T("diff --git")) == 0)
345 this->m_IsGitPatch=true;
346 break;
350 //first, skip possible garbage at the beginning
351 //garbage is finished when a line starts with "Index: "
352 //and the next line consists of only "=" characters
353 if( !m_IsGitPatch )
355 for (nIndex=0; nIndex<PatchLines.GetCount(); nIndex++)
357 sLine = PatchLines.GetAt(nIndex);
359 if (sLine.Left(4).Compare(_T("--- "))==0)
360 break;
361 if ((nIndex+1)<PatchLines.GetCount())
363 sLine = PatchLines.GetAt(nIndex+1);
365 if(sLine.IsEmpty()&&m_IsGitPatch)
366 continue;
368 sLine.Replace(_T("="), _T(""));
369 if (sLine.IsEmpty())
370 break;
375 if ((PatchLines.GetCount()-nIndex) < 2)
377 //no file entry found.
378 m_sErrorMessage.LoadString(IDS_ERR_PATCH_NOINDEX);
379 return FALSE;
382 if( m_IsGitPatch )
383 return ParserGitPatch(PatchLines,nIndex);
385 //from this point on we have the real unified diff data
386 int state = 0;
387 Chunks * chunks = NULL;
388 Chunk * chunk = NULL;
389 int nAddLineCount = 0;
390 int nRemoveLineCount = 0;
391 int nContextLineCount = 0;
392 for ( ;nIndex<PatchLines.GetCount(); nIndex++)
394 sLine = PatchLines.GetAt(nIndex);
395 ending = PatchLines.GetLineEnding(nIndex);
396 if (ending != EOL_NOENDING)
397 ending = EOL_AUTOLINE;
398 if (state == 0)
400 if ((sLine.Left(4).Compare(_T("--- "))==0)&&((sLine.Find('\t') >= 0)||this->m_IsGitPatch))
402 state = 2;
403 if (chunks)
405 //this is a new file diff, so add the last one to
406 //our array.
407 m_arFileDiffs.Add(chunks);
409 chunks = new Chunks();
411 int nTab = sLine.Find('\t');
413 int filestart = 4;
414 if(m_IsGitPatch)
416 nTab=sLine.GetLength();
417 filestart = 6;
420 if (nTab >= 0)
422 chunks->sFilePath = sLine.Mid(filestart, nTab-filestart).Trim();
426 switch (state)
428 case 0: //Index: <filepath>
430 CString nextLine;
431 if ((nIndex+1)<PatchLines.GetCount())
433 nextLine = PatchLines.GetAt(nIndex+1);
434 if (!nextLine.IsEmpty())
436 nextLine.Replace(_T("="), _T(""));
437 if (nextLine.IsEmpty())
439 if (chunks)
441 //this is a new file diff, so add the last one to
442 //our array.
443 m_arFileDiffs.Add(chunks);
445 chunks = new Chunks();
446 int nColon = sLine.Find(':');
447 if (nColon >= 0)
449 chunks->sFilePath = sLine.Mid(nColon+1).Trim();
450 if (chunks->sFilePath.Find('\t')>=0)
451 chunks->sFilePath.Left(chunks->sFilePath.Find('\t')).TrimRight();
452 if (chunks->sFilePath.Right(9).Compare(_T("(deleted)"))==0)
453 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-9).TrimRight();
454 if (chunks->sFilePath.Right(7).Compare(_T("(added)"))==0)
455 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-7).TrimRight();
457 state++;
461 if (state == 0)
463 if (nIndex > 0)
465 nIndex--;
466 state = 4;
467 if (chunks == NULL)
469 //the line
470 //Index: <filepath>
471 //was not found at the start of a file diff!
472 break;
477 break;
478 case 1: //====================
480 sLine.Replace(_T("="), _T(""));
481 if (sLine.IsEmpty())
483 // if the next line is already the start of the chunk,
484 // then the patch/diff file was not created by svn. But we
485 // still try to use it
486 if (PatchLines.GetCount() > (nIndex + 1))
489 if (PatchLines.GetAt(nIndex+1).Left(2).Compare(_T("@@"))==0)
491 state += 2;
494 state++;
496 else
498 //the line
499 //=========================
500 //was not found
501 m_sErrorMessage.Format(IDS_ERR_PATCH_NOEQUATIONCHARLINE, nIndex);
502 goto errorcleanup;
505 break;
506 case 2: //--- <filepath>
508 if (sLine.Left(3).Compare(_T("---"))!=0)
510 //no starting "---" found
511 //seems to be either garbage or just
512 //a binary file. So start over...
513 state = 0;
514 nIndex--;
515 if (chunks)
517 delete chunks;
518 chunks = NULL;
520 break;
522 sLine = sLine.Mid(3); //remove the "---"
523 sLine =sLine.Trim();
524 //at the end of the filepath there's a revision number...
525 int bracket = sLine.ReverseFind('(');
526 if (bracket < 0)
527 // some patch files can have another '(' char, especially ones created in Chinese OS
528 bracket = sLine.ReverseFind(0xff08);
529 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
530 num = num.Mid(num.Find(' '));
531 num = num.Trim(_T(" )"));
532 // here again, check for the Chinese bracket
533 num = num.Trim(0xff09);
534 chunks->sRevision = num;
535 if (bracket < 0)
537 if (chunks->sFilePath.IsEmpty())
538 chunks->sFilePath = sLine.Trim();
540 else
541 chunks->sFilePath = sLine.Left(bracket-1).Trim();
542 if (chunks->sFilePath.Find('\t')>=0)
544 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
546 state++;
548 break;
549 case 3: //+++ <filepath>
551 if (sLine.Left(3).Compare(_T("+++"))!=0)
553 //no starting "+++" found
554 m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);
555 goto errorcleanup;
557 sLine = sLine.Mid(3); //remove the "---"
558 sLine =sLine.Trim();
559 //at the end of the filepath there's a revision number...
560 int bracket = sLine.ReverseFind('(');
561 if (bracket < 0)
562 // some patch files can have another '(' char, especially ones created in Chinese OS
563 bracket = sLine.ReverseFind(0xff08);
564 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
565 num = num.Mid(num.Find(' '));
566 num = num.Trim(_T(" )"));
567 // here again, check for the Chinese bracket
568 num = num.Trim(0xff09);
569 chunks->sRevision2 = num;
570 if (bracket < 0)
571 chunks->sFilePath2 = sLine.Trim();
572 else
573 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
574 if (chunks->sFilePath2.Find('\t')>=0)
576 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
578 state++;
580 break;
581 case 4: //@@ -xxx,xxx +xxx,xxx @@
583 //start of a new chunk
584 if (sLine.Left(2).Compare(_T("@@"))!=0)
586 //chunk doesn't start with "@@"
587 //so there's garbage in between two file diffs
588 state = 0;
589 if (chunk)
591 delete chunk;
592 chunk = 0;
593 if (chunks)
595 for (int i=0; i<chunks->chunks.GetCount(); i++)
597 delete chunks->chunks.GetAt(i);
599 chunks->chunks.RemoveAll();
600 delete chunks;
601 chunks = NULL;
604 break; //skip the garbage
606 sLine = sLine.Mid(2);
607 sLine = sLine.Trim();
608 chunk = new Chunk();
609 CString sRemove = sLine.Left(sLine.Find(' '));
610 CString sAdd = sLine.Mid(sLine.Find(' '));
611 chunk->lRemoveStart = (-_ttol(sRemove));
612 if (sRemove.Find(',')>=0)
614 sRemove = sRemove.Mid(sRemove.Find(',')+1);
615 chunk->lRemoveLength = _ttol(sRemove);
617 else
619 chunk->lRemoveStart = 0;
620 chunk->lRemoveLength = (-_ttol(sRemove));
622 chunk->lAddStart = _ttol(sAdd);
623 if (sAdd.Find(',')>=0)
625 sAdd = sAdd.Mid(sAdd.Find(',')+1);
626 chunk->lAddLength = _ttol(sAdd);
628 else
630 chunk->lAddStart = 1;
631 chunk->lAddLength = _ttol(sAdd);
633 state++;
635 break;
636 case 5: //[ |+|-] <sourceline>
638 //this line is either a context line (with a ' ' in front)
639 //a line added (with a '+' in front)
640 //or a removed line (with a '-' in front)
641 TCHAR type;
642 if (sLine.IsEmpty())
643 type = ' ';
644 else
645 type = sLine.GetAt(0);
646 if (type == ' ')
648 //it's a context line - we don't use them here right now
649 //but maybe in the future the patch algorithm can be
650 //extended to use those in case the file to patch has
651 //already changed and no base file is around...
652 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
653 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
654 chunk->arEOLs.push_back(ending);
655 nContextLineCount++;
657 else if (type == '\\')
659 //it's a context line (sort of):
660 //warnings start with a '\' char (e.g. "\ No newline at end of file")
661 //so just ignore this...
663 else if (type == '-')
665 //a removed line
666 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
667 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
668 chunk->arEOLs.push_back(ending);
669 nRemoveLineCount++;
671 else if (type == '+')
673 //an added line
674 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
675 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
676 chunk->arEOLs.push_back(ending);
677 nAddLineCount++;
679 else
681 //none of those lines! what the hell happened here?
682 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
683 goto errorcleanup;
685 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
686 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
688 //chunk is finished
689 if (chunks)
690 chunks->chunks.Add(chunk);
691 else
692 delete chunk;
693 chunk = NULL;
694 nAddLineCount = 0;
695 nContextLineCount = 0;
696 nRemoveLineCount = 0;
697 state = 0;
700 break;
701 default:
702 ASSERT(FALSE);
703 } // switch (state)
704 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
705 if (chunk)
707 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
708 goto errorcleanup;
710 if (chunks)
711 m_arFileDiffs.Add(chunks);
712 return TRUE;
713 errorcleanup:
714 if (chunk)
715 delete chunk;
716 if (chunks)
718 for (int i=0; i<chunks->chunks.GetCount(); i++)
720 delete chunks->chunks.GetAt(i);
722 chunks->chunks.RemoveAll();
723 delete chunks;
725 FreeMemory();
726 return FALSE;
729 CString CPatch::GetFilename(int nIndex)
731 if (nIndex < 0)
732 return _T("");
733 if (nIndex < m_arFileDiffs.GetCount())
735 Chunks * c = m_arFileDiffs.GetAt(nIndex);
736 CString filepath = Strip(c->sFilePath);
737 return filepath;
739 return _T("");
742 CString CPatch::GetRevision(int nIndex)
744 if (nIndex < 0)
745 return 0;
746 if (nIndex < m_arFileDiffs.GetCount())
748 Chunks * c = m_arFileDiffs.GetAt(nIndex);
749 return c->sRevision;
751 return 0;
754 CString CPatch::GetFilename2(int nIndex)
756 if (nIndex < 0)
757 return _T("");
758 if (nIndex < m_arFileDiffs.GetCount())
760 Chunks * c = m_arFileDiffs.GetAt(nIndex);
761 CString filepath = Strip(c->sFilePath2);
762 return filepath;
764 return _T("");
767 CString CPatch::GetRevision2(int nIndex)
769 if (nIndex < 0)
770 return 0;
771 if (nIndex < m_arFileDiffs.GetCount())
773 Chunks * c = m_arFileDiffs.GetAt(nIndex);
774 return c->sRevision2;
776 return 0;
779 BOOL CPatch::PatchFile(const CString& sPath, const CString& sSavePath, const CString& sBaseFile)
781 if (PathIsDirectory(sPath))
783 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);
784 return FALSE;
786 // find the entry in the patch file which matches the full path given in sPath.
787 int nIndex = -1;
788 // use the longest path that matches
789 int nMaxMatch = 0;
790 for (int i=0; i<GetNumberOfFiles(); i++)
792 CString temppath = sPath;
793 CString temp = GetFilename(i);
794 temppath.Replace('/', '\\');
795 temp.Replace('/', '\\');
796 if (temppath.Mid(temppath.GetLength()-temp.GetLength()-1, 1).CompareNoCase(_T("\\"))==0)
798 temppath = temppath.Right(temp.GetLength());
799 if ((temp.CompareNoCase(temppath)==0))
801 if (nMaxMatch < temp.GetLength())
803 nMaxMatch = temp.GetLength();
804 nIndex = i;
808 else if (temppath.CompareNoCase(temp)==0)
810 if ((nIndex < 0)&&(! temp.IsEmpty()))
812 nIndex = i;
816 if (nIndex < 0)
818 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);
819 return FALSE;
822 CString sLine;
823 CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;
824 if (PathFileExists(sPatchFile))
826 g_crasher.AddFile((LPCSTR)(LPCTSTR)sPatchFile, (LPCSTR)(LPCTSTR)_T("File to patch"));
828 CFileTextLines PatchLines;
829 CFileTextLines PatchLinesResult;
830 PatchLines.Load(sPatchFile);
831 PatchLinesResult = PatchLines; //.Copy(PatchLines);
832 PatchLines.CopySettings(&PatchLinesResult);
834 Chunks * chunks = m_arFileDiffs.GetAt(nIndex);
836 for (int i=0; i<chunks->chunks.GetCount(); i++)
838 Chunk * chunk = chunks->chunks.GetAt(i);
839 LONG lRemoveLine = chunk->lRemoveStart;
840 LONG lAddLine = chunk->lAddStart;
841 for (int j=0; j<chunk->arLines.GetCount(); j++)
843 CString sPatchLine = chunk->arLines.GetAt(j);
844 EOL ending = chunk->arEOLs[j];
845 if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))
847 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))
849 // convert the UTF-8 contents in CString sPatchLine into a CStringA
850 sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));
853 int nPatchState = (int)chunk->arLinesStates.GetAt(j);
854 switch (nPatchState)
856 case PATCHSTATE_REMOVED:
858 if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))
860 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
861 return FALSE;
863 if (lAddLine == 0)
864 lAddLine = 1;
865 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))
867 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
868 return FALSE;
870 if (lAddLine > PatchLines.GetCount())
872 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));
873 return FALSE;
875 PatchLines.RemoveAt(lAddLine-1);
877 break;
878 case PATCHSTATE_ADDED:
880 if (lAddLine == 0)
881 lAddLine = 1;
882 PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);
883 lAddLine++;
885 break;
886 case PATCHSTATE_CONTEXT:
888 if (lAddLine > PatchLines.GetCount())
890 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
891 return FALSE;
893 if (lAddLine == 0)
894 lAddLine++;
895 if (lRemoveLine == 0)
896 lRemoveLine++;
897 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&
898 (!HasExpandedKeyWords(sPatchLine)) &&
899 (lRemoveLine <= PatchLines.GetCount()) &&
900 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))
902 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))
903 lAddLine++;
904 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))
905 lAddLine += 2;
906 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))
907 lRemoveLine++;
908 else
910 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
911 return FALSE;
914 lAddLine++;
915 lRemoveLine++;
917 break;
918 default:
919 ASSERT(FALSE);
920 break;
921 } // switch (nPatchState)
922 } // for (j=0; j<chunk->arLines.GetCount(); j++)
923 } // for (int i=0; i<chunks->chunks.GetCount(); i++)
924 if (!sSavePath.IsEmpty())
926 PatchLines.Save(sSavePath, false);
928 return TRUE;
931 BOOL CPatch::HasExpandedKeyWords(const CString& line)
933 if (line.Find(_T("$LastChangedDate"))>=0)
934 return TRUE;
935 if (line.Find(_T("$Date"))>=0)
936 return TRUE;
937 if (line.Find(_T("$LastChangedRevision"))>=0)
938 return TRUE;
939 if (line.Find(_T("$Rev"))>=0)
940 return TRUE;
941 if (line.Find(_T("$LastChangedBy"))>=0)
942 return TRUE;
943 if (line.Find(_T("$Author"))>=0)
944 return TRUE;
945 if (line.Find(_T("$HeadURL"))>=0)
946 return TRUE;
947 if (line.Find(_T("$URL"))>=0)
948 return TRUE;
949 if (line.Find(_T("$Id"))>=0)
950 return TRUE;
951 return FALSE;
954 CString CPatch::CheckPatchPath(const CString& path)
956 //first check if the path already matches
957 if (CountMatches(path) > (GetNumberOfFiles()/3))
958 return path;
959 //now go up the tree and try again
960 CString upperpath = path;
961 while (upperpath.ReverseFind('\\')>0)
963 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
964 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
965 return upperpath;
967 //still no match found. So try sub folders
968 bool isDir = false;
969 CString subpath;
970 CDirFileEnum filefinder(path);
971 while (filefinder.NextFile(subpath, &isDir))
973 if (!isDir)
974 continue;
975 if (g_GitAdminDir.IsAdminDirPath(subpath))
976 continue;
977 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
978 return subpath;
981 // if a patch file only contains newly added files
982 // we can't really find the correct path.
983 // But: we can compare paths strings without the filenames
984 // and check if at least those match
985 upperpath = path;
986 while (upperpath.ReverseFind('\\')>0)
988 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
989 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
990 return upperpath;
993 return path;
996 int CPatch::CountMatches(const CString& path)
998 int matches = 0;
999 for (int i=0; i<GetNumberOfFiles(); ++i)
1001 CString temp = GetFilename(i);
1002 temp.Replace('/', '\\');
1003 if (PathIsRelative(temp))
1004 temp = path + _T("\\")+ temp;
1005 if (PathFileExists(temp))
1006 matches++;
1008 return matches;
1011 int CPatch::CountDirMatches(const CString& path)
1013 int matches = 0;
1014 for (int i=0; i<GetNumberOfFiles(); ++i)
1016 CString temp = GetFilename(i);
1017 temp.Replace('/', '\\');
1018 if (PathIsRelative(temp))
1019 temp = path + _T("\\")+ temp;
1020 // remove the filename
1021 temp = temp.Left(temp.ReverseFind('\\'));
1022 if (PathFileExists(temp))
1023 matches++;
1025 return matches;
1028 BOOL CPatch::StripPrefixes(const CString& path)
1030 int nSlashesMax = 0;
1031 for (int i=0; i<GetNumberOfFiles(); i++)
1033 CString filename = GetFilename(i);
1034 filename.Replace('/','\\');
1035 int nSlashes = filename.Replace('\\','/');
1036 nSlashesMax = max(nSlashesMax,nSlashes);
1039 for (int nStrip=1;nStrip<nSlashesMax;nStrip++)
1041 m_nStrip = nStrip;
1042 if ( CountMatches(path) > GetNumberOfFiles()/3 )
1044 // Use current m_nStrip
1045 return TRUE;
1049 // Stripping doesn't help so reset it again
1050 m_nStrip = 0;
1051 return FALSE;
1054 CString CPatch::Strip(const CString& filename)
1056 CString s = filename;
1057 if ( m_nStrip>0 )
1059 // Remove windows drive letter "c:"
1060 if ( s.GetLength()>2 && s[1]==':')
1062 s = s.Mid(2);
1065 for (int nStrip=1;nStrip<=m_nStrip;nStrip++)
1067 // "/home/ts/my-working-copy/dir/file.txt"
1068 // "home/ts/my-working-copy/dir/file.txt"
1069 // "ts/my-working-copy/dir/file.txt"
1070 // "my-working-copy/dir/file.txt"
1071 // "dir/file.txt"
1072 s = s.Mid(s.FindOneOf(_T("/\\"))+1);
1075 return s;
1078 CString CPatch::RemoveUnicodeBOM(const CString& str)
1080 if (str.GetLength()==0)
1081 return str;
1082 if (str[0] == 0xFEFF)
1083 return str.Mid(1);
1084 return str;