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.
22 #include "UnicodeUtils.h"
23 #include "DirFileEnum.h"
24 #include "TortoiseMerge.h"
26 #include "GitAdminDir.h"
32 static char THIS_FILE
[] = __FILE__
;
37 , m_UnicodeType(CFileTextLines::AUTOTYPE
)
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();
59 m_arFileDiffs
.RemoveAll();
62 BOOL
CPatch::ParserGitPatch(CFileTextLines
&PatchLines
,int nIndex
)
65 EOL ending
= EOL_NOENDING
;
68 Chunks
* chunks
= 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
;
85 if( sLine
.Find(_T("diff --git"))==0)
89 //this is a new file diff, so add the last one to
91 m_arFileDiffs
.Add(chunks
);
93 chunks
= new Chunks();
98 if( sLine
.Find(_T("index"))==0 )
100 int dotstart
=sLine
.Find(_T(".."));
103 chunks
->sRevision
= sLine
.Mid(dotstart
-7,7);
104 chunks
->sRevision2
= sLine
.Mid(dotstart
+2,7);
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...
126 sLine
= sLine
.Mid(3); //remove the "---"
128 //at the end of the filepath there's a revision number...
129 int bracket
= sLine
.ReverseFind('(');
131 // some patch files can have another '(' char, especially ones created in Chinese OS
132 bracket
= sLine
.ReverseFind(0xff08);
136 if (chunks
->sFilePath
.IsEmpty())
137 chunks
->sFilePath
= sLine
.Trim();
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");
162 if( sLine
.Find(_T("+++ ")) == 0 )
164 sLine
= sLine
.Mid(3); //remove the "---"
167 //at the end of the filepath there's a revision number...
168 int bracket
= sLine
.ReverseFind('(');
170 // some patch files can have another '(' char, especially ones created in Chinese OS
171 bracket
= sLine
.ReverseFind(0xff08);
174 chunks
->sFilePath2
= sLine
.Trim();
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();
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
);
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
);
222 chunk
->lAddStart
= 1;
223 chunk
->lAddLength
= _ttol(sAdd
);
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)
241 type
= sLine
.GetAt(0);
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
);
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
== '-')
262 chunk
->arLines
.Add(RemoveUnicodeBOM(sLine
.Mid(1)));
263 chunk
->arLinesStates
.Add(PATCHSTATE_REMOVED
);
264 chunk
->arEOLs
.push_back(ending
);
267 else if (type
== '+')
270 chunk
->arLines
.Add(RemoveUnicodeBOM(sLine
.Mid(1)));
271 chunk
->arLinesStates
.Add(PATCHSTATE_ADDED
);
272 chunk
->arEOLs
.push_back(ending
);
277 //none of those lines! what the hell happened here?
278 m_sErrorMessage
.Format(IDS_ERR_PATCH_UNKOWNLINETYPE
, nIndex
);
281 if ((chunk
->lAddLength
== (nAddLineCount
+ nContextLineCount
)) &&
282 chunk
->lRemoveLength
== (nRemoveLineCount
+ nContextLineCount
))
286 chunks
->chunks
.Add(chunk
);
291 nContextLineCount
= 0;
292 nRemoveLineCount
= 0;
300 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
303 m_sErrorMessage
.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH
);
307 m_arFileDiffs
.Add(chunks
);
315 for (int i
=0; i
<chunks
->chunks
.GetCount(); i
++)
317 delete chunks
->chunks
.GetAt(i
);
319 chunks
->chunks
.RemoveAll();
326 BOOL
CPatch::OpenUnifiedDiffFile(const CString
& filename
)
329 EOL ending
= EOL_NOENDING
;
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();
340 m_UnicodeType
= PatchLines
.GetUnicodeType();
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;
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
361 for (nIndex
=0; nIndex
<PatchLines
.GetCount(); nIndex
++)
363 sLine
= PatchLines
.GetAt(nIndex
);
365 if (sLine
.Left(4).Compare(_T("--- "))==0)
367 if ((nIndex
+1)<PatchLines
.GetCount())
369 sLine
= PatchLines
.GetAt(nIndex
+1);
371 if(sLine
.IsEmpty()&&m_IsGitPatch
)
381 if ((PatchLines
.GetCount()-nIndex
) < 2)
383 //no file entry found.
384 m_sErrorMessage
.LoadString(IDS_ERR_PATCH_NOINDEX
);
389 return ParserGitPatch(PatchLines
,nIndex
);
391 //from this point on we have the real unified diff data
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
;
406 if ((sLine
.Left(4).Compare(_T("--- "))==0)&&((sLine
.Find('\t') >= 0)||this->m_IsGitPatch
))
411 //this is a new file diff, so add the last one to
413 m_arFileDiffs
.Add(chunks
);
415 chunks
= new Chunks();
417 int nTab
= sLine
.Find('\t');
422 nTab
=sLine
.GetLength();
428 chunks
->sFilePath
= sLine
.Mid(filestart
, nTab
-filestart
).Trim();
434 case 0: //Index: <filepath>
437 if ((nIndex
+1)<PatchLines
.GetCount())
439 nextLine
= PatchLines
.GetAt(nIndex
+1);
440 if (!nextLine
.IsEmpty())
442 nextLine
.Remove('=');
443 if (nextLine
.IsEmpty())
447 //this is a new file diff, so add the last one to
449 m_arFileDiffs
.Add(chunks
);
451 chunks
= new Chunks();
452 int nColon
= sLine
.Find(':');
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();
477 //was not found at the start of a file diff!
484 case 1: //====================
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)
505 //=========================
507 m_sErrorMessage
.Format(IDS_ERR_PATCH_NOEQUATIONCHARLINE
, nIndex
);
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...
528 sLine
= sLine
.Mid(3); //remove the "---"
530 //at the end of the filepath there's a revision number...
531 int bracket
= sLine
.ReverseFind('(');
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
;
543 if (chunks
->sFilePath
.IsEmpty())
544 chunks
->sFilePath
= sLine
.Trim();
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'));
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
);
563 sLine
= sLine
.Mid(3); //remove the "---"
565 //at the end of the filepath there's a revision number...
566 int bracket
= sLine
.ReverseFind('(');
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
;
577 chunks
->sFilePath2
= sLine
.Trim();
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'));
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
601 for (int i
=0; i
<chunks
->chunks
.GetCount(); i
++)
603 delete chunks
->chunks
.GetAt(i
);
605 chunks
->chunks
.RemoveAll();
610 break; //skip the garbage
612 sLine
= sLine
.Mid(2);
613 sLine
= sLine
.Trim();
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
);
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
);
636 chunk
->lAddStart
= 1;
637 chunk
->lAddLength
= _ttol(sAdd
);
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)
651 type
= sLine
.GetAt(0);
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
);
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
== '-')
672 chunk
->arLines
.Add(RemoveUnicodeBOM(sLine
.Mid(1)));
673 chunk
->arLinesStates
.Add(PATCHSTATE_REMOVED
);
674 chunk
->arEOLs
.push_back(ending
);
677 else if (type
== '+')
680 chunk
->arLines
.Add(RemoveUnicodeBOM(sLine
.Mid(1)));
681 chunk
->arLinesStates
.Add(PATCHSTATE_ADDED
);
682 chunk
->arEOLs
.push_back(ending
);
687 //none of those lines! what the hell happened here?
688 m_sErrorMessage
.Format(IDS_ERR_PATCH_UNKOWNLINETYPE
, nIndex
);
691 if ((chunk
->lAddLength
== (nAddLineCount
+ nContextLineCount
)) &&
692 chunk
->lRemoveLength
== (nRemoveLineCount
+ nContextLineCount
))
696 chunks
->chunks
.Add(chunk
);
701 nContextLineCount
= 0;
702 nRemoveLineCount
= 0;
710 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
713 m_sErrorMessage
.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH
);
717 m_arFileDiffs
.Add(chunks
);
724 for (int i
=0; i
<chunks
->chunks
.GetCount(); i
++)
726 delete chunks
->chunks
.GetAt(i
);
728 chunks
->chunks
.RemoveAll();
735 CString
CPatch::GetFilename(int nIndex
)
739 if (nIndex
< m_arFileDiffs
.GetCount())
741 Chunks
* c
= m_arFileDiffs
.GetAt(nIndex
);
742 CString filepath
= Strip(c
->sFilePath
);
748 CString
CPatch::GetRevision(int nIndex
)
752 if (nIndex
< m_arFileDiffs
.GetCount())
754 Chunks
* c
= m_arFileDiffs
.GetAt(nIndex
);
760 CString
CPatch::GetFilename2(int nIndex
)
764 if (nIndex
< m_arFileDiffs
.GetCount())
766 Chunks
* c
= m_arFileDiffs
.GetAt(nIndex
);
767 CString filepath
= Strip(c
->sFilePath2
);
773 CString
CPatch::GetRevision2(int nIndex
)
777 if (nIndex
< m_arFileDiffs
.GetCount())
779 Chunks
* c
= m_arFileDiffs
.GetAt(nIndex
);
780 return c
->sRevision2
;
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
);
792 // find the entry in the patch file which matches the full path given in sPath.
794 // use the longest path that matches
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();
814 else if (temppath
.CompareNoCase(temp
)==0)
816 if ((nIndex
< 0)&&(! temp
.IsEmpty()))
824 m_sErrorMessage
.Format(IDS_ERR_PATCH_FILENOTINPATCH
, (LPCTSTR
)sPath
);
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
);
862 case PATCHSTATE_REMOVED
:
864 if ((lAddLine
> PatchLines
.GetCount())||(PatchLines
.GetCount()==0))
866 m_sErrorMessage
.Format(IDS_ERR_PATCH_DOESNOTMATCH
, _T(""), (LPCTSTR
)sPatchLine
);
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));
876 if (lAddLine
> PatchLines
.GetCount())
878 m_sErrorMessage
.Format(IDS_ERR_PATCH_DOESNOTMATCH
, (LPCTSTR
)sPatchLine
, _T(""));
881 PatchLines
.RemoveAt(lAddLine
-1);
884 case PATCHSTATE_ADDED
:
888 PatchLines
.InsertAt(lAddLine
-1, sPatchLine
, ending
);
892 case PATCHSTATE_CONTEXT
:
894 if (lAddLine
> PatchLines
.GetCount())
896 m_sErrorMessage
.Format(IDS_ERR_PATCH_DOESNOTMATCH
, _T(""), (LPCTSTR
)sPatchLine
);
901 if (lRemoveLine
== 0)
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))
910 else if (((lAddLine
+ 1) < PatchLines
.GetCount())&&(sPatchLine
.Compare(PatchLines
.GetAt(lAddLine
+1))==0))
912 else if ((lRemoveLine
< PatchLines
.GetCount())&&(sPatchLine
.Compare(PatchLines
.GetAt(lRemoveLine
))==0))
916 m_sErrorMessage
.Format(IDS_ERR_PATCH_DOESNOTMATCH
, (LPCTSTR
)sPatchLine
, (LPCTSTR
)PatchLines
.GetAt(lAddLine
-1));
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);
937 BOOL
CPatch::HasExpandedKeyWords(const CString
& line
)
939 if (line
.Find(_T("$LastChangedDate"))>=0)
941 if (line
.Find(_T("$Date"))>=0)
943 if (line
.Find(_T("$LastChangedRevision"))>=0)
945 if (line
.Find(_T("$Rev"))>=0)
947 if (line
.Find(_T("$LastChangedBy"))>=0)
949 if (line
.Find(_T("$Author"))>=0)
951 if (line
.Find(_T("$HeadURL"))>=0)
953 if (line
.Find(_T("$URL"))>=0)
955 if (line
.Find(_T("$Id"))>=0)
960 CString
CPatch::CheckPatchPath(const CString
& path
)
962 //first check if the path already matches
963 if (CountMatches(path
) > (GetNumberOfFiles()/3))
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))
973 //still no match found. So try sub folders
976 CDirFileEnum
filefinder(path
);
977 while (filefinder
.NextFile(subpath
, &isDir
))
981 if (g_GitAdminDir
.IsAdminDirPath(subpath
))
983 if (CountMatches(subpath
) > (GetNumberOfFiles()/3))
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
992 while (upperpath
.ReverseFind('\\')>0)
994 upperpath
= upperpath
.Left(upperpath
.ReverseFind('\\'));
995 if (CountDirMatches(upperpath
) > (GetNumberOfFiles()/3))
1002 int CPatch::CountMatches(const CString
& path
)
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
))
1017 int CPatch::CountDirMatches(const CString
& path
)
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
))
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
++)
1048 if ( CountMatches(path
) > GetNumberOfFiles()/3 )
1050 // Use current m_nStrip
1055 // Stripping doesn't help so reset it again
1060 CString
CPatch::Strip(const CString
& filename
)
1062 CString s
= filename
;
1065 // Remove windows drive letter "c:"
1066 if ( s
.GetLength()>2 && s
[1]==':')
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"
1078 s
= s
.Mid(s
.FindOneOf(_T("/\\"))+1);
1084 CString
CPatch::RemoveUnicodeBOM(const CString
& str
)
1086 if (str
.GetLength()==0)
1088 if (str
[0] == 0xFEFF)