Fixed issue #1507: Submodule Diff Dialog should show dirty state only on working...
[TortoiseGit.git] / src / TortoiseMerge / Patch.cpp
blobf2ad3fc59fb465080229b17beaec1de0b3735026
1 // TortoiseMerge - a Diff/Patch program
3 // Copyright (C) 2009-2012 - TortoiseGit
4 // Copyright (C) 2012 - Sven Strickroth <email@cs-ware.de>
5 // Copyright (C) 2004-2009,2011-2012 - TortoiseSVN
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "StdAfx.h"
22 #include "Resource.h"
23 #include "UnicodeUtils.h"
24 #include "DirFileEnum.h"
25 #include "TortoiseMerge.h"
26 #include "svn_wc.h"
27 #include "GitAdminDir.h"
28 #include "Patch.h"
30 #ifdef _DEBUG
31 #define new DEBUG_NEW
32 #undef THIS_FILE
33 static char THIS_FILE[] = __FILE__;
34 #endif
36 CPatch::CPatch(void)
37 : m_nStrip(0)
38 , m_UnicodeType(CFileTextLines::AUTOTYPE)
39 , m_IsGitPatch(false)
43 CPatch::~CPatch(void)
45 FreeMemory();
48 void CPatch::FreeMemory()
50 for (int i=0; i<m_arFileDiffs.GetCount(); i++)
52 Chunks * chunks = m_arFileDiffs.GetAt(i);
53 for (int j=0; j<chunks->chunks.GetCount(); j++)
55 delete chunks->chunks.GetAt(j);
57 chunks->chunks.RemoveAll();
58 delete chunks;
60 m_arFileDiffs.RemoveAll();
63 BOOL CPatch::ParserGitPatch(CFileTextLines &PatchLines,int nIndex)
65 CString sLine;
66 EOL ending = EOL_NOENDING;
68 int state = 0;
69 Chunks * chunks = NULL;
70 Chunk * chunk = NULL;
71 int nAddLineCount = 0;
72 int nRemoveLineCount = 0;
73 int nContextLineCount = 0;
74 for ( ;nIndex<PatchLines.GetCount(); nIndex++)
76 sLine = PatchLines.GetAt(nIndex);
77 ending = PatchLines.GetLineEnding(nIndex);
78 if (ending != EOL_NOENDING)
79 ending = EOL_AUTOLINE;
81 switch (state)
83 case 0:
85 // diff --git
86 if( sLine.Find(_T("diff --git"))==0)
88 if (chunks)
90 //this is a new file diff, so add the last one to
91 //our array.
92 m_arFileDiffs.Add(chunks);
94 chunks = new Chunks();
98 //index
99 if( sLine.Find(_T("index"))==0 )
101 int dotstart=sLine.Find(_T(".."));
102 if(dotstart>=0)
104 chunks->sRevision = sLine.Mid(dotstart-7,7);
105 chunks->sRevision2 = sLine.Mid(dotstart+2,7);
109 //---
110 if( sLine.Find(_T("--- "))==0 )
112 if (sLine.Left(3).Compare(_T("---"))!=0)
114 //no starting "---" found
115 //seems to be either garbage or just
116 //a binary file. So start over...
117 state = 0;
118 nIndex--;
119 if (chunks)
121 delete chunks;
122 chunks = NULL;
124 break;
127 sLine = sLine.Mid(3); //remove the "---"
128 sLine =sLine.Trim();
129 //at the end of the filepath there's a revision number...
130 int bracket = sLine.ReverseFind('(');
131 if (bracket < 0)
132 // some patch files can have another '(' char, especially ones created in Chinese OS
133 bracket = sLine.ReverseFind(0xff08);
135 if (bracket < 0)
137 if (chunks->sFilePath.IsEmpty())
138 chunks->sFilePath = sLine.Trim();
140 else
141 chunks->sFilePath = sLine.Left(bracket-1).Trim();
143 if (chunks->sFilePath.Find('\t')>=0)
145 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
147 if (chunks->sFilePath.Find(_T('"')) == 0 && chunks->sFilePath.ReverseFind(_T('"')) == chunks->sFilePath.GetLength() - 1)
148 chunks->sFilePath=chunks->sFilePath.Mid(1, chunks->sFilePath.GetLength() - 2);
149 if( chunks->sFilePath.Find(_T("a/")) == 0 )
150 chunks->sFilePath=chunks->sFilePath.Mid(2);
152 if( chunks->sFilePath.Find(_T("b/")) == 0 )
153 chunks->sFilePath=chunks->sFilePath.Mid(2);
156 chunks->sFilePath.Replace(_T('/'),_T('\\'));
158 if (chunks->sFilePath == _T("\\dev\\null") || chunks->sFilePath == _T("/dev/null"))
159 chunks->sFilePath = _T("NUL");
162 // +++
163 if( sLine.Find(_T("+++ ")) == 0 )
165 sLine = sLine.Mid(3); //remove the "---"
166 sLine =sLine.Trim();
168 //at the end of the filepath there's a revision number...
169 int bracket = sLine.ReverseFind('(');
170 if (bracket < 0)
171 // some patch files can have another '(' char, especially ones created in Chinese OS
172 bracket = sLine.ReverseFind(0xff08);
174 if (bracket < 0)
175 chunks->sFilePath2 = sLine.Trim();
176 else
177 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
178 if (chunks->sFilePath2.Find('\t')>=0)
180 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
182 if (chunks->sFilePath2.Find(_T('"')) == 0 && chunks->sFilePath2.ReverseFind(_T('"')) == chunks->sFilePath2.GetLength() - 1)
183 chunks->sFilePath2=chunks->sFilePath2.Mid(1, chunks->sFilePath2.GetLength() - 2);
184 if( chunks->sFilePath2.Find(_T("a/")) == 0 )
185 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
187 if( chunks->sFilePath2.Find(_T("b/")) == 0 )
188 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
190 chunks->sFilePath2.Replace(_T('/'),_T('\\'));
192 if (chunks->sFilePath2 == _T("\\dev\\null") || chunks->sFilePath2 == _T("/dev/null"))
193 chunks->sFilePath2 = _T("NUL");
196 //@@ -xxx,xxx +xxx,xxx @@
197 if( sLine.Find(_T("@@")) == 0 )
199 sLine = sLine.Mid(2);
200 sLine = sLine.Trim();
201 chunk = new Chunk();
202 CString sRemove = sLine.Left(sLine.Find(' '));
203 CString sAdd = sLine.Mid(sLine.Find(' '));
204 chunk->lRemoveStart = (-_ttol(sRemove));
205 if (sRemove.Find(',')>=0)
207 sRemove = sRemove.Mid(sRemove.Find(',')+1);
208 chunk->lRemoveLength = _ttol(sRemove);
210 else
212 chunk->lRemoveStart = 0;
213 chunk->lRemoveLength = (-_ttol(sRemove));
215 chunk->lAddStart = _ttol(sAdd);
216 if (sAdd.Find(',')>=0)
218 sAdd = sAdd.Mid(sAdd.Find(',')+1);
219 chunk->lAddLength = _ttol(sAdd);
221 else
223 chunk->lAddStart = 1;
224 chunk->lAddLength = _ttol(sAdd);
227 state =5;
230 break;
233 case 5: //[ |+|-] <sourceline>
235 //this line is either a context line (with a ' ' in front)
236 //a line added (with a '+' in front)
237 //or a removed line (with a '-' in front)
238 TCHAR type;
239 if (sLine.IsEmpty())
240 type = ' ';
241 else
242 type = sLine.GetAt(0);
243 if (type == ' ')
245 //it's a context line - we don't use them here right now
246 //but maybe in the future the patch algorithm can be
247 //extended to use those in case the file to patch has
248 //already changed and no base file is around...
249 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
250 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
251 chunk->arEOLs.push_back(ending);
252 nContextLineCount++;
254 else if (type == '\\')
256 //it's a context line (sort of):
257 //warnings start with a '\' char (e.g. "\ No newline at end of file")
258 //so just ignore this...
260 else if (type == '-')
262 //a removed line
263 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
264 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
265 chunk->arEOLs.push_back(ending);
266 nRemoveLineCount++;
268 else if (type == '+')
270 //an added line
271 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
272 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
273 chunk->arEOLs.push_back(ending);
274 nAddLineCount++;
276 else
278 //none of those lines! what the hell happened here?
279 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
280 goto errorcleanup;
282 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
283 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
285 //chunk is finished
286 if (chunks)
287 chunks->chunks.Add(chunk);
288 else
289 delete chunk;
290 chunk = NULL;
291 nAddLineCount = 0;
292 nContextLineCount = 0;
293 nRemoveLineCount = 0;
294 state = 0;
297 break;
298 default:
299 ASSERT(FALSE);
300 } // switch (state)
301 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
302 if (chunk)
304 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
305 goto errorcleanup;
307 if (chunks)
308 m_arFileDiffs.Add(chunks);
309 return TRUE;
311 errorcleanup:
312 if (chunk)
313 delete chunk;
314 if (chunks)
316 for (int i=0; i<chunks->chunks.GetCount(); i++)
318 delete chunks->chunks.GetAt(i);
320 chunks->chunks.RemoveAll();
321 delete chunks;
323 FreeMemory();
324 return FALSE;
327 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)
329 CString sLine;
330 EOL ending = EOL_NOENDING;
331 INT_PTR nIndex = 0;
332 INT_PTR nLineCount = 0;
333 CCrashReport::Instance().AddFile2(filename, NULL, _T("unified diff file"), CR_AF_MAKE_FILE_COPY);
335 CFileTextLines PatchLines;
336 if (!PatchLines.Load(filename))
338 m_sErrorMessage = PatchLines.GetErrorString();
339 return FALSE;
341 m_UnicodeType = PatchLines.GetUnicodeType();
342 FreeMemory();
343 nLineCount = PatchLines.GetCount();
344 //now we got all the lines of the patch file
345 //in our array - parsing can start...
347 for(nIndex=0;nIndex<PatchLines.GetCount();nIndex++)
349 sLine = PatchLines.GetAt((int)nIndex);
350 if(sLine.Left(10).Compare(_T("diff --git")) == 0)
352 this->m_IsGitPatch=true;
353 break;
357 //first, skip possible garbage at the beginning
358 //garbage is finished when a line starts with "Index: "
359 //and the next line consists of only "=" characters
360 if( !m_IsGitPatch )
362 for (nIndex=0; nIndex<PatchLines.GetCount(); nIndex++)
364 sLine = PatchLines.GetAt((int)nIndex);
366 if (sLine.Left(4).Compare(_T("--- "))==0)
367 break;
368 if ((nIndex+1)<PatchLines.GetCount())
370 sLine = PatchLines.GetAt((int)nIndex + 1);
372 if(sLine.IsEmpty()&&m_IsGitPatch)
373 continue;
375 sLine.Remove('=');
376 if (sLine.IsEmpty())
377 break;
382 if ((PatchLines.GetCount()-nIndex) < 2)
384 //no file entry found.
385 m_sErrorMessage.LoadString(IDS_ERR_PATCH_NOINDEX);
386 return FALSE;
389 if( m_IsGitPatch )
390 return ParserGitPatch(PatchLines, (int)nIndex);
392 //from this point on we have the real unified diff data
393 int state = 0;
394 Chunks * chunks = NULL;
395 Chunk * chunk = NULL;
396 int nAddLineCount = 0;
397 int nRemoveLineCount = 0;
398 int nContextLineCount = 0;
399 for ( ;nIndex<PatchLines.GetCount(); nIndex++)
401 sLine = PatchLines.GetAt((int)nIndex);
402 ending = PatchLines.GetLineEnding((int)nIndex);
403 if (ending != EOL_NOENDING)
404 ending = EOL_AUTOLINE;
405 if (state == 0)
407 if ((sLine.Left(4).Compare(_T("--- "))==0)&&((sLine.Find('\t') >= 0)||this->m_IsGitPatch))
409 state = 2;
410 if (chunks)
412 //this is a new file diff, so add the last one to
413 //our array.
414 m_arFileDiffs.Add(chunks);
416 chunks = new Chunks();
418 int nTab = sLine.Find('\t');
420 int filestart = 4;
421 if(m_IsGitPatch)
423 nTab=sLine.GetLength();
424 filestart = 6;
427 if (nTab >= 0)
429 chunks->sFilePath = sLine.Mid(filestart, nTab-filestart).Trim();
433 switch (state)
435 case 0: //Index: <filepath>
437 CString nextLine;
438 if ((nIndex+1)<PatchLines.GetCount())
440 nextLine = PatchLines.GetAt((int)nIndex + 1);
441 if (!nextLine.IsEmpty())
443 nextLine.Remove('=');
444 if (nextLine.IsEmpty())
446 if (chunks)
448 //this is a new file diff, so add the last one to
449 //our array.
450 m_arFileDiffs.Add(chunks);
452 chunks = new Chunks();
453 int nColon = sLine.Find(':');
454 if (nColon >= 0)
456 chunks->sFilePath = sLine.Mid(nColon+1).Trim();
457 if (chunks->sFilePath.Find('\t')>=0)
458 chunks->sFilePath.Left(chunks->sFilePath.Find('\t')).TrimRight();
459 if (chunks->sFilePath.Right(9).Compare(_T("(deleted)"))==0)
460 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-9).TrimRight();
461 if (chunks->sFilePath.Right(7).Compare(_T("(added)"))==0)
462 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-7).TrimRight();
464 state++;
468 if (state == 0)
470 if (nIndex > 0)
472 nIndex--;
473 state = 4;
474 if (chunks == NULL)
476 //the line
477 //Index: <filepath>
478 //was not found at the start of a file diff!
479 break;
484 break;
485 case 1: //====================
487 sLine.Remove('=');
488 if (sLine.IsEmpty())
490 // if the next line is already the start of the chunk,
491 // then the patch/diff file was not created by svn. But we
492 // still try to use it
493 if (PatchLines.GetCount() > (nIndex + 1))
496 if (PatchLines.GetAt((int)nIndex + 1).Left(2).Compare(_T("@@"))==0)
498 state += 2;
501 state++;
503 else
505 //the line
506 //=========================
507 //was not found
508 m_sErrorMessage.Format(IDS_ERR_PATCH_NOEQUATIONCHARLINE, nIndex);
509 goto errorcleanup;
512 break;
513 case 2: //--- <filepath>
515 if (sLine.Left(3).Compare(_T("---"))!=0)
517 //no starting "---" found
518 //seems to be either garbage or just
519 //a binary file. So start over...
520 state = 0;
521 nIndex--;
522 if (chunks)
524 delete chunks;
525 chunks = NULL;
527 break;
529 sLine = sLine.Mid(3); //remove the "---"
530 sLine =sLine.Trim();
531 //at the end of the filepath there's a revision number...
532 int bracket = sLine.ReverseFind('(');
533 if (bracket < 0)
534 // some patch files can have another '(' char, especially ones created in Chinese OS
535 bracket = sLine.ReverseFind(0xff08);
536 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
537 num = num.Mid(num.Find(' '));
538 num = num.Trim(_T(" )"));
539 // here again, check for the Chinese bracket
540 num = num.Trim(0xff09);
541 chunks->sRevision = num;
542 if (bracket < 0)
544 if (chunks->sFilePath.IsEmpty())
545 chunks->sFilePath = sLine.Trim();
547 else
548 chunks->sFilePath = sLine.Left(bracket-1).Trim();
549 if (chunks->sFilePath.Find('\t')>=0)
551 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
553 state++;
555 break;
556 case 3: //+++ <filepath>
558 if (sLine.Left(3).Compare(_T("+++"))!=0)
560 //no starting "+++" found
561 m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);
562 goto errorcleanup;
564 sLine = sLine.Mid(3); //remove the "---"
565 sLine =sLine.Trim();
566 //at the end of the filepath there's a revision number...
567 int bracket = sLine.ReverseFind('(');
568 if (bracket < 0)
569 // some patch files can have another '(' char, especially ones created in Chinese OS
570 bracket = sLine.ReverseFind(0xff08);
571 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
572 num = num.Mid(num.Find(' '));
573 num = num.Trim(_T(" )"));
574 // here again, check for the Chinese bracket
575 num = num.Trim(0xff09);
576 chunks->sRevision2 = num;
577 if (bracket < 0)
578 chunks->sFilePath2 = sLine.Trim();
579 else
580 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
581 if (chunks->sFilePath2.Find('\t')>=0)
583 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
585 state++;
587 break;
588 case 4: //@@ -xxx,xxx +xxx,xxx @@
590 //start of a new chunk
591 if (sLine.Left(2).Compare(_T("@@"))!=0)
593 //chunk doesn't start with "@@"
594 //so there's garbage in between two file diffs
595 state = 0;
596 if (chunk)
598 delete chunk;
599 chunk = 0;
600 if (chunks)
602 for (int i=0; i<chunks->chunks.GetCount(); i++)
604 delete chunks->chunks.GetAt(i);
606 chunks->chunks.RemoveAll();
607 delete chunks;
608 chunks = NULL;
611 break; //skip the garbage
613 sLine = sLine.Mid(2);
614 sLine = sLine.Trim();
615 chunk = new Chunk();
616 CString sRemove = sLine.Left(sLine.Find(' '));
617 CString sAdd = sLine.Mid(sLine.Find(' '));
618 chunk->lRemoveStart = (-_ttol(sRemove));
619 if (sRemove.Find(',')>=0)
621 sRemove = sRemove.Mid(sRemove.Find(',')+1);
622 chunk->lRemoveLength = _ttol(sRemove);
624 else
626 chunk->lRemoveStart = 0;
627 chunk->lRemoveLength = (-_ttol(sRemove));
629 chunk->lAddStart = _ttol(sAdd);
630 if (sAdd.Find(',')>=0)
632 sAdd = sAdd.Mid(sAdd.Find(',')+1);
633 chunk->lAddLength = _ttol(sAdd);
635 else
637 chunk->lAddStart = 1;
638 chunk->lAddLength = _ttol(sAdd);
640 state++;
642 break;
643 case 5: //[ |+|-] <sourceline>
645 //this line is either a context line (with a ' ' in front)
646 //a line added (with a '+' in front)
647 //or a removed line (with a '-' in front)
648 TCHAR type;
649 if (sLine.IsEmpty())
650 type = ' ';
651 else
652 type = sLine.GetAt(0);
653 if (type == ' ')
655 //it's a context line - we don't use them here right now
656 //but maybe in the future the patch algorithm can be
657 //extended to use those in case the file to patch has
658 //already changed and no base file is around...
659 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
660 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
661 chunk->arEOLs.push_back(ending);
662 nContextLineCount++;
664 else if (type == '\\')
666 //it's a context line (sort of):
667 //warnings start with a '\' char (e.g. "\ No newline at end of file")
668 //so just ignore this...
670 else if (type == '-')
672 //a removed line
673 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
674 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
675 chunk->arEOLs.push_back(ending);
676 nRemoveLineCount++;
678 else if (type == '+')
680 //an added line
681 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
682 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
683 chunk->arEOLs.push_back(ending);
684 nAddLineCount++;
686 else
688 //none of those lines! what the hell happened here?
689 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
690 goto errorcleanup;
692 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
693 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
695 //chunk is finished
696 if (chunks)
697 chunks->chunks.Add(chunk);
698 else
699 delete chunk;
700 chunk = NULL;
701 nAddLineCount = 0;
702 nContextLineCount = 0;
703 nRemoveLineCount = 0;
704 state = 0;
707 break;
708 default:
709 ASSERT(FALSE);
710 } // switch (state)
711 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
712 if (chunk)
714 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
715 goto errorcleanup;
717 if (chunks)
718 m_arFileDiffs.Add(chunks);
719 return TRUE;
720 errorcleanup:
721 if (chunk)
722 delete chunk;
723 if (chunks)
725 for (int i=0; i<chunks->chunks.GetCount(); i++)
727 delete chunks->chunks.GetAt(i);
729 chunks->chunks.RemoveAll();
730 delete chunks;
732 FreeMemory();
733 return FALSE;
736 CString CPatch::GetFilename(int nIndex)
738 if (nIndex < 0)
739 return _T("");
740 if (nIndex < m_arFileDiffs.GetCount())
742 Chunks * c = m_arFileDiffs.GetAt(nIndex);
743 CString filepath = Strip(c->sFilePath);
744 return filepath;
746 return _T("");
749 CString CPatch::GetRevision(int nIndex)
751 if (nIndex < 0)
752 return 0;
753 if (nIndex < m_arFileDiffs.GetCount())
755 Chunks * c = m_arFileDiffs.GetAt(nIndex);
756 return c->sRevision;
758 return 0;
761 CString CPatch::GetFilename2(int nIndex)
763 if (nIndex < 0)
764 return _T("");
765 if (nIndex < m_arFileDiffs.GetCount())
767 Chunks * c = m_arFileDiffs.GetAt(nIndex);
768 CString filepath = Strip(c->sFilePath2);
769 return filepath;
771 return _T("");
774 CString CPatch::GetRevision2(int nIndex)
776 if (nIndex < 0)
777 return 0;
778 if (nIndex < m_arFileDiffs.GetCount())
780 Chunks * c = m_arFileDiffs.GetAt(nIndex);
781 return c->sRevision2;
783 return 0;
786 int CPatch::PatchFile(int nIndex, const CString& sPatchPath, const CString& sSavePath, const CString& sBaseFile, const bool force)
788 CString sPath = GetFullPath(sPatchPath, nIndex);
789 if (PathIsDirectory(sPath))
791 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);
792 return FALSE;
794 if (nIndex < 0)
796 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);
797 return FALSE;
800 if (!force && sPath == _T("NUL") && PathFileExists(GetFullPath(sPatchPath, nIndex, 1)))
801 return FALSE;
803 if (GetFullPath(sPatchPath, nIndex, 1) == _T("NUL") && !PathFileExists(sPath))
804 return 2;
806 CString sLine;
807 CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;
808 if (PathFileExists(sPatchFile))
810 CCrashReport::Instance().AddFile2(sPatchFile, NULL, _T("File to patch"), CR_AF_MAKE_FILE_COPY);
812 CFileTextLines PatchLines;
813 CFileTextLines PatchLinesResult;
814 PatchLines.Load(sPatchFile);
815 PatchLinesResult = PatchLines; //.Copy(PatchLines);
816 PatchLines.CopySettings(&PatchLinesResult);
818 Chunks * chunks = m_arFileDiffs.GetAt(nIndex);
820 for (int i=0; i<chunks->chunks.GetCount(); i++)
822 Chunk * chunk = chunks->chunks.GetAt(i);
823 LONG lRemoveLine = chunk->lRemoveStart;
824 LONG lAddLine = chunk->lAddStart;
825 for (int j=0; j<chunk->arLines.GetCount(); j++)
827 CString sPatchLine = chunk->arLines.GetAt(j);
828 EOL ending = chunk->arEOLs[j];
829 if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))
831 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))
833 // convert the UTF-8 contents in CString sPatchLine into a CStringA
834 sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));
837 int nPatchState = (int)chunk->arLinesStates.GetAt(j);
838 switch (nPatchState)
840 case PATCHSTATE_REMOVED:
842 if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))
844 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
845 return FALSE;
847 if (lAddLine == 0)
848 lAddLine = 1;
849 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))
851 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
852 return FALSE;
854 if (lAddLine > PatchLines.GetCount())
856 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));
857 return FALSE;
859 PatchLines.RemoveAt(lAddLine-1);
861 break;
862 case PATCHSTATE_ADDED:
864 if (lAddLine == 0)
865 lAddLine = 1;
866 PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);
867 lAddLine++;
869 break;
870 case PATCHSTATE_CONTEXT:
872 if (lAddLine > PatchLines.GetCount())
874 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
875 return FALSE;
877 if (lAddLine == 0)
878 lAddLine++;
879 if (lRemoveLine == 0)
880 lRemoveLine++;
881 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&
882 (!HasExpandedKeyWords(sPatchLine)) &&
883 (lRemoveLine <= PatchLines.GetCount()) &&
884 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))
886 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))
887 lAddLine++;
888 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))
889 lAddLine += 2;
890 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))
891 lRemoveLine++;
892 else
894 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
895 return FALSE;
898 lAddLine++;
899 lRemoveLine++;
901 break;
902 default:
903 ASSERT(FALSE);
904 break;
905 } // switch (nPatchState)
906 } // for (j=0; j<chunk->arLines.GetCount(); j++)
907 } // for (int i=0; i<chunks->chunks.GetCount(); i++)
908 if (!sSavePath.IsEmpty())
910 PatchLines.Save(sSavePath, false);
912 return TRUE;
915 BOOL CPatch::HasExpandedKeyWords(const CString& line)
917 if (line.Find(_T("$LastChangedDate"))>=0)
918 return TRUE;
919 if (line.Find(_T("$Date"))>=0)
920 return TRUE;
921 if (line.Find(_T("$LastChangedRevision"))>=0)
922 return TRUE;
923 if (line.Find(_T("$Rev"))>=0)
924 return TRUE;
925 if (line.Find(_T("$LastChangedBy"))>=0)
926 return TRUE;
927 if (line.Find(_T("$Author"))>=0)
928 return TRUE;
929 if (line.Find(_T("$HeadURL"))>=0)
930 return TRUE;
931 if (line.Find(_T("$URL"))>=0)
932 return TRUE;
933 if (line.Find(_T("$Id"))>=0)
934 return TRUE;
935 return FALSE;
938 CString CPatch::CheckPatchPath(const CString& path)
940 //first check if the path already matches
941 if (CountMatches(path) > (GetNumberOfFiles()/3))
942 return path;
943 //now go up the tree and try again
944 CString upperpath = path;
945 while (upperpath.ReverseFind('\\')>0)
947 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
948 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
949 return upperpath;
951 //still no match found. So try sub folders
952 bool isDir = false;
953 CString subpath;
954 CDirFileEnum filefinder(path);
955 while (filefinder.NextFile(subpath, &isDir))
957 if (!isDir)
958 continue;
959 if (g_GitAdminDir.IsAdminDirPath(subpath))
960 continue;
961 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
962 return subpath;
965 // if a patch file only contains newly added files
966 // we can't really find the correct path.
967 // But: we can compare paths strings without the filenames
968 // and check if at least those match
969 upperpath = path;
970 while (upperpath.ReverseFind('\\')>0)
972 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
973 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
974 return upperpath;
977 return path;
980 int CPatch::CountMatches(const CString& path)
982 int matches = 0;
983 for (int i=0; i<GetNumberOfFiles(); ++i)
985 CString temp = GetFilename(i);
986 temp.Replace('/', '\\');
987 if (PathIsRelative(temp))
988 temp = path + _T("\\")+ temp;
989 if (PathFileExists(temp))
990 matches++;
992 return matches;
995 int CPatch::CountDirMatches(const CString& path)
997 int matches = 0;
998 for (int i=0; i<GetNumberOfFiles(); ++i)
1000 CString temp = GetFilename(i);
1001 temp.Replace('/', '\\');
1002 if (PathIsRelative(temp))
1003 temp = path + _T("\\")+ temp;
1004 // remove the filename
1005 temp = temp.Left(temp.ReverseFind('\\'));
1006 if (PathFileExists(temp))
1007 matches++;
1009 return matches;
1012 BOOL CPatch::StripPrefixes(const CString& path)
1014 int nSlashesMax = 0;
1015 for (int i=0; i<GetNumberOfFiles(); i++)
1017 CString filename = GetFilename(i);
1018 filename.Replace('/','\\');
1019 int nSlashes = filename.Replace('\\','/');
1020 nSlashesMax = max(nSlashesMax,nSlashes);
1023 for (int nStrip=1;nStrip<nSlashesMax;nStrip++)
1025 m_nStrip = nStrip;
1026 if ( CountMatches(path) > GetNumberOfFiles()/3 )
1028 // Use current m_nStrip
1029 return TRUE;
1033 // Stripping doesn't help so reset it again
1034 m_nStrip = 0;
1035 return FALSE;
1038 CString CPatch::Strip(const CString& filename)
1040 CString s = filename;
1041 if ( m_nStrip>0 )
1043 // Remove windows drive letter "c:"
1044 if ( s.GetLength()>2 && s[1]==':')
1046 s = s.Mid(2);
1049 for (int nStrip=1;nStrip<=m_nStrip;nStrip++)
1051 // "/home/ts/my-working-copy/dir/file.txt"
1052 // "home/ts/my-working-copy/dir/file.txt"
1053 // "ts/my-working-copy/dir/file.txt"
1054 // "my-working-copy/dir/file.txt"
1055 // "dir/file.txt"
1056 s = s.Mid(s.FindOneOf(_T("/\\"))+1);
1059 return s;
1062 CString CPatch::GetFullPath(const CString& sPath, int nIndex, int fileno /* = 0*/)
1064 CString temp;
1065 if (fileno == 0)
1066 temp = GetFilename(nIndex);
1067 else
1068 temp = GetFilename2(nIndex);
1070 temp.Replace('/', '\\');
1071 if(temp == _T("NUL"))
1072 return temp;
1074 if (PathIsRelative(temp))
1076 if (sPath.Right(1).Compare(_T("\\")) != 0)
1077 temp = sPath + _T("\\") + temp;
1078 else
1079 temp = sPath + temp;
1082 return temp;
1085 CString CPatch::RemoveUnicodeBOM(const CString& str)
1087 if (str.GetLength()==0)
1088 return str;
1089 if (str[0] == 0xFEFF)
1090 return str.Mid(1);
1091 return str;