1 // TortoiseMerge - a Diff/Patch program
\r
3 // Copyright (C) 2004-2009 - TortoiseSVN
\r
5 // This program is free software; you can redistribute it and/or
\r
6 // modify it under the terms of the GNU General Public License
\r
7 // as published by the Free Software Foundation; either version 2
\r
8 // of the License, or (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, write to the Free Software Foundation,
\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
20 #include "Resource.h"
\r
21 #include "UnicodeUtils.h"
\r
22 #include "DirFileEnum.h"
\r
23 #include "TortoiseMerge.h"
\r
25 #include "GitAdminDir.h"
\r
29 #define new DEBUG_NEW
\r
31 static char THIS_FILE[] = __FILE__;
\r
34 CPatch::CPatch(void)
\r
37 m_IsGitPatch = false;
\r
40 CPatch::~CPatch(void)
\r
45 void CPatch::FreeMemory()
\r
47 for (int i=0; i<m_arFileDiffs.GetCount(); i++)
\r
49 Chunks * chunks = m_arFileDiffs.GetAt(i);
\r
50 for (int j=0; j<chunks->chunks.GetCount(); j++)
\r
52 delete chunks->chunks.GetAt(j);
\r
54 chunks->chunks.RemoveAll();
\r
57 m_arFileDiffs.RemoveAll();
\r
60 BOOL CPatch::ParserGitPatch(CFileTextLines &PatchLines,int nIndex)
\r
63 EOL ending = EOL_NOENDING;
\r
66 Chunks * chunks = NULL;
\r
67 Chunk * chunk = NULL;
\r
68 int nAddLineCount = 0;
\r
69 int nRemoveLineCount = 0;
\r
70 int nContextLineCount = 0;
\r
71 for ( ;nIndex<PatchLines.GetCount(); nIndex++)
\r
73 sLine = PatchLines.GetAt(nIndex);
\r
74 ending = PatchLines.GetLineEnding(nIndex);
\r
75 if (ending != EOL_NOENDING)
\r
76 ending = EOL_AUTOLINE;
\r
83 if( sLine.Find(_T("diff --git"))==0)
\r
87 //this is a new file diff, so add the last one to
\r
89 m_arFileDiffs.Add(chunks);
\r
91 chunks = new Chunks();
\r
96 if( sLine.Find(_T("index"))==0 )
\r
98 int dotstart=sLine.Find(_T(".."));
\r
101 chunks->sRevision = sLine.Mid(dotstart-7,7);
\r
102 chunks->sRevision2 = sLine.Mid(dotstart+2,7);
\r
107 if( sLine.Find(_T("--- "))==0 )
\r
109 if (sLine.Left(3).Compare(_T("---"))!=0)
\r
111 //no starting "---" found
\r
112 //seems to be either garbage or just
\r
113 //a binary file. So start over...
\r
124 sLine = sLine.Mid(3); //remove the "---"
\r
125 sLine =sLine.Trim();
\r
126 //at the end of the filepath there's a revision number...
\r
127 int bracket = sLine.ReverseFind('(');
\r
129 // some patch files can have another '(' char, especially ones created in Chinese OS
\r
130 bracket = sLine.ReverseFind(0xff08);
\r
134 if (chunks->sFilePath.IsEmpty())
\r
135 chunks->sFilePath = sLine.Trim();
\r
138 chunks->sFilePath = sLine.Left(bracket-1).Trim();
\r
140 if (chunks->sFilePath.Find('\t')>=0)
\r
142 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
\r
144 if( chunks->sFilePath.Find(_T("a/")) == 0 )
\r
145 chunks->sFilePath=chunks->sFilePath.Mid(2);
\r
147 chunks->sFilePath.Replace(_T('/'),_T('\\'));
\r
151 if( sLine.Find(_T("+++ ")) == 0 )
\r
153 sLine = sLine.Mid(3); //remove the "---"
\r
154 sLine =sLine.Trim();
\r
156 //at the end of the filepath there's a revision number...
\r
157 int bracket = sLine.ReverseFind('(');
\r
159 // some patch files can have another '(' char, especially ones created in Chinese OS
\r
160 bracket = sLine.ReverseFind(0xff08);
\r
163 chunks->sFilePath2 = sLine.Trim();
\r
165 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
\r
166 if (chunks->sFilePath2.Find('\t')>=0)
\r
168 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
\r
170 if( chunks->sFilePath2.Find(_T("a/")) == 0 )
\r
171 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
\r
173 chunks->sFilePath2.Replace(_T('/'),_T('\\'));
\r
176 //@@ -xxx,xxx +xxx,xxx @@
\r
177 if( sLine.Find(_T("@@")) == 0 )
\r
179 sLine = sLine.Mid(2);
\r
180 sLine = sLine.Trim();
\r
181 chunk = new Chunk();
\r
182 CString sRemove = sLine.Left(sLine.Find(' '));
\r
183 CString sAdd = sLine.Mid(sLine.Find(' '));
\r
184 chunk->lRemoveStart = (-_ttol(sRemove));
\r
185 if (sRemove.Find(',')>=0)
\r
187 sRemove = sRemove.Mid(sRemove.Find(',')+1);
\r
188 chunk->lRemoveLength = _ttol(sRemove);
\r
192 chunk->lRemoveStart = 0;
\r
193 chunk->lRemoveLength = (-_ttol(sRemove));
\r
195 chunk->lAddStart = _ttol(sAdd);
\r
196 if (sAdd.Find(',')>=0)
\r
198 sAdd = sAdd.Mid(sAdd.Find(',')+1);
\r
199 chunk->lAddLength = _ttol(sAdd);
\r
203 chunk->lAddStart = 1;
\r
204 chunk->lAddLength = _ttol(sAdd);
\r
213 case 5: //[ |+|-] <sourceline>
\r
215 //this line is either a context line (with a ' ' in front)
\r
216 //a line added (with a '+' in front)
\r
217 //or a removed line (with a '-' in front)
\r
219 if (sLine.IsEmpty())
\r
222 type = sLine.GetAt(0);
\r
225 //it's a context line - we don't use them here right now
\r
226 //but maybe in the future the patch algorithm can be
\r
227 //extended to use those in case the file to patch has
\r
228 //already changed and no base file is around...
\r
229 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
\r
230 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
\r
231 chunk->arEOLs.push_back(ending);
\r
232 nContextLineCount++;
\r
234 else if (type == '\\')
\r
236 //it's a context line (sort of):
\r
237 //warnings start with a '\' char (e.g. "\ No newline at end of file")
\r
238 //so just ignore this...
\r
240 else if (type == '-')
\r
243 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
\r
244 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
\r
245 chunk->arEOLs.push_back(ending);
\r
246 nRemoveLineCount++;
\r
248 else if (type == '+')
\r
251 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
\r
252 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
\r
253 chunk->arEOLs.push_back(ending);
\r
258 //none of those lines! what the hell happened here?
\r
259 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
\r
262 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
\r
263 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
\r
265 //chunk is finished
\r
267 chunks->chunks.Add(chunk);
\r
272 nContextLineCount = 0;
\r
273 nRemoveLineCount = 0;
\r
280 } // switch (state)
\r
281 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
\r
284 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
\r
288 m_arFileDiffs.Add(chunks);
\r
296 for (int i=0; i<chunks->chunks.GetCount(); i++)
\r
298 delete chunks->chunks.GetAt(i);
\r
300 chunks->chunks.RemoveAll();
\r
307 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)
\r
310 EOL ending = EOL_NOENDING;
\r
311 INT_PTR nIndex = 0;
\r
312 INT_PTR nLineCount = 0;
\r
313 g_crasher.AddFile((LPCSTR)(LPCTSTR)filename, (LPCSTR)(LPCTSTR)_T("unified diff file"));
\r
315 CFileTextLines PatchLines;
\r
316 if (!PatchLines.Load(filename))
\r
318 m_sErrorMessage = PatchLines.GetErrorString();
\r
321 m_UnicodeType = PatchLines.GetUnicodeType();
\r
323 nLineCount = PatchLines.GetCount();
\r
324 //now we got all the lines of the patch file
\r
325 //in our array - parsing can start...
\r
327 for(nIndex=0;PatchLines.GetCount();nIndex++)
\r
329 sLine = PatchLines.GetAt(nIndex);
\r
330 if(sLine.Left(10).Compare(_T("diff --git")) == 0)
\r
332 this->m_IsGitPatch=true;
\r
337 //first, skip possible garbage at the beginning
\r
338 //garbage is finished when a line starts with "Index: "
\r
339 //and the next line consists of only "=" characters
\r
340 if( !m_IsGitPatch )
\r
342 for (nIndex=0; nIndex<PatchLines.GetCount(); nIndex++)
\r
344 sLine = PatchLines.GetAt(nIndex);
\r
346 if (sLine.Left(4).Compare(_T("--- "))==0)
\r
348 if ((nIndex+1)<PatchLines.GetCount())
\r
350 sLine = PatchLines.GetAt(nIndex+1);
\r
352 if(sLine.IsEmpty()&&m_IsGitPatch)
\r
355 sLine.Replace(_T("="), _T(""));
\r
356 if (sLine.IsEmpty())
\r
362 if ((PatchLines.GetCount()-nIndex) < 2)
\r
364 //no file entry found.
\r
365 m_sErrorMessage.LoadString(IDS_ERR_PATCH_NOINDEX);
\r
370 return ParserGitPatch(PatchLines,nIndex);
\r
372 //from this point on we have the real unified diff data
\r
374 Chunks * chunks = NULL;
\r
375 Chunk * chunk = NULL;
\r
376 int nAddLineCount = 0;
\r
377 int nRemoveLineCount = 0;
\r
378 int nContextLineCount = 0;
\r
379 for ( ;nIndex<PatchLines.GetCount(); nIndex++)
\r
381 sLine = PatchLines.GetAt(nIndex);
\r
382 ending = PatchLines.GetLineEnding(nIndex);
\r
383 if (ending != EOL_NOENDING)
\r
384 ending = EOL_AUTOLINE;
\r
387 if ((sLine.Left(4).Compare(_T("--- "))==0)&&((sLine.Find('\t') >= 0)||this->m_IsGitPatch))
\r
392 //this is a new file diff, so add the last one to
\r
394 m_arFileDiffs.Add(chunks);
\r
396 chunks = new Chunks();
\r
398 int nTab = sLine.Find('\t');
\r
403 nTab=sLine.GetLength();
\r
409 chunks->sFilePath = sLine.Mid(filestart, nTab-filestart).Trim();
\r
415 case 0: //Index: <filepath>
\r
418 if ((nIndex+1)<PatchLines.GetCount())
\r
420 nextLine = PatchLines.GetAt(nIndex+1);
\r
421 if (!nextLine.IsEmpty())
\r
423 nextLine.Replace(_T("="), _T(""));
\r
424 if (nextLine.IsEmpty())
\r
428 //this is a new file diff, so add the last one to
\r
430 m_arFileDiffs.Add(chunks);
\r
432 chunks = new Chunks();
\r
433 int nColon = sLine.Find(':');
\r
436 chunks->sFilePath = sLine.Mid(nColon+1).Trim();
\r
437 if (chunks->sFilePath.Find('\t')>=0)
\r
438 chunks->sFilePath.Left(chunks->sFilePath.Find('\t')).TrimRight();
\r
439 if (chunks->sFilePath.Right(9).Compare(_T("(deleted)"))==0)
\r
440 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-9).TrimRight();
\r
441 if (chunks->sFilePath.Right(7).Compare(_T("(added)"))==0)
\r
442 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-7).TrimRight();
\r
454 if (chunks == NULL)
\r
457 //Index: <filepath>
\r
458 //was not found at the start of a file diff!
\r
465 case 1: //====================
\r
467 sLine.Replace(_T("="), _T(""));
\r
468 if (sLine.IsEmpty())
\r
470 // if the next line is already the start of the chunk,
\r
471 // then the patch/diff file was not created by svn. But we
\r
472 // still try to use it
\r
473 if (PatchLines.GetCount() > (nIndex + 1))
\r
476 if (PatchLines.GetAt(nIndex+1).Left(2).Compare(_T("@@"))==0)
\r
486 //=========================
\r
488 m_sErrorMessage.Format(IDS_ERR_PATCH_NOEQUATIONCHARLINE, nIndex);
\r
493 case 2: //--- <filepath>
\r
495 if (sLine.Left(3).Compare(_T("---"))!=0)
\r
497 //no starting "---" found
\r
498 //seems to be either garbage or just
\r
499 //a binary file. So start over...
\r
509 sLine = sLine.Mid(3); //remove the "---"
\r
510 sLine =sLine.Trim();
\r
511 //at the end of the filepath there's a revision number...
\r
512 int bracket = sLine.ReverseFind('(');
\r
514 // some patch files can have another '(' char, especially ones created in Chinese OS
\r
515 bracket = sLine.ReverseFind(0xff08);
\r
516 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
\r
517 num = num.Mid(num.Find(' '));
\r
518 num = num.Trim(_T(" )"));
\r
519 // here again, check for the Chinese bracket
\r
520 num = num.Trim(0xff09);
\r
521 chunks->sRevision = num;
\r
524 if (chunks->sFilePath.IsEmpty())
\r
525 chunks->sFilePath = sLine.Trim();
\r
528 chunks->sFilePath = sLine.Left(bracket-1).Trim();
\r
529 if (chunks->sFilePath.Find('\t')>=0)
\r
531 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
\r
536 case 3: //+++ <filepath>
\r
538 if (sLine.Left(3).Compare(_T("+++"))!=0)
\r
540 //no starting "+++" found
\r
541 m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);
\r
544 sLine = sLine.Mid(3); //remove the "---"
\r
545 sLine =sLine.Trim();
\r
546 //at the end of the filepath there's a revision number...
\r
547 int bracket = sLine.ReverseFind('(');
\r
549 // some patch files can have another '(' char, especially ones created in Chinese OS
\r
550 bracket = sLine.ReverseFind(0xff08);
\r
551 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
\r
552 num = num.Mid(num.Find(' '));
\r
553 num = num.Trim(_T(" )"));
\r
554 // here again, check for the Chinese bracket
\r
555 num = num.Trim(0xff09);
\r
556 chunks->sRevision2 = num;
\r
558 chunks->sFilePath2 = sLine.Trim();
\r
560 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
\r
561 if (chunks->sFilePath2.Find('\t')>=0)
\r
563 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
\r
568 case 4: //@@ -xxx,xxx +xxx,xxx @@
\r
570 //start of a new chunk
\r
571 if (sLine.Left(2).Compare(_T("@@"))!=0)
\r
573 //chunk doesn't start with "@@"
\r
574 //so there's garbage in between two file diffs
\r
582 for (int i=0; i<chunks->chunks.GetCount(); i++)
\r
584 delete chunks->chunks.GetAt(i);
\r
586 chunks->chunks.RemoveAll();
\r
591 break; //skip the garbage
\r
593 sLine = sLine.Mid(2);
\r
594 sLine = sLine.Trim();
\r
595 chunk = new Chunk();
\r
596 CString sRemove = sLine.Left(sLine.Find(' '));
\r
597 CString sAdd = sLine.Mid(sLine.Find(' '));
\r
598 chunk->lRemoveStart = (-_ttol(sRemove));
\r
599 if (sRemove.Find(',')>=0)
\r
601 sRemove = sRemove.Mid(sRemove.Find(',')+1);
\r
602 chunk->lRemoveLength = _ttol(sRemove);
\r
606 chunk->lRemoveStart = 0;
\r
607 chunk->lRemoveLength = (-_ttol(sRemove));
\r
609 chunk->lAddStart = _ttol(sAdd);
\r
610 if (sAdd.Find(',')>=0)
\r
612 sAdd = sAdd.Mid(sAdd.Find(',')+1);
\r
613 chunk->lAddLength = _ttol(sAdd);
\r
617 chunk->lAddStart = 1;
\r
618 chunk->lAddLength = _ttol(sAdd);
\r
623 case 5: //[ |+|-] <sourceline>
\r
625 //this line is either a context line (with a ' ' in front)
\r
626 //a line added (with a '+' in front)
\r
627 //or a removed line (with a '-' in front)
\r
629 if (sLine.IsEmpty())
\r
632 type = sLine.GetAt(0);
\r
635 //it's a context line - we don't use them here right now
\r
636 //but maybe in the future the patch algorithm can be
\r
637 //extended to use those in case the file to patch has
\r
638 //already changed and no base file is around...
\r
639 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
\r
640 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
\r
641 chunk->arEOLs.push_back(ending);
\r
642 nContextLineCount++;
\r
644 else if (type == '\\')
\r
646 //it's a context line (sort of):
\r
647 //warnings start with a '\' char (e.g. "\ No newline at end of file")
\r
648 //so just ignore this...
\r
650 else if (type == '-')
\r
653 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
\r
654 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
\r
655 chunk->arEOLs.push_back(ending);
\r
656 nRemoveLineCount++;
\r
658 else if (type == '+')
\r
661 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
\r
662 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
\r
663 chunk->arEOLs.push_back(ending);
\r
668 //none of those lines! what the hell happened here?
\r
669 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
\r
672 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
\r
673 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
\r
675 //chunk is finished
\r
677 chunks->chunks.Add(chunk);
\r
682 nContextLineCount = 0;
\r
683 nRemoveLineCount = 0;
\r
690 } // switch (state)
\r
691 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
\r
694 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
\r
698 m_arFileDiffs.Add(chunks);
\r
705 for (int i=0; i<chunks->chunks.GetCount(); i++)
\r
707 delete chunks->chunks.GetAt(i);
\r
709 chunks->chunks.RemoveAll();
\r
716 CString CPatch::GetFilename(int nIndex)
\r
720 if (nIndex < m_arFileDiffs.GetCount())
\r
722 Chunks * c = m_arFileDiffs.GetAt(nIndex);
\r
723 CString filepath = Strip(c->sFilePath);
\r
729 CString CPatch::GetRevision(int nIndex)
\r
733 if (nIndex < m_arFileDiffs.GetCount())
\r
735 Chunks * c = m_arFileDiffs.GetAt(nIndex);
\r
736 return c->sRevision;
\r
741 CString CPatch::GetFilename2(int nIndex)
\r
745 if (nIndex < m_arFileDiffs.GetCount())
\r
747 Chunks * c = m_arFileDiffs.GetAt(nIndex);
\r
748 CString filepath = Strip(c->sFilePath2);
\r
754 CString CPatch::GetRevision2(int nIndex)
\r
758 if (nIndex < m_arFileDiffs.GetCount())
\r
760 Chunks * c = m_arFileDiffs.GetAt(nIndex);
\r
761 return c->sRevision2;
\r
766 BOOL CPatch::PatchFile(const CString& sPath, const CString& sSavePath, const CString& sBaseFile)
\r
768 if (PathIsDirectory(sPath))
\r
770 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);
\r
773 // find the entry in the patch file which matches the full path given in sPath.
\r
775 // use the longest path that matches
\r
777 for (int i=0; i<GetNumberOfFiles(); i++)
\r
779 CString temppath = sPath;
\r
780 CString temp = GetFilename(i);
\r
781 temppath.Replace('/', '\\');
\r
782 temp.Replace('/', '\\');
\r
783 if (temppath.Mid(temppath.GetLength()-temp.GetLength()-1, 1).CompareNoCase(_T("\\"))==0)
\r
785 temppath = temppath.Right(temp.GetLength());
\r
786 if ((temp.CompareNoCase(temppath)==0))
\r
788 if (nMaxMatch < temp.GetLength())
\r
790 nMaxMatch = temp.GetLength();
\r
795 else if (temppath.CompareNoCase(temp)==0)
\r
797 if ((nIndex < 0)&&(! temp.IsEmpty()))
\r
805 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);
\r
810 CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;
\r
811 if (PathFileExists(sPatchFile))
\r
813 g_crasher.AddFile((LPCSTR)(LPCTSTR)sPatchFile, (LPCSTR)(LPCTSTR)_T("File to patch"));
\r
815 CFileTextLines PatchLines;
\r
816 CFileTextLines PatchLinesResult;
\r
817 PatchLines.Load(sPatchFile);
\r
818 PatchLinesResult = PatchLines; //.Copy(PatchLines);
\r
819 PatchLines.CopySettings(&PatchLinesResult);
\r
821 Chunks * chunks = m_arFileDiffs.GetAt(nIndex);
\r
823 for (int i=0; i<chunks->chunks.GetCount(); i++)
\r
825 Chunk * chunk = chunks->chunks.GetAt(i);
\r
826 LONG lRemoveLine = chunk->lRemoveStart;
\r
827 LONG lAddLine = chunk->lAddStart;
\r
828 for (int j=0; j<chunk->arLines.GetCount(); j++)
\r
830 CString sPatchLine = chunk->arLines.GetAt(j);
\r
831 EOL ending = chunk->arEOLs[j];
\r
832 if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))
\r
834 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))
\r
836 // convert the UTF-8 contents in CString sPatchLine into a CStringA
\r
837 sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));
\r
840 int nPatchState = (int)chunk->arLinesStates.GetAt(j);
\r
841 switch (nPatchState)
\r
843 case PATCHSTATE_REMOVED:
\r
845 if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))
\r
847 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
\r
852 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))
\r
854 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
\r
857 if (lAddLine > PatchLines.GetCount())
\r
859 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));
\r
862 PatchLines.RemoveAt(lAddLine-1);
\r
865 case PATCHSTATE_ADDED:
\r
869 PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);
\r
873 case PATCHSTATE_CONTEXT:
\r
875 if (lAddLine > PatchLines.GetCount())
\r
877 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
\r
882 if (lRemoveLine == 0)
\r
884 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&
\r
885 (!HasExpandedKeyWords(sPatchLine)) &&
\r
886 (lRemoveLine <= PatchLines.GetCount()) &&
\r
887 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))
\r
889 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))
\r
891 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))
\r
893 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))
\r
897 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
\r
908 } // switch (nPatchState)
\r
909 } // for (j=0; j<chunk->arLines.GetCount(); j++)
\r
910 } // for (int i=0; i<chunks->chunks.GetCount(); i++)
\r
911 if (!sSavePath.IsEmpty())
\r
913 PatchLines.Save(sSavePath, false);
\r
918 BOOL CPatch::HasExpandedKeyWords(const CString& line)
\r
920 if (line.Find(_T("$LastChangedDate"))>=0)
\r
922 if (line.Find(_T("$Date"))>=0)
\r
924 if (line.Find(_T("$LastChangedRevision"))>=0)
\r
926 if (line.Find(_T("$Rev"))>=0)
\r
928 if (line.Find(_T("$LastChangedBy"))>=0)
\r
930 if (line.Find(_T("$Author"))>=0)
\r
932 if (line.Find(_T("$HeadURL"))>=0)
\r
934 if (line.Find(_T("$URL"))>=0)
\r
936 if (line.Find(_T("$Id"))>=0)
\r
941 CString CPatch::CheckPatchPath(const CString& path)
\r
943 //first check if the path already matches
\r
944 if (CountMatches(path) > (GetNumberOfFiles()/3))
\r
946 //now go up the tree and try again
\r
947 CString upperpath = path;
\r
948 while (upperpath.ReverseFind('\\')>0)
\r
950 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
\r
951 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
\r
954 //still no match found. So try sub folders
\r
955 bool isDir = false;
\r
957 CDirFileEnum filefinder(path);
\r
958 while (filefinder.NextFile(subpath, &isDir))
\r
962 if (g_GitAdminDir.IsAdminDirPath(subpath))
\r
964 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
\r
968 // if a patch file only contains newly added files
\r
969 // we can't really find the correct path.
\r
970 // But: we can compare paths strings without the filenames
\r
971 // and check if at least those match
\r
973 while (upperpath.ReverseFind('\\')>0)
\r
975 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
\r
976 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
\r
983 int CPatch::CountMatches(const CString& path)
\r
986 for (int i=0; i<GetNumberOfFiles(); ++i)
\r
988 CString temp = GetFilename(i);
\r
989 temp.Replace('/', '\\');
\r
990 if (PathIsRelative(temp))
\r
991 temp = path + _T("\\")+ temp;
\r
992 if (PathFileExists(temp))
\r
998 int CPatch::CountDirMatches(const CString& path)
\r
1001 for (int i=0; i<GetNumberOfFiles(); ++i)
\r
1003 CString temp = GetFilename(i);
\r
1004 temp.Replace('/', '\\');
\r
1005 if (PathIsRelative(temp))
\r
1006 temp = path + _T("\\")+ temp;
\r
1007 // remove the filename
\r
1008 temp = temp.Left(temp.ReverseFind('\\'));
\r
1009 if (PathFileExists(temp))
\r
1015 BOOL CPatch::StripPrefixes(const CString& path)
\r
1017 int nSlashesMax = 0;
\r
1018 for (int i=0; i<GetNumberOfFiles(); i++)
\r
1020 CString filename = GetFilename(i);
\r
1021 filename.Replace('/','\\');
\r
1022 int nSlashes = filename.Replace('\\','/');
\r
1023 nSlashesMax = max(nSlashesMax,nSlashes);
\r
1026 for (int nStrip=1;nStrip<nSlashesMax;nStrip++)
\r
1028 m_nStrip = nStrip;
\r
1029 if ( CountMatches(path) > GetNumberOfFiles()/3 )
\r
1031 // Use current m_nStrip
\r
1036 // Stripping doesn't help so reset it again
\r
1041 CString CPatch::Strip(const CString& filename)
\r
1043 CString s = filename;
\r
1046 // Remove windows drive letter "c:"
\r
1047 if ( s.GetLength()>2 && s[1]==':')
\r
1052 for (int nStrip=1;nStrip<=m_nStrip;nStrip++)
\r
1054 // "/home/ts/my-working-copy/dir/file.txt"
\r
1055 // "home/ts/my-working-copy/dir/file.txt"
\r
1056 // "ts/my-working-copy/dir/file.txt"
\r
1057 // "my-working-copy/dir/file.txt"
\r
1059 s = s.Mid(s.FindOneOf(_T("/\\"))+1);
\r
1065 CString CPatch::RemoveUnicodeBOM(const CString& str)
\r
1067 if (str.GetLength()==0)
\r
1069 if (str[0] == 0xFEFF)
\r
1070 return str.Mid(1);
\r