optimize TGitCache for CLI operations
[TortoiseGit.git] / src / TortoiseMerge / Patch.cpp
blobbb0b8918c37bc4f262afcf0c3cb73bd48a97c76c
1 // TortoiseMerge - a Diff/Patch program
3 // Copyright (C) 2009-2011 - TortoiseGit
4 // Copyright (C) 2004-2009,2011 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "StdAfx.h"
21 #include "Resource.h"
22 #include "UnicodeUtils.h"
23 #include "DirFileEnum.h"
24 #include "TortoiseMerge.h"
25 #include "svn_wc.h"
26 #include "GitAdminDir.h"
27 #include "Patch.h"
29 #ifdef _DEBUG
30 #define new DEBUG_NEW
31 #undef THIS_FILE
32 static char THIS_FILE[] = __FILE__;
33 #endif
35 CPatch::CPatch(void)
36 : m_nStrip(0)
37 , m_UnicodeType(CFileTextLines::AUTOTYPE)
38 , m_IsGitPatch(false)
42 CPatch::~CPatch(void)
44 FreeMemory();
47 void CPatch::FreeMemory()
49 for (int i=0; i<m_arFileDiffs.GetCount(); i++)
51 Chunks * chunks = m_arFileDiffs.GetAt(i);
52 for (int j=0; j<chunks->chunks.GetCount(); j++)
54 delete chunks->chunks.GetAt(j);
56 chunks->chunks.RemoveAll();
57 delete chunks;
59 m_arFileDiffs.RemoveAll();
62 BOOL CPatch::ParserGitPatch(CFileTextLines &PatchLines,int nIndex)
64 CString sLine;
65 EOL ending = EOL_NOENDING;
67 int state = 0;
68 Chunks * chunks = NULL;
69 Chunk * chunk = NULL;
70 int nAddLineCount = 0;
71 int nRemoveLineCount = 0;
72 int nContextLineCount = 0;
73 for ( ;nIndex<PatchLines.GetCount(); nIndex++)
75 sLine = PatchLines.GetAt(nIndex);
76 ending = PatchLines.GetLineEnding(nIndex);
77 if (ending != EOL_NOENDING)
78 ending = EOL_AUTOLINE;
80 switch (state)
82 case 0:
84 // diff --git
85 if( sLine.Find(_T("diff --git"))==0)
87 if (chunks)
89 //this is a new file diff, so add the last one to
90 //our array.
91 m_arFileDiffs.Add(chunks);
93 chunks = new Chunks();
97 //index
98 if( sLine.Find(_T("index"))==0 )
100 int dotstart=sLine.Find(_T(".."));
101 if(dotstart>=0)
103 chunks->sRevision = sLine.Mid(dotstart-7,7);
104 chunks->sRevision2 = sLine.Mid(dotstart+2,7);
108 //---
109 if( sLine.Find(_T("--- "))==0 )
111 if (sLine.Left(3).Compare(_T("---"))!=0)
113 //no starting "---" found
114 //seems to be either garbage or just
115 //a binary file. So start over...
116 state = 0;
117 nIndex--;
118 if (chunks)
120 delete chunks;
121 chunks = NULL;
123 break;
126 sLine = sLine.Mid(3); //remove the "---"
127 sLine =sLine.Trim();
128 //at the end of the filepath there's a revision number...
129 int bracket = sLine.ReverseFind('(');
130 if (bracket < 0)
131 // some patch files can have another '(' char, especially ones created in Chinese OS
132 bracket = sLine.ReverseFind(0xff08);
134 if (bracket < 0)
136 if (chunks->sFilePath.IsEmpty())
137 chunks->sFilePath = sLine.Trim();
139 else
140 chunks->sFilePath = sLine.Left(bracket-1).Trim();
142 if (chunks->sFilePath.Find('\t')>=0)
144 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
146 if (chunks->sFilePath.Find(_T('"')) == 0 && chunks->sFilePath.ReverseFind(_T('"')) == chunks->sFilePath.GetLength() - 1)
147 chunks->sFilePath=chunks->sFilePath.Mid(1, chunks->sFilePath.GetLength() - 2);
148 if( chunks->sFilePath.Find(_T("a/")) == 0 )
149 chunks->sFilePath=chunks->sFilePath.Mid(2);
151 if( chunks->sFilePath.Find(_T("b/")) == 0 )
152 chunks->sFilePath=chunks->sFilePath.Mid(2);
155 chunks->sFilePath.Replace(_T('/'),_T('\\'));
157 if(chunks->sFilePath == _T("\\dev\\null"))
158 chunks->sFilePath = _T("NUL");
161 // +++
162 if( sLine.Find(_T("+++ ")) == 0 )
164 sLine = sLine.Mid(3); //remove the "---"
165 sLine =sLine.Trim();
167 //at the end of the filepath there's a revision number...
168 int bracket = sLine.ReverseFind('(');
169 if (bracket < 0)
170 // some patch files can have another '(' char, especially ones created in Chinese OS
171 bracket = sLine.ReverseFind(0xff08);
173 if (bracket < 0)
174 chunks->sFilePath2 = sLine.Trim();
175 else
176 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
177 if (chunks->sFilePath2.Find('\t')>=0)
179 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
181 if (chunks->sFilePath2.Find(_T('"')) == 0 && chunks->sFilePath2.ReverseFind(_T('"')) == chunks->sFilePath2.GetLength() - 1)
182 chunks->sFilePath2=chunks->sFilePath2.Mid(1, chunks->sFilePath2.GetLength() - 2);
183 if( chunks->sFilePath2.Find(_T("a/")) == 0 )
184 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
186 if( chunks->sFilePath2.Find(_T("b/")) == 0 )
187 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
189 chunks->sFilePath2.Replace(_T('/'),_T('\\'));
191 if(chunks->sFilePath2 == _T("\\dev\\null"))
192 chunks->sFilePath2 = _T("NUL");
195 //@@ -xxx,xxx +xxx,xxx @@
196 if( sLine.Find(_T("@@")) == 0 )
198 sLine = sLine.Mid(2);
199 sLine = sLine.Trim();
200 chunk = new Chunk();
201 CString sRemove = sLine.Left(sLine.Find(' '));
202 CString sAdd = sLine.Mid(sLine.Find(' '));
203 chunk->lRemoveStart = (-_ttol(sRemove));
204 if (sRemove.Find(',')>=0)
206 sRemove = sRemove.Mid(sRemove.Find(',')+1);
207 chunk->lRemoveLength = _ttol(sRemove);
209 else
211 chunk->lRemoveStart = 0;
212 chunk->lRemoveLength = (-_ttol(sRemove));
214 chunk->lAddStart = _ttol(sAdd);
215 if (sAdd.Find(',')>=0)
217 sAdd = sAdd.Mid(sAdd.Find(',')+1);
218 chunk->lAddLength = _ttol(sAdd);
220 else
222 chunk->lAddStart = 1;
223 chunk->lAddLength = _ttol(sAdd);
226 state =5;
229 break;
232 case 5: //[ |+|-] <sourceline>
234 //this line is either a context line (with a ' ' in front)
235 //a line added (with a '+' in front)
236 //or a removed line (with a '-' in front)
237 TCHAR type;
238 if (sLine.IsEmpty())
239 type = ' ';
240 else
241 type = sLine.GetAt(0);
242 if (type == ' ')
244 //it's a context line - we don't use them here right now
245 //but maybe in the future the patch algorithm can be
246 //extended to use those in case the file to patch has
247 //already changed and no base file is around...
248 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
249 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
250 chunk->arEOLs.push_back(ending);
251 nContextLineCount++;
253 else if (type == '\\')
255 //it's a context line (sort of):
256 //warnings start with a '\' char (e.g. "\ No newline at end of file")
257 //so just ignore this...
259 else if (type == '-')
261 //a removed line
262 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
263 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
264 chunk->arEOLs.push_back(ending);
265 nRemoveLineCount++;
267 else if (type == '+')
269 //an added line
270 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
271 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
272 chunk->arEOLs.push_back(ending);
273 nAddLineCount++;
275 else
277 //none of those lines! what the hell happened here?
278 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
279 goto errorcleanup;
281 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
282 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
284 //chunk is finished
285 if (chunks)
286 chunks->chunks.Add(chunk);
287 else
288 delete chunk;
289 chunk = NULL;
290 nAddLineCount = 0;
291 nContextLineCount = 0;
292 nRemoveLineCount = 0;
293 state = 0;
296 break;
297 default:
298 ASSERT(FALSE);
299 } // switch (state)
300 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
301 if (chunk)
303 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
304 goto errorcleanup;
306 if (chunks)
307 m_arFileDiffs.Add(chunks);
308 return TRUE;
310 errorcleanup:
311 if (chunk)
312 delete chunk;
313 if (chunks)
315 for (int i=0; i<chunks->chunks.GetCount(); i++)
317 delete chunks->chunks.GetAt(i);
319 chunks->chunks.RemoveAll();
320 delete chunks;
322 FreeMemory();
323 return FALSE;
326 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)
328 CString sLine;
329 EOL ending = EOL_NOENDING;
330 INT_PTR nIndex = 0;
331 INT_PTR nLineCount = 0;
332 g_crasher.AddFile((LPCSTR)(LPCTSTR)filename, (LPCSTR)(LPCTSTR)_T("unified diff file"));
334 CFileTextLines PatchLines;
335 if (!PatchLines.Load(filename))
337 m_sErrorMessage = PatchLines.GetErrorString();
338 return FALSE;
340 m_UnicodeType = PatchLines.GetUnicodeType();
341 FreeMemory();
342 nLineCount = PatchLines.GetCount();
343 //now we got all the lines of the patch file
344 //in our array - parsing can start...
346 for(nIndex=0;nIndex<PatchLines.GetCount();nIndex++)
348 sLine = PatchLines.GetAt(nIndex);
349 if(sLine.Left(10).Compare(_T("diff --git")) == 0)
351 this->m_IsGitPatch=true;
352 break;
356 //first, skip possible garbage at the beginning
357 //garbage is finished when a line starts with "Index: "
358 //and the next line consists of only "=" characters
359 if( !m_IsGitPatch )
361 for (nIndex=0; nIndex<PatchLines.GetCount(); nIndex++)
363 sLine = PatchLines.GetAt(nIndex);
365 if (sLine.Left(4).Compare(_T("--- "))==0)
366 break;
367 if ((nIndex+1)<PatchLines.GetCount())
369 sLine = PatchLines.GetAt(nIndex+1);
371 if(sLine.IsEmpty()&&m_IsGitPatch)
372 continue;
374 sLine.Remove('=');
375 if (sLine.IsEmpty())
376 break;
381 if ((PatchLines.GetCount()-nIndex) < 2)
383 //no file entry found.
384 m_sErrorMessage.LoadString(IDS_ERR_PATCH_NOINDEX);
385 return FALSE;
388 if( m_IsGitPatch )
389 return ParserGitPatch(PatchLines,nIndex);
391 //from this point on we have the real unified diff data
392 int state = 0;
393 Chunks * chunks = NULL;
394 Chunk * chunk = NULL;
395 int nAddLineCount = 0;
396 int nRemoveLineCount = 0;
397 int nContextLineCount = 0;
398 for ( ;nIndex<PatchLines.GetCount(); nIndex++)
400 sLine = PatchLines.GetAt(nIndex);
401 ending = PatchLines.GetLineEnding(nIndex);
402 if (ending != EOL_NOENDING)
403 ending = EOL_AUTOLINE;
404 if (state == 0)
406 if ((sLine.Left(4).Compare(_T("--- "))==0)&&((sLine.Find('\t') >= 0)||this->m_IsGitPatch))
408 state = 2;
409 if (chunks)
411 //this is a new file diff, so add the last one to
412 //our array.
413 m_arFileDiffs.Add(chunks);
415 chunks = new Chunks();
417 int nTab = sLine.Find('\t');
419 int filestart = 4;
420 if(m_IsGitPatch)
422 nTab=sLine.GetLength();
423 filestart = 6;
426 if (nTab >= 0)
428 chunks->sFilePath = sLine.Mid(filestart, nTab-filestart).Trim();
432 switch (state)
434 case 0: //Index: <filepath>
436 CString nextLine;
437 if ((nIndex+1)<PatchLines.GetCount())
439 nextLine = PatchLines.GetAt(nIndex+1);
440 if (!nextLine.IsEmpty())
442 nextLine.Remove('=');
443 if (nextLine.IsEmpty())
445 if (chunks)
447 //this is a new file diff, so add the last one to
448 //our array.
449 m_arFileDiffs.Add(chunks);
451 chunks = new Chunks();
452 int nColon = sLine.Find(':');
453 if (nColon >= 0)
455 chunks->sFilePath = sLine.Mid(nColon+1).Trim();
456 if (chunks->sFilePath.Find('\t')>=0)
457 chunks->sFilePath.Left(chunks->sFilePath.Find('\t')).TrimRight();
458 if (chunks->sFilePath.Right(9).Compare(_T("(deleted)"))==0)
459 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-9).TrimRight();
460 if (chunks->sFilePath.Right(7).Compare(_T("(added)"))==0)
461 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-7).TrimRight();
463 state++;
467 if (state == 0)
469 if (nIndex > 0)
471 nIndex--;
472 state = 4;
473 if (chunks == NULL)
475 //the line
476 //Index: <filepath>
477 //was not found at the start of a file diff!
478 break;
483 break;
484 case 1: //====================
486 sLine.Remove('=');
487 if (sLine.IsEmpty())
489 // if the next line is already the start of the chunk,
490 // then the patch/diff file was not created by svn. But we
491 // still try to use it
492 if (PatchLines.GetCount() > (nIndex + 1))
495 if (PatchLines.GetAt(nIndex+1).Left(2).Compare(_T("@@"))==0)
497 state += 2;
500 state++;
502 else
504 //the line
505 //=========================
506 //was not found
507 m_sErrorMessage.Format(IDS_ERR_PATCH_NOEQUATIONCHARLINE, nIndex);
508 goto errorcleanup;
511 break;
512 case 2: //--- <filepath>
514 if (sLine.Left(3).Compare(_T("---"))!=0)
516 //no starting "---" found
517 //seems to be either garbage or just
518 //a binary file. So start over...
519 state = 0;
520 nIndex--;
521 if (chunks)
523 delete chunks;
524 chunks = NULL;
526 break;
528 sLine = sLine.Mid(3); //remove the "---"
529 sLine =sLine.Trim();
530 //at the end of the filepath there's a revision number...
531 int bracket = sLine.ReverseFind('(');
532 if (bracket < 0)
533 // some patch files can have another '(' char, especially ones created in Chinese OS
534 bracket = sLine.ReverseFind(0xff08);
535 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
536 num = num.Mid(num.Find(' '));
537 num = num.Trim(_T(" )"));
538 // here again, check for the Chinese bracket
539 num = num.Trim(0xff09);
540 chunks->sRevision = num;
541 if (bracket < 0)
543 if (chunks->sFilePath.IsEmpty())
544 chunks->sFilePath = sLine.Trim();
546 else
547 chunks->sFilePath = sLine.Left(bracket-1).Trim();
548 if (chunks->sFilePath.Find('\t')>=0)
550 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
552 state++;
554 break;
555 case 3: //+++ <filepath>
557 if (sLine.Left(3).Compare(_T("+++"))!=0)
559 //no starting "+++" found
560 m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);
561 goto errorcleanup;
563 sLine = sLine.Mid(3); //remove the "---"
564 sLine =sLine.Trim();
565 //at the end of the filepath there's a revision number...
566 int bracket = sLine.ReverseFind('(');
567 if (bracket < 0)
568 // some patch files can have another '(' char, especially ones created in Chinese OS
569 bracket = sLine.ReverseFind(0xff08);
570 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
571 num = num.Mid(num.Find(' '));
572 num = num.Trim(_T(" )"));
573 // here again, check for the Chinese bracket
574 num = num.Trim(0xff09);
575 chunks->sRevision2 = num;
576 if (bracket < 0)
577 chunks->sFilePath2 = sLine.Trim();
578 else
579 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
580 if (chunks->sFilePath2.Find('\t')>=0)
582 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
584 state++;
586 break;
587 case 4: //@@ -xxx,xxx +xxx,xxx @@
589 //start of a new chunk
590 if (sLine.Left(2).Compare(_T("@@"))!=0)
592 //chunk doesn't start with "@@"
593 //so there's garbage in between two file diffs
594 state = 0;
595 if (chunk)
597 delete chunk;
598 chunk = 0;
599 if (chunks)
601 for (int i=0; i<chunks->chunks.GetCount(); i++)
603 delete chunks->chunks.GetAt(i);
605 chunks->chunks.RemoveAll();
606 delete chunks;
607 chunks = NULL;
610 break; //skip the garbage
612 sLine = sLine.Mid(2);
613 sLine = sLine.Trim();
614 chunk = new Chunk();
615 CString sRemove = sLine.Left(sLine.Find(' '));
616 CString sAdd = sLine.Mid(sLine.Find(' '));
617 chunk->lRemoveStart = (-_ttol(sRemove));
618 if (sRemove.Find(',')>=0)
620 sRemove = sRemove.Mid(sRemove.Find(',')+1);
621 chunk->lRemoveLength = _ttol(sRemove);
623 else
625 chunk->lRemoveStart = 0;
626 chunk->lRemoveLength = (-_ttol(sRemove));
628 chunk->lAddStart = _ttol(sAdd);
629 if (sAdd.Find(',')>=0)
631 sAdd = sAdd.Mid(sAdd.Find(',')+1);
632 chunk->lAddLength = _ttol(sAdd);
634 else
636 chunk->lAddStart = 1;
637 chunk->lAddLength = _ttol(sAdd);
639 state++;
641 break;
642 case 5: //[ |+|-] <sourceline>
644 //this line is either a context line (with a ' ' in front)
645 //a line added (with a '+' in front)
646 //or a removed line (with a '-' in front)
647 TCHAR type;
648 if (sLine.IsEmpty())
649 type = ' ';
650 else
651 type = sLine.GetAt(0);
652 if (type == ' ')
654 //it's a context line - we don't use them here right now
655 //but maybe in the future the patch algorithm can be
656 //extended to use those in case the file to patch has
657 //already changed and no base file is around...
658 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
659 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
660 chunk->arEOLs.push_back(ending);
661 nContextLineCount++;
663 else if (type == '\\')
665 //it's a context line (sort of):
666 //warnings start with a '\' char (e.g. "\ No newline at end of file")
667 //so just ignore this...
669 else if (type == '-')
671 //a removed line
672 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
673 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
674 chunk->arEOLs.push_back(ending);
675 nRemoveLineCount++;
677 else if (type == '+')
679 //an added line
680 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
681 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
682 chunk->arEOLs.push_back(ending);
683 nAddLineCount++;
685 else
687 //none of those lines! what the hell happened here?
688 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
689 goto errorcleanup;
691 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
692 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
694 //chunk is finished
695 if (chunks)
696 chunks->chunks.Add(chunk);
697 else
698 delete chunk;
699 chunk = NULL;
700 nAddLineCount = 0;
701 nContextLineCount = 0;
702 nRemoveLineCount = 0;
703 state = 0;
706 break;
707 default:
708 ASSERT(FALSE);
709 } // switch (state)
710 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
711 if (chunk)
713 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
714 goto errorcleanup;
716 if (chunks)
717 m_arFileDiffs.Add(chunks);
718 return TRUE;
719 errorcleanup:
720 if (chunk)
721 delete chunk;
722 if (chunks)
724 for (int i=0; i<chunks->chunks.GetCount(); i++)
726 delete chunks->chunks.GetAt(i);
728 chunks->chunks.RemoveAll();
729 delete chunks;
731 FreeMemory();
732 return FALSE;
735 CString CPatch::GetFilename(int nIndex)
737 if (nIndex < 0)
738 return _T("");
739 if (nIndex < m_arFileDiffs.GetCount())
741 Chunks * c = m_arFileDiffs.GetAt(nIndex);
742 CString filepath = Strip(c->sFilePath);
743 return filepath;
745 return _T("");
748 CString CPatch::GetRevision(int nIndex)
750 if (nIndex < 0)
751 return 0;
752 if (nIndex < m_arFileDiffs.GetCount())
754 Chunks * c = m_arFileDiffs.GetAt(nIndex);
755 return c->sRevision;
757 return 0;
760 CString CPatch::GetFilename2(int nIndex)
762 if (nIndex < 0)
763 return _T("");
764 if (nIndex < m_arFileDiffs.GetCount())
766 Chunks * c = m_arFileDiffs.GetAt(nIndex);
767 CString filepath = Strip(c->sFilePath2);
768 return filepath;
770 return _T("");
773 CString CPatch::GetRevision2(int nIndex)
775 if (nIndex < 0)
776 return 0;
777 if (nIndex < m_arFileDiffs.GetCount())
779 Chunks * c = m_arFileDiffs.GetAt(nIndex);
780 return c->sRevision2;
782 return 0;
785 BOOL CPatch::PatchFile(const CString& sPath, const CString& sSavePath, const CString& sBaseFile)
787 if (PathIsDirectory(sPath))
789 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);
790 return FALSE;
792 // find the entry in the patch file which matches the full path given in sPath.
793 int nIndex = -1;
794 // use the longest path that matches
795 int nMaxMatch = 0;
796 for (int i=0; i<GetNumberOfFiles(); i++)
798 CString temppath = sPath;
799 CString temp = GetFilename(i);
800 temppath.Replace('/', '\\');
801 temp.Replace('/', '\\');
802 if (temppath.Mid(temppath.GetLength()-temp.GetLength()-1, 1).CompareNoCase(_T("\\"))==0)
804 temppath = temppath.Right(temp.GetLength());
805 if ((temp.CompareNoCase(temppath)==0))
807 if (nMaxMatch < temp.GetLength())
809 nMaxMatch = temp.GetLength();
810 nIndex = i;
814 else if (temppath.CompareNoCase(temp)==0)
816 if ((nIndex < 0)&&(! temp.IsEmpty()))
818 nIndex = i;
822 if (nIndex < 0)
824 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);
825 return FALSE;
828 CString sLine;
829 CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;
830 if (PathFileExists(sPatchFile))
832 g_crasher.AddFile((LPCSTR)(LPCTSTR)sPatchFile, (LPCSTR)(LPCTSTR)_T("File to patch"));
834 CFileTextLines PatchLines;
835 CFileTextLines PatchLinesResult;
836 PatchLines.Load(sPatchFile);
837 PatchLinesResult = PatchLines; //.Copy(PatchLines);
838 PatchLines.CopySettings(&PatchLinesResult);
840 Chunks * chunks = m_arFileDiffs.GetAt(nIndex);
842 for (int i=0; i<chunks->chunks.GetCount(); i++)
844 Chunk * chunk = chunks->chunks.GetAt(i);
845 LONG lRemoveLine = chunk->lRemoveStart;
846 LONG lAddLine = chunk->lAddStart;
847 for (int j=0; j<chunk->arLines.GetCount(); j++)
849 CString sPatchLine = chunk->arLines.GetAt(j);
850 EOL ending = chunk->arEOLs[j];
851 if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))
853 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))
855 // convert the UTF-8 contents in CString sPatchLine into a CStringA
856 sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));
859 int nPatchState = (int)chunk->arLinesStates.GetAt(j);
860 switch (nPatchState)
862 case PATCHSTATE_REMOVED:
864 if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))
866 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
867 return FALSE;
869 if (lAddLine == 0)
870 lAddLine = 1;
871 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))
873 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
874 return FALSE;
876 if (lAddLine > PatchLines.GetCount())
878 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));
879 return FALSE;
881 PatchLines.RemoveAt(lAddLine-1);
883 break;
884 case PATCHSTATE_ADDED:
886 if (lAddLine == 0)
887 lAddLine = 1;
888 PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);
889 lAddLine++;
891 break;
892 case PATCHSTATE_CONTEXT:
894 if (lAddLine > PatchLines.GetCount())
896 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
897 return FALSE;
899 if (lAddLine == 0)
900 lAddLine++;
901 if (lRemoveLine == 0)
902 lRemoveLine++;
903 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&
904 (!HasExpandedKeyWords(sPatchLine)) &&
905 (lRemoveLine <= PatchLines.GetCount()) &&
906 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))
908 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))
909 lAddLine++;
910 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))
911 lAddLine += 2;
912 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))
913 lRemoveLine++;
914 else
916 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
917 return FALSE;
920 lAddLine++;
921 lRemoveLine++;
923 break;
924 default:
925 ASSERT(FALSE);
926 break;
927 } // switch (nPatchState)
928 } // for (j=0; j<chunk->arLines.GetCount(); j++)
929 } // for (int i=0; i<chunks->chunks.GetCount(); i++)
930 if (!sSavePath.IsEmpty())
932 PatchLines.Save(sSavePath, false);
934 return TRUE;
937 BOOL CPatch::HasExpandedKeyWords(const CString& line)
939 if (line.Find(_T("$LastChangedDate"))>=0)
940 return TRUE;
941 if (line.Find(_T("$Date"))>=0)
942 return TRUE;
943 if (line.Find(_T("$LastChangedRevision"))>=0)
944 return TRUE;
945 if (line.Find(_T("$Rev"))>=0)
946 return TRUE;
947 if (line.Find(_T("$LastChangedBy"))>=0)
948 return TRUE;
949 if (line.Find(_T("$Author"))>=0)
950 return TRUE;
951 if (line.Find(_T("$HeadURL"))>=0)
952 return TRUE;
953 if (line.Find(_T("$URL"))>=0)
954 return TRUE;
955 if (line.Find(_T("$Id"))>=0)
956 return TRUE;
957 return FALSE;
960 CString CPatch::CheckPatchPath(const CString& path)
962 //first check if the path already matches
963 if (CountMatches(path) > (GetNumberOfFiles()/3))
964 return path;
965 //now go up the tree and try again
966 CString upperpath = path;
967 while (upperpath.ReverseFind('\\')>0)
969 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
970 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
971 return upperpath;
973 //still no match found. So try sub folders
974 bool isDir = false;
975 CString subpath;
976 CDirFileEnum filefinder(path);
977 while (filefinder.NextFile(subpath, &isDir))
979 if (!isDir)
980 continue;
981 if (g_GitAdminDir.IsAdminDirPath(subpath))
982 continue;
983 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
984 return subpath;
987 // if a patch file only contains newly added files
988 // we can't really find the correct path.
989 // But: we can compare paths strings without the filenames
990 // and check if at least those match
991 upperpath = path;
992 while (upperpath.ReverseFind('\\')>0)
994 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
995 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
996 return upperpath;
999 return path;
1002 int CPatch::CountMatches(const CString& path)
1004 int matches = 0;
1005 for (int i=0; i<GetNumberOfFiles(); ++i)
1007 CString temp = GetFilename(i);
1008 temp.Replace('/', '\\');
1009 if (PathIsRelative(temp))
1010 temp = path + _T("\\")+ temp;
1011 if (PathFileExists(temp))
1012 matches++;
1014 return matches;
1017 int CPatch::CountDirMatches(const CString& path)
1019 int matches = 0;
1020 for (int i=0; i<GetNumberOfFiles(); ++i)
1022 CString temp = GetFilename(i);
1023 temp.Replace('/', '\\');
1024 if (PathIsRelative(temp))
1025 temp = path + _T("\\")+ temp;
1026 // remove the filename
1027 temp = temp.Left(temp.ReverseFind('\\'));
1028 if (PathFileExists(temp))
1029 matches++;
1031 return matches;
1034 BOOL CPatch::StripPrefixes(const CString& path)
1036 int nSlashesMax = 0;
1037 for (int i=0; i<GetNumberOfFiles(); i++)
1039 CString filename = GetFilename(i);
1040 filename.Replace('/','\\');
1041 int nSlashes = filename.Replace('\\','/');
1042 nSlashesMax = max(nSlashesMax,nSlashes);
1045 for (int nStrip=1;nStrip<nSlashesMax;nStrip++)
1047 m_nStrip = nStrip;
1048 if ( CountMatches(path) > GetNumberOfFiles()/3 )
1050 // Use current m_nStrip
1051 return TRUE;
1055 // Stripping doesn't help so reset it again
1056 m_nStrip = 0;
1057 return FALSE;
1060 CString CPatch::Strip(const CString& filename)
1062 CString s = filename;
1063 if ( m_nStrip>0 )
1065 // Remove windows drive letter "c:"
1066 if ( s.GetLength()>2 && s[1]==':')
1068 s = s.Mid(2);
1071 for (int nStrip=1;nStrip<=m_nStrip;nStrip++)
1073 // "/home/ts/my-working-copy/dir/file.txt"
1074 // "home/ts/my-working-copy/dir/file.txt"
1075 // "ts/my-working-copy/dir/file.txt"
1076 // "my-working-copy/dir/file.txt"
1077 // "dir/file.txt"
1078 s = s.Mid(s.FindOneOf(_T("/\\"))+1);
1081 return s;
1084 CString CPatch::RemoveUnicodeBOM(const CString& str)
1086 if (str.GetLength()==0)
1087 return str;
1088 if (str[0] == 0xFEFF)
1089 return str.Mid(1);
1090 return str;