Update diff del rename ignore document.
[TortoiseGit.git] / src / TortoiseMerge / Patch.cpp
blob0f696e5c37ac047c84ec3c420225635b34e4331a
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 chunks->sFilePath.Replace(_T('/'),_T('\\'));
150 // +++
151 if( sLine.Find(_T("+++ ")) == 0 )
153 sLine = sLine.Mid(3); //remove the "---"
154 sLine =sLine.Trim();
156 //at the end of the filepath there's a revision number...
157 int bracket = sLine.ReverseFind('(');
158 if (bracket < 0)
159 // some patch files can have another '(' char, especially ones created in Chinese OS
160 bracket = sLine.ReverseFind(0xff08);
162 if (bracket < 0)
163 chunks->sFilePath2 = sLine.Trim();
164 else
165 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
166 if (chunks->sFilePath2.Find('\t')>=0)
168 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
170 if( chunks->sFilePath2.Find(_T("a/")) == 0 )
171 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
173 chunks->sFilePath2.Replace(_T('/'),_T('\\'));
176 //@@ -xxx,xxx +xxx,xxx @@
177 if( sLine.Find(_T("@@")) == 0 )
179 sLine = sLine.Mid(2);
180 sLine = sLine.Trim();
181 chunk = new Chunk();
182 CString sRemove = sLine.Left(sLine.Find(' '));
183 CString sAdd = sLine.Mid(sLine.Find(' '));
184 chunk->lRemoveStart = (-_ttol(sRemove));
185 if (sRemove.Find(',')>=0)
187 sRemove = sRemove.Mid(sRemove.Find(',')+1);
188 chunk->lRemoveLength = _ttol(sRemove);
190 else
192 chunk->lRemoveStart = 0;
193 chunk->lRemoveLength = (-_ttol(sRemove));
195 chunk->lAddStart = _ttol(sAdd);
196 if (sAdd.Find(',')>=0)
198 sAdd = sAdd.Mid(sAdd.Find(',')+1);
199 chunk->lAddLength = _ttol(sAdd);
201 else
203 chunk->lAddStart = 1;
204 chunk->lAddLength = _ttol(sAdd);
207 state =5;
210 break;
213 case 5: //[ |+|-] <sourceline>
215 //this line is either a context line (with a ' ' in front)
216 //a line added (with a '+' in front)
217 //or a removed line (with a '-' in front)
218 TCHAR type;
219 if (sLine.IsEmpty())
220 type = ' ';
221 else
222 type = sLine.GetAt(0);
223 if (type == ' ')
225 //it's a context line - we don't use them here right now
226 //but maybe in the future the patch algorithm can be
227 //extended to use those in case the file to patch has
228 //already changed and no base file is around...
229 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
230 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
231 chunk->arEOLs.push_back(ending);
232 nContextLineCount++;
234 else if (type == '\\')
236 //it's a context line (sort of):
237 //warnings start with a '\' char (e.g. "\ No newline at end of file")
238 //so just ignore this...
240 else if (type == '-')
242 //a removed line
243 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
244 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
245 chunk->arEOLs.push_back(ending);
246 nRemoveLineCount++;
248 else if (type == '+')
250 //an added line
251 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
252 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
253 chunk->arEOLs.push_back(ending);
254 nAddLineCount++;
256 else
258 //none of those lines! what the hell happened here?
259 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
260 goto errorcleanup;
262 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
263 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
265 //chunk is finished
266 if (chunks)
267 chunks->chunks.Add(chunk);
268 else
269 delete chunk;
270 chunk = NULL;
271 nAddLineCount = 0;
272 nContextLineCount = 0;
273 nRemoveLineCount = 0;
274 state = 0;
277 break;
278 default:
279 ASSERT(FALSE);
280 } // switch (state)
281 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
282 if (chunk)
284 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
285 goto errorcleanup;
287 if (chunks)
288 m_arFileDiffs.Add(chunks);
289 return TRUE;
291 errorcleanup:
292 if (chunk)
293 delete chunk;
294 if (chunks)
296 for (int i=0; i<chunks->chunks.GetCount(); i++)
298 delete chunks->chunks.GetAt(i);
300 chunks->chunks.RemoveAll();
301 delete chunks;
303 FreeMemory();
304 return FALSE;
307 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)
309 CString sLine;
310 EOL ending = EOL_NOENDING;
311 INT_PTR nIndex = 0;
312 INT_PTR nLineCount = 0;
313 g_crasher.AddFile((LPCSTR)(LPCTSTR)filename, (LPCSTR)(LPCTSTR)_T("unified diff file"));
315 CFileTextLines PatchLines;
316 if (!PatchLines.Load(filename))
318 m_sErrorMessage = PatchLines.GetErrorString();
319 return FALSE;
321 m_UnicodeType = PatchLines.GetUnicodeType();
322 FreeMemory();
323 nLineCount = PatchLines.GetCount();
324 //now we got all the lines of the patch file
325 //in our array - parsing can start...
327 for(nIndex=0;PatchLines.GetCount();nIndex++)
329 sLine = PatchLines.GetAt(nIndex);
330 if(sLine.Left(10).Compare(_T("diff --git")) == 0)
332 this->m_IsGitPatch=true;
333 break;
337 //first, skip possible garbage at the beginning
338 //garbage is finished when a line starts with "Index: "
339 //and the next line consists of only "=" characters
340 if( !m_IsGitPatch )
342 for (nIndex=0; nIndex<PatchLines.GetCount(); nIndex++)
344 sLine = PatchLines.GetAt(nIndex);
346 if (sLine.Left(4).Compare(_T("--- "))==0)
347 break;
348 if ((nIndex+1)<PatchLines.GetCount())
350 sLine = PatchLines.GetAt(nIndex+1);
352 if(sLine.IsEmpty()&&m_IsGitPatch)
353 continue;
355 sLine.Replace(_T("="), _T(""));
356 if (sLine.IsEmpty())
357 break;
362 if ((PatchLines.GetCount()-nIndex) < 2)
364 //no file entry found.
365 m_sErrorMessage.LoadString(IDS_ERR_PATCH_NOINDEX);
366 return FALSE;
369 if( m_IsGitPatch )
370 return ParserGitPatch(PatchLines,nIndex);
372 //from this point on we have the real unified diff data
373 int state = 0;
374 Chunks * chunks = NULL;
375 Chunk * chunk = NULL;
376 int nAddLineCount = 0;
377 int nRemoveLineCount = 0;
378 int nContextLineCount = 0;
379 for ( ;nIndex<PatchLines.GetCount(); nIndex++)
381 sLine = PatchLines.GetAt(nIndex);
382 ending = PatchLines.GetLineEnding(nIndex);
383 if (ending != EOL_NOENDING)
384 ending = EOL_AUTOLINE;
385 if (state == 0)
387 if ((sLine.Left(4).Compare(_T("--- "))==0)&&((sLine.Find('\t') >= 0)||this->m_IsGitPatch))
389 state = 2;
390 if (chunks)
392 //this is a new file diff, so add the last one to
393 //our array.
394 m_arFileDiffs.Add(chunks);
396 chunks = new Chunks();
398 int nTab = sLine.Find('\t');
400 int filestart = 4;
401 if(m_IsGitPatch)
403 nTab=sLine.GetLength();
404 filestart = 6;
407 if (nTab >= 0)
409 chunks->sFilePath = sLine.Mid(filestart, nTab-filestart).Trim();
413 switch (state)
415 case 0: //Index: <filepath>
417 CString nextLine;
418 if ((nIndex+1)<PatchLines.GetCount())
420 nextLine = PatchLines.GetAt(nIndex+1);
421 if (!nextLine.IsEmpty())
423 nextLine.Replace(_T("="), _T(""));
424 if (nextLine.IsEmpty())
426 if (chunks)
428 //this is a new file diff, so add the last one to
429 //our array.
430 m_arFileDiffs.Add(chunks);
432 chunks = new Chunks();
433 int nColon = sLine.Find(':');
434 if (nColon >= 0)
436 chunks->sFilePath = sLine.Mid(nColon+1).Trim();
437 if (chunks->sFilePath.Find('\t')>=0)
438 chunks->sFilePath.Left(chunks->sFilePath.Find('\t')).TrimRight();
439 if (chunks->sFilePath.Right(9).Compare(_T("(deleted)"))==0)
440 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-9).TrimRight();
441 if (chunks->sFilePath.Right(7).Compare(_T("(added)"))==0)
442 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-7).TrimRight();
444 state++;
448 if (state == 0)
450 if (nIndex > 0)
452 nIndex--;
453 state = 4;
454 if (chunks == NULL)
456 //the line
457 //Index: <filepath>
458 //was not found at the start of a file diff!
459 break;
464 break;
465 case 1: //====================
467 sLine.Replace(_T("="), _T(""));
468 if (sLine.IsEmpty())
470 // if the next line is already the start of the chunk,
471 // then the patch/diff file was not created by svn. But we
472 // still try to use it
473 if (PatchLines.GetCount() > (nIndex + 1))
476 if (PatchLines.GetAt(nIndex+1).Left(2).Compare(_T("@@"))==0)
478 state += 2;
481 state++;
483 else
485 //the line
486 //=========================
487 //was not found
488 m_sErrorMessage.Format(IDS_ERR_PATCH_NOEQUATIONCHARLINE, nIndex);
489 goto errorcleanup;
492 break;
493 case 2: //--- <filepath>
495 if (sLine.Left(3).Compare(_T("---"))!=0)
497 //no starting "---" found
498 //seems to be either garbage or just
499 //a binary file. So start over...
500 state = 0;
501 nIndex--;
502 if (chunks)
504 delete chunks;
505 chunks = NULL;
507 break;
509 sLine = sLine.Mid(3); //remove the "---"
510 sLine =sLine.Trim();
511 //at the end of the filepath there's a revision number...
512 int bracket = sLine.ReverseFind('(');
513 if (bracket < 0)
514 // some patch files can have another '(' char, especially ones created in Chinese OS
515 bracket = sLine.ReverseFind(0xff08);
516 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
517 num = num.Mid(num.Find(' '));
518 num = num.Trim(_T(" )"));
519 // here again, check for the Chinese bracket
520 num = num.Trim(0xff09);
521 chunks->sRevision = num;
522 if (bracket < 0)
524 if (chunks->sFilePath.IsEmpty())
525 chunks->sFilePath = sLine.Trim();
527 else
528 chunks->sFilePath = sLine.Left(bracket-1).Trim();
529 if (chunks->sFilePath.Find('\t')>=0)
531 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
533 state++;
535 break;
536 case 3: //+++ <filepath>
538 if (sLine.Left(3).Compare(_T("+++"))!=0)
540 //no starting "+++" found
541 m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);
542 goto errorcleanup;
544 sLine = sLine.Mid(3); //remove the "---"
545 sLine =sLine.Trim();
546 //at the end of the filepath there's a revision number...
547 int bracket = sLine.ReverseFind('(');
548 if (bracket < 0)
549 // some patch files can have another '(' char, especially ones created in Chinese OS
550 bracket = sLine.ReverseFind(0xff08);
551 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
552 num = num.Mid(num.Find(' '));
553 num = num.Trim(_T(" )"));
554 // here again, check for the Chinese bracket
555 num = num.Trim(0xff09);
556 chunks->sRevision2 = num;
557 if (bracket < 0)
558 chunks->sFilePath2 = sLine.Trim();
559 else
560 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
561 if (chunks->sFilePath2.Find('\t')>=0)
563 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
565 state++;
567 break;
568 case 4: //@@ -xxx,xxx +xxx,xxx @@
570 //start of a new chunk
571 if (sLine.Left(2).Compare(_T("@@"))!=0)
573 //chunk doesn't start with "@@"
574 //so there's garbage in between two file diffs
575 state = 0;
576 if (chunk)
578 delete chunk;
579 chunk = 0;
580 if (chunks)
582 for (int i=0; i<chunks->chunks.GetCount(); i++)
584 delete chunks->chunks.GetAt(i);
586 chunks->chunks.RemoveAll();
587 delete chunks;
588 chunks = NULL;
591 break; //skip the garbage
593 sLine = sLine.Mid(2);
594 sLine = sLine.Trim();
595 chunk = new Chunk();
596 CString sRemove = sLine.Left(sLine.Find(' '));
597 CString sAdd = sLine.Mid(sLine.Find(' '));
598 chunk->lRemoveStart = (-_ttol(sRemove));
599 if (sRemove.Find(',')>=0)
601 sRemove = sRemove.Mid(sRemove.Find(',')+1);
602 chunk->lRemoveLength = _ttol(sRemove);
604 else
606 chunk->lRemoveStart = 0;
607 chunk->lRemoveLength = (-_ttol(sRemove));
609 chunk->lAddStart = _ttol(sAdd);
610 if (sAdd.Find(',')>=0)
612 sAdd = sAdd.Mid(sAdd.Find(',')+1);
613 chunk->lAddLength = _ttol(sAdd);
615 else
617 chunk->lAddStart = 1;
618 chunk->lAddLength = _ttol(sAdd);
620 state++;
622 break;
623 case 5: //[ |+|-] <sourceline>
625 //this line is either a context line (with a ' ' in front)
626 //a line added (with a '+' in front)
627 //or a removed line (with a '-' in front)
628 TCHAR type;
629 if (sLine.IsEmpty())
630 type = ' ';
631 else
632 type = sLine.GetAt(0);
633 if (type == ' ')
635 //it's a context line - we don't use them here right now
636 //but maybe in the future the patch algorithm can be
637 //extended to use those in case the file to patch has
638 //already changed and no base file is around...
639 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
640 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
641 chunk->arEOLs.push_back(ending);
642 nContextLineCount++;
644 else if (type == '\\')
646 //it's a context line (sort of):
647 //warnings start with a '\' char (e.g. "\ No newline at end of file")
648 //so just ignore this...
650 else if (type == '-')
652 //a removed line
653 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
654 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
655 chunk->arEOLs.push_back(ending);
656 nRemoveLineCount++;
658 else if (type == '+')
660 //an added line
661 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
662 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
663 chunk->arEOLs.push_back(ending);
664 nAddLineCount++;
666 else
668 //none of those lines! what the hell happened here?
669 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
670 goto errorcleanup;
672 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
673 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
675 //chunk is finished
676 if (chunks)
677 chunks->chunks.Add(chunk);
678 else
679 delete chunk;
680 chunk = NULL;
681 nAddLineCount = 0;
682 nContextLineCount = 0;
683 nRemoveLineCount = 0;
684 state = 0;
687 break;
688 default:
689 ASSERT(FALSE);
690 } // switch (state)
691 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
692 if (chunk)
694 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
695 goto errorcleanup;
697 if (chunks)
698 m_arFileDiffs.Add(chunks);
699 return TRUE;
700 errorcleanup:
701 if (chunk)
702 delete chunk;
703 if (chunks)
705 for (int i=0; i<chunks->chunks.GetCount(); i++)
707 delete chunks->chunks.GetAt(i);
709 chunks->chunks.RemoveAll();
710 delete chunks;
712 FreeMemory();
713 return FALSE;
716 CString CPatch::GetFilename(int nIndex)
718 if (nIndex < 0)
719 return _T("");
720 if (nIndex < m_arFileDiffs.GetCount())
722 Chunks * c = m_arFileDiffs.GetAt(nIndex);
723 CString filepath = Strip(c->sFilePath);
724 return filepath;
726 return _T("");
729 CString CPatch::GetRevision(int nIndex)
731 if (nIndex < 0)
732 return 0;
733 if (nIndex < m_arFileDiffs.GetCount())
735 Chunks * c = m_arFileDiffs.GetAt(nIndex);
736 return c->sRevision;
738 return 0;
741 CString CPatch::GetFilename2(int nIndex)
743 if (nIndex < 0)
744 return _T("");
745 if (nIndex < m_arFileDiffs.GetCount())
747 Chunks * c = m_arFileDiffs.GetAt(nIndex);
748 CString filepath = Strip(c->sFilePath2);
749 return filepath;
751 return _T("");
754 CString CPatch::GetRevision2(int nIndex)
756 if (nIndex < 0)
757 return 0;
758 if (nIndex < m_arFileDiffs.GetCount())
760 Chunks * c = m_arFileDiffs.GetAt(nIndex);
761 return c->sRevision2;
763 return 0;
766 BOOL CPatch::PatchFile(const CString& sPath, const CString& sSavePath, const CString& sBaseFile)
768 if (PathIsDirectory(sPath))
770 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);
771 return FALSE;
773 // find the entry in the patch file which matches the full path given in sPath.
774 int nIndex = -1;
775 // use the longest path that matches
776 int nMaxMatch = 0;
777 for (int i=0; i<GetNumberOfFiles(); i++)
779 CString temppath = sPath;
780 CString temp = GetFilename(i);
781 temppath.Replace('/', '\\');
782 temp.Replace('/', '\\');
783 if (temppath.Mid(temppath.GetLength()-temp.GetLength()-1, 1).CompareNoCase(_T("\\"))==0)
785 temppath = temppath.Right(temp.GetLength());
786 if ((temp.CompareNoCase(temppath)==0))
788 if (nMaxMatch < temp.GetLength())
790 nMaxMatch = temp.GetLength();
791 nIndex = i;
795 else if (temppath.CompareNoCase(temp)==0)
797 if ((nIndex < 0)&&(! temp.IsEmpty()))
799 nIndex = i;
803 if (nIndex < 0)
805 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);
806 return FALSE;
809 CString sLine;
810 CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;
811 if (PathFileExists(sPatchFile))
813 g_crasher.AddFile((LPCSTR)(LPCTSTR)sPatchFile, (LPCSTR)(LPCTSTR)_T("File to patch"));
815 CFileTextLines PatchLines;
816 CFileTextLines PatchLinesResult;
817 PatchLines.Load(sPatchFile);
818 PatchLinesResult = PatchLines; //.Copy(PatchLines);
819 PatchLines.CopySettings(&PatchLinesResult);
821 Chunks * chunks = m_arFileDiffs.GetAt(nIndex);
823 for (int i=0; i<chunks->chunks.GetCount(); i++)
825 Chunk * chunk = chunks->chunks.GetAt(i);
826 LONG lRemoveLine = chunk->lRemoveStart;
827 LONG lAddLine = chunk->lAddStart;
828 for (int j=0; j<chunk->arLines.GetCount(); j++)
830 CString sPatchLine = chunk->arLines.GetAt(j);
831 EOL ending = chunk->arEOLs[j];
832 if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))
834 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))
836 // convert the UTF-8 contents in CString sPatchLine into a CStringA
837 sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));
840 int nPatchState = (int)chunk->arLinesStates.GetAt(j);
841 switch (nPatchState)
843 case PATCHSTATE_REMOVED:
845 if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))
847 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
848 return FALSE;
850 if (lAddLine == 0)
851 lAddLine = 1;
852 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))
854 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
855 return FALSE;
857 if (lAddLine > PatchLines.GetCount())
859 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));
860 return FALSE;
862 PatchLines.RemoveAt(lAddLine-1);
864 break;
865 case PATCHSTATE_ADDED:
867 if (lAddLine == 0)
868 lAddLine = 1;
869 PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);
870 lAddLine++;
872 break;
873 case PATCHSTATE_CONTEXT:
875 if (lAddLine > PatchLines.GetCount())
877 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
878 return FALSE;
880 if (lAddLine == 0)
881 lAddLine++;
882 if (lRemoveLine == 0)
883 lRemoveLine++;
884 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&
885 (!HasExpandedKeyWords(sPatchLine)) &&
886 (lRemoveLine <= PatchLines.GetCount()) &&
887 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))
889 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))
890 lAddLine++;
891 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))
892 lAddLine += 2;
893 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))
894 lRemoveLine++;
895 else
897 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
898 return FALSE;
901 lAddLine++;
902 lRemoveLine++;
904 break;
905 default:
906 ASSERT(FALSE);
907 break;
908 } // switch (nPatchState)
909 } // for (j=0; j<chunk->arLines.GetCount(); j++)
910 } // for (int i=0; i<chunks->chunks.GetCount(); i++)
911 if (!sSavePath.IsEmpty())
913 PatchLines.Save(sSavePath, false);
915 return TRUE;
918 BOOL CPatch::HasExpandedKeyWords(const CString& line)
920 if (line.Find(_T("$LastChangedDate"))>=0)
921 return TRUE;
922 if (line.Find(_T("$Date"))>=0)
923 return TRUE;
924 if (line.Find(_T("$LastChangedRevision"))>=0)
925 return TRUE;
926 if (line.Find(_T("$Rev"))>=0)
927 return TRUE;
928 if (line.Find(_T("$LastChangedBy"))>=0)
929 return TRUE;
930 if (line.Find(_T("$Author"))>=0)
931 return TRUE;
932 if (line.Find(_T("$HeadURL"))>=0)
933 return TRUE;
934 if (line.Find(_T("$URL"))>=0)
935 return TRUE;
936 if (line.Find(_T("$Id"))>=0)
937 return TRUE;
938 return FALSE;
941 CString CPatch::CheckPatchPath(const CString& path)
943 //first check if the path already matches
944 if (CountMatches(path) > (GetNumberOfFiles()/3))
945 return path;
946 //now go up the tree and try again
947 CString upperpath = path;
948 while (upperpath.ReverseFind('\\')>0)
950 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
951 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
952 return upperpath;
954 //still no match found. So try sub folders
955 bool isDir = false;
956 CString subpath;
957 CDirFileEnum filefinder(path);
958 while (filefinder.NextFile(subpath, &isDir))
960 if (!isDir)
961 continue;
962 if (g_GitAdminDir.IsAdminDirPath(subpath))
963 continue;
964 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
965 return subpath;
968 // if a patch file only contains newly added files
969 // we can't really find the correct path.
970 // But: we can compare paths strings without the filenames
971 // and check if at least those match
972 upperpath = path;
973 while (upperpath.ReverseFind('\\')>0)
975 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
976 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
977 return upperpath;
980 return path;
983 int CPatch::CountMatches(const CString& path)
985 int matches = 0;
986 for (int i=0; i<GetNumberOfFiles(); ++i)
988 CString temp = GetFilename(i);
989 temp.Replace('/', '\\');
990 if (PathIsRelative(temp))
991 temp = path + _T("\\")+ temp;
992 if (PathFileExists(temp))
993 matches++;
995 return matches;
998 int CPatch::CountDirMatches(const CString& path)
1000 int matches = 0;
1001 for (int i=0; i<GetNumberOfFiles(); ++i)
1003 CString temp = GetFilename(i);
1004 temp.Replace('/', '\\');
1005 if (PathIsRelative(temp))
1006 temp = path + _T("\\")+ temp;
1007 // remove the filename
1008 temp = temp.Left(temp.ReverseFind('\\'));
1009 if (PathFileExists(temp))
1010 matches++;
1012 return matches;
1015 BOOL CPatch::StripPrefixes(const CString& path)
1017 int nSlashesMax = 0;
1018 for (int i=0; i<GetNumberOfFiles(); i++)
1020 CString filename = GetFilename(i);
1021 filename.Replace('/','\\');
1022 int nSlashes = filename.Replace('\\','/');
1023 nSlashesMax = max(nSlashesMax,nSlashes);
1026 for (int nStrip=1;nStrip<nSlashesMax;nStrip++)
1028 m_nStrip = nStrip;
1029 if ( CountMatches(path) > GetNumberOfFiles()/3 )
1031 // Use current m_nStrip
1032 return TRUE;
1036 // Stripping doesn't help so reset it again
1037 m_nStrip = 0;
1038 return FALSE;
1041 CString CPatch::Strip(const CString& filename)
1043 CString s = filename;
1044 if ( m_nStrip>0 )
1046 // Remove windows drive letter "c:"
1047 if ( s.GetLength()>2 && s[1]==':')
1049 s = s.Mid(2);
1052 for (int nStrip=1;nStrip<=m_nStrip;nStrip++)
1054 // "/home/ts/my-working-copy/dir/file.txt"
1055 // "home/ts/my-working-copy/dir/file.txt"
1056 // "ts/my-working-copy/dir/file.txt"
1057 // "my-working-copy/dir/file.txt"
1058 // "dir/file.txt"
1059 s = s.Mid(s.FindOneOf(_T("/\\"))+1);
1062 return s;
1065 CString CPatch::RemoveUnicodeBOM(const CString& str)
1067 if (str.GetLength()==0)
1068 return str;
1069 if (str[0] == 0xFEFF)
1070 return str.Mid(1);
1071 return str;