Update editorconfig
[TortoiseGit.git] / src / TortoiseMerge / Patch.cpp
blobcd295eb69ea620cdbdc2b7c4fed31541ac1c481f
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2009-2013, 2015-2016 - TortoiseGit
4 // Copyright (C) 2012-2013 - Sven Strickroth <email@cs-ware.de>
5 // Copyright (C) 2004-2009,2011-2014 - 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 "GitAdminDir.h"
27 #include "Patch.h"
28 #include "StringUtils.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)
42 CPatch::~CPatch(void)
44 FreeMemory();
47 void CPatch::FreeMemory()
49 for (int i=0; i<m_arFileDiffs.GetCount(); ++i)
51 Chunks * chunks = m_arFileDiffs.GetAt(i);
52 for (int j=0; j<chunks->chunks.GetCount(); ++j)
54 delete chunks->chunks.GetAt(j);
56 chunks->chunks.RemoveAll();
57 delete chunks;
59 m_arFileDiffs.RemoveAll();
62 BOOL CPatch::ParsePatchFile(CFileTextLines &PatchLines)
64 CString sLine;
65 EOL ending = EOL_NOENDING;
67 int state = 0;
68 int nIndex = 0;
69 Chunks* chunks = nullptr;
70 Chunk* chunk = nullptr;
71 int nAddLineCount = 0;
72 int nRemoveLineCount = 0;
73 int nContextLineCount = 0;
74 std::map<CString, int> filenamesToPatch;
75 for ( ; nIndex < PatchLines.GetCount(); ++nIndex)
77 sLine = PatchLines.GetAt(nIndex);
78 ending = PatchLines.GetLineEnding(nIndex);
79 if (ending != EOL_NOENDING)
80 ending = EOL_AUTOLINE;
82 switch (state)
84 case 0:
86 // diff
87 if (CStringUtils::StartsWith(sLine, L"diff "))
89 if (chunks)
91 //this is a new file diff, so add the last one to
92 //our array.
93 if (chunks->chunks.GetCount() > 0)
94 m_arFileDiffs.Add(chunks);
95 else
96 delete chunks;
98 chunks = new Chunks();
99 state = 1;
100 break;
103 // fallthrough!
104 case 1:
106 //index
107 if (CStringUtils::StartsWith(sLine, L"index"))
109 int dotstart=sLine.Find(_T(".."));
110 if(dotstart>=0 && chunks)
112 chunks->sRevision = sLine.Mid(dotstart-7,7);
113 chunks->sRevision2 = sLine.Mid(dotstart+2,7);
115 break;
118 //---
119 if (CStringUtils::StartsWith(sLine, L"--- "))
121 if (state == 0)
123 if (chunks)
125 //this is a new file diff, so add the last one to
126 //our array.
127 if (chunks->chunks.GetCount() > 0)
128 m_arFileDiffs.Add(chunks);
129 else
130 delete chunks;
132 chunks = new Chunks();
135 sLine = sLine.Mid(3); //remove the "---"
136 sLine =sLine.Trim();
137 //at the end of the filepath there's a revision number...
138 int bracket = sLine.ReverseFind('(');
139 if (bracket < 0)
140 // some patch files can have another '(' char, especially ones created in Chinese OS
141 bracket = sLine.ReverseFind(0xff08);
143 if (bracket < 0)
145 if (chunks->sFilePath.IsEmpty())
146 chunks->sFilePath = sLine.Trim();
148 else
149 chunks->sFilePath = sLine.Left(bracket-1).Trim();
151 if (chunks->sFilePath.Find('\t')>=0)
152 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
153 if (CStringUtils::StartsWith(chunks->sFilePath, L"\"") && chunks->sFilePath.ReverseFind(_T('"')) == chunks->sFilePath.GetLength() - 1)
154 chunks->sFilePath=chunks->sFilePath.Mid(1, chunks->sFilePath.GetLength() - 2);
155 if (CStringUtils::StartsWith(chunks->sFilePath, L"a/"))
156 chunks->sFilePath=chunks->sFilePath.Mid(2);
158 if (CStringUtils::StartsWith(chunks->sFilePath, L"b/"))
159 chunks->sFilePath=chunks->sFilePath.Mid(2);
162 chunks->sFilePath.Replace(_T('/'),_T('\\'));
164 if (chunks->sFilePath == _T("\\dev\\null") || chunks->sFilePath == _T("/dev/null"))
165 chunks->sFilePath = _T("NUL");
167 state = 3;
169 if (state == 0)
171 if (CStringUtils::StartsWith(sLine, L"@@"))
173 if (chunks)
175 nIndex--;
176 state = 4;
178 else
179 break;
183 break;
185 case 3:
187 // +++
188 if (!CStringUtils::StartsWith(sLine, L"+++"))
190 // no starting "+++" found
191 m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);
192 goto errorcleanup;
194 sLine = sLine.Mid(3); //remove the "---"
195 sLine =sLine.Trim();
197 //at the end of the filepath there's a revision number...
198 int bracket = sLine.ReverseFind('(');
199 if (bracket < 0)
200 // some patch files can have another '(' char, especially ones created in Chinese OS
201 bracket = sLine.ReverseFind(0xff08);
203 if (bracket < 0)
204 chunks->sFilePath2 = sLine.Trim();
205 else
206 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
207 if (chunks->sFilePath2.Find('\t')>=0)
208 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
209 if (CStringUtils::StartsWith(chunks->sFilePath2, L"\"") && chunks->sFilePath2.ReverseFind(_T('"')) == chunks->sFilePath2.GetLength() - 1)
210 chunks->sFilePath2=chunks->sFilePath2.Mid(1, chunks->sFilePath2.GetLength() - 2);
211 if (CStringUtils::StartsWith(chunks->sFilePath2, L"a/"))
212 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
214 if (CStringUtils::StartsWith(chunks->sFilePath2, L"b/"))
215 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
217 chunks->sFilePath2.Replace(_T('/'),_T('\\'));
219 if (chunks->sFilePath2 == _T("\\dev\\null") || chunks->sFilePath2 == _T("/dev/null"))
220 chunks->sFilePath2 = _T("NUL");
222 ++state;
224 break;
226 case 4:
228 //start of a new chunk
229 if (!CStringUtils::StartsWith(sLine, L"@@"))
231 //chunk doesn't start with "@@"
232 //so there's garbage in between two file diffs
233 state = 0;
234 if (chunk)
236 delete chunk;
237 chunk = nullptr;
238 if (chunks)
240 for (int i = 0; i < chunks->chunks.GetCount(); ++i)
242 delete chunks->chunks.GetAt(i);
244 chunks->chunks.RemoveAll();
245 delete chunks;
246 chunks = nullptr;
249 break; //skip the garbage
252 //@@ -xxx,xxx +xxx,xxx @@
253 sLine = sLine.Mid(2);
254 sLine = sLine.Trim();
255 chunk = new Chunk();
256 CString sRemove = sLine.Left(sLine.Find(' '));
257 CString sAdd = sLine.Mid(sLine.Find(' '));
258 chunk->lRemoveStart = (-_ttol(sRemove));
259 if (sRemove.Find(',')>=0)
261 sRemove = sRemove.Mid(sRemove.Find(',')+1);
262 chunk->lRemoveLength = _ttol(sRemove);
264 else
266 chunk->lRemoveStart = 0;
267 chunk->lRemoveLength = (-_ttol(sRemove));
269 chunk->lAddStart = _ttol(sAdd);
270 if (sAdd.Find(',')>=0)
272 sAdd = sAdd.Mid(sAdd.Find(',')+1);
273 chunk->lAddLength = _ttol(sAdd);
275 else
277 chunk->lAddStart = 1;
278 chunk->lAddLength = _ttol(sAdd);
280 ++state;
282 break;
284 case 5: //[ |+|-] <sourceline>
286 //this line is either a context line (with a ' ' in front)
287 //a line added (with a '+' in front)
288 //or a removed line (with a '-' in front)
289 TCHAR type;
290 if (sLine.IsEmpty())
291 type = ' ';
292 else
293 type = sLine.GetAt(0);
294 if (type == ' ')
296 //it's a context line - we don't use them here right now
297 //but maybe in the future the patch algorithm can be
298 //extended to use those in case the file to patch has
299 //already changed and no base file is around...
300 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
301 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
302 chunk->arEOLs.push_back(ending);
303 ++nContextLineCount;
305 else if (type == '\\')
307 //it's a context line (sort of):
308 //warnings start with a '\' char (e.g. "\ No newline at end of file")
309 //so just ignore this...
311 else if (type == '-')
313 //a removed line
314 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
315 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
316 chunk->arEOLs.push_back(ending);
317 ++nRemoveLineCount;
319 else if (type == '+')
321 //an added line
322 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
323 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
324 chunk->arEOLs.push_back(ending);
325 ++nAddLineCount;
327 else
329 //none of those lines! what the hell happened here?
330 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
331 goto errorcleanup;
333 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
334 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
336 //chunk is finished
337 if (chunks)
338 chunks->chunks.Add(chunk);
339 else
340 delete chunk;
341 chunk = nullptr;
342 nAddLineCount = 0;
343 nContextLineCount = 0;
344 nRemoveLineCount = 0;
345 state = 0;
348 break;
349 default:
350 ASSERT(FALSE);
351 } // switch (state)
352 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
353 if (chunk)
355 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
356 goto errorcleanup;
358 if (chunks)
359 m_arFileDiffs.Add(chunks);
361 for (int i = 0; i < m_arFileDiffs.GetCount(); ++i)
363 if (filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath] > 1 && m_arFileDiffs.GetAt(i)->sFilePath != _T("NUL"))
365 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENAMENOTUNIQUE, (LPCTSTR)m_arFileDiffs.GetAt(i)->sFilePath);
366 FreeMemory();
367 return FALSE;
369 ++filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath];
370 if (m_arFileDiffs.GetAt(i)->sFilePath != m_arFileDiffs.GetAt(i)->sFilePath2)
372 if (filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath2] > 1 && m_arFileDiffs.GetAt(i)->sFilePath2 != _T("NUL"))
374 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENAMENOTUNIQUE, (LPCTSTR)m_arFileDiffs.GetAt(i)->sFilePath);
375 FreeMemory();
376 return FALSE;
378 ++filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath2];
382 return TRUE;
384 errorcleanup:
385 if (chunk)
386 delete chunk;
387 if (chunks)
389 for (int i = 0; i < chunks->chunks.GetCount(); ++i)
391 delete chunks->chunks.GetAt(i);
393 chunks->chunks.RemoveAll();
394 delete chunks;
396 FreeMemory();
397 return FALSE;
400 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)
402 CCrashReport::Instance().AddFile2(filename, nullptr, _T("unified diff file"), CR_AF_MAKE_FILE_COPY);
404 CFileTextLines PatchLines;
405 if (!PatchLines.Load(filename))
407 m_sErrorMessage = PatchLines.GetErrorString();
408 return FALSE;
410 FreeMemory();
412 //now we got all the lines of the patch file
413 //in our array - parsing can start...
414 return ParsePatchFile(PatchLines);
417 CString CPatch::GetFilename(int nIndex)
419 if (nIndex < 0)
420 return _T("");
421 if (nIndex < m_arFileDiffs.GetCount())
423 Chunks * c = m_arFileDiffs.GetAt(nIndex);
424 CString filepath = Strip(c->sFilePath);
425 return filepath;
427 return _T("");
430 CString CPatch::GetRevision(int nIndex)
432 if (nIndex < 0)
433 return 0;
434 if (nIndex < m_arFileDiffs.GetCount())
436 Chunks * c = m_arFileDiffs.GetAt(nIndex);
437 return c->sRevision;
439 return 0;
442 CString CPatch::GetFilename2(int nIndex)
444 if (nIndex < 0)
445 return _T("");
446 if (nIndex < m_arFileDiffs.GetCount())
448 Chunks * c = m_arFileDiffs.GetAt(nIndex);
449 CString filepath = Strip(c->sFilePath2);
450 return filepath;
452 return _T("");
455 CString CPatch::GetRevision2(int nIndex)
457 if (nIndex < 0)
458 return 0;
459 if (nIndex < m_arFileDiffs.GetCount())
461 Chunks * c = m_arFileDiffs.GetAt(nIndex);
462 return c->sRevision2;
464 return 0;
467 int CPatch::PatchFile(const int strip, int nIndex, const CString& sPatchPath, const CString& sSavePath, const CString& sBaseFile, const bool force)
469 m_nStrip = strip;
470 CString sPath = GetFullPath(sPatchPath, nIndex);
471 if (PathIsDirectory(sPath))
473 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);
474 return FALSE;
476 if (nIndex < 0)
478 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);
479 return FALSE;
482 if (!force && sPath == _T("NUL") && PathFileExists(GetFullPath(sPatchPath, nIndex, 1)))
483 return FALSE;
485 if (GetFullPath(sPatchPath, nIndex, 1) == _T("NUL") && !PathFileExists(sPath))
486 return 2;
488 CString sLine;
489 CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;
490 if (PathFileExists(sPatchFile))
492 CCrashReport::Instance().AddFile2(sPatchFile, nullptr, _T("File to patch"), CR_AF_MAKE_FILE_COPY);
494 CFileTextLines PatchLines;
495 CFileTextLines PatchLinesResult;
496 PatchLines.Load(sPatchFile);
497 PatchLinesResult = PatchLines; //.Copy(PatchLines);
498 PatchLines.CopySettings(&PatchLinesResult);
500 Chunks * chunks = m_arFileDiffs.GetAt(nIndex);
502 for (int i = 0; i < chunks->chunks.GetCount(); ++i)
504 Chunk * chunk = chunks->chunks.GetAt(i);
505 LONG lRemoveLine = chunk->lRemoveStart;
506 LONG lAddLine = chunk->lAddStart;
507 for (int j = 0; j < chunk->arLines.GetCount(); ++j)
509 CString sPatchLine = chunk->arLines.GetAt(j);
510 EOL ending = chunk->arEOLs[j];
511 if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))
513 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))
515 // convert the UTF-8 contents in CString sPatchLine into a CStringA
516 sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));
519 int nPatchState = (int)chunk->arLinesStates.GetAt(j);
520 switch (nPatchState)
522 case PATCHSTATE_REMOVED:
524 if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))
526 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
527 return FALSE;
529 if (lAddLine == 0)
530 lAddLine = 1;
531 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))
533 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
534 return FALSE;
536 if (lAddLine > PatchLines.GetCount())
538 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));
539 return FALSE;
541 PatchLines.RemoveAt(lAddLine-1);
543 break;
544 case PATCHSTATE_ADDED:
546 if (lAddLine == 0)
547 lAddLine = 1;
548 // check context after insertions in order to avoid double insertions
549 bool insertOk = !(lAddLine < PatchLines.GetCount());
550 int k = j;
551 for (; k < chunk->arLines.GetCount(); ++k)
553 if ((int)chunk->arLinesStates.GetAt(k) == PATCHSTATE_ADDED)
554 continue;
555 if (PatchLines.GetCount() >= lAddLine && chunk->arLines.GetAt(k).Compare(PatchLines.GetAt(lAddLine - 1)) == 0)
556 insertOk = true;
557 else
558 break;
561 if (insertOk)
563 PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);
564 ++lAddLine;
566 else
568 if (k >= chunk->arLines.GetCount())
569 k = j;
570 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)PatchLines.GetAt(lAddLine - 1), (LPCTSTR)chunk->arLines.GetAt(k));
571 return FALSE;
574 break;
575 case PATCHSTATE_CONTEXT:
577 if (lAddLine > PatchLines.GetCount())
579 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
580 return FALSE;
582 if (lAddLine == 0)
583 ++lAddLine;
584 if (lRemoveLine == 0)
585 ++lRemoveLine;
586 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&
587 (!HasExpandedKeyWords(sPatchLine)) &&
588 (lRemoveLine <= PatchLines.GetCount()) &&
589 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))
591 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))
592 ++lAddLine;
593 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))
594 lAddLine += 2;
595 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))
596 ++lRemoveLine;
597 else
599 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
600 return FALSE;
603 ++lAddLine;
604 ++lRemoveLine;
606 break;
607 default:
608 ASSERT(FALSE);
609 break;
610 } // switch (nPatchState)
611 } // for (j=0; j<chunk->arLines.GetCount(); j++)
612 } // for (int i=0; i<chunks->chunks.GetCount(); i++)
613 if (!sSavePath.IsEmpty())
615 PatchLines.Save(sSavePath, false);
617 return TRUE;
620 BOOL CPatch::HasExpandedKeyWords(const CString& line) const
622 if (line.Find(_T("$LastChangedDate"))>=0)
623 return TRUE;
624 if (line.Find(_T("$Date"))>=0)
625 return TRUE;
626 if (line.Find(_T("$LastChangedRevision"))>=0)
627 return TRUE;
628 if (line.Find(_T("$Rev"))>=0)
629 return TRUE;
630 if (line.Find(_T("$LastChangedBy"))>=0)
631 return TRUE;
632 if (line.Find(_T("$Author"))>=0)
633 return TRUE;
634 if (line.Find(_T("$HeadURL"))>=0)
635 return TRUE;
636 if (line.Find(_T("$URL"))>=0)
637 return TRUE;
638 if (line.Find(_T("$Id"))>=0)
639 return TRUE;
640 return FALSE;
643 CString CPatch::CheckPatchPath(const CString& path)
645 //first check if the path already matches
646 if (CountMatches(path) > (GetNumberOfFiles()/3))
647 return path;
648 //now go up the tree and try again
649 CString upperpath = path;
650 while (upperpath.ReverseFind('\\')>0)
652 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
653 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
654 return upperpath;
656 //still no match found. So try sub folders
657 bool isDir = false;
658 CString subpath;
659 CDirFileEnum filefinder(path);
660 while (filefinder.NextFile(subpath, &isDir))
662 if (!isDir)
663 continue;
664 if (GitAdminDir::IsAdminDirPath(subpath))
665 continue;
666 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
667 return subpath;
670 // if a patch file only contains newly added files
671 // we can't really find the correct path.
672 // But: we can compare paths strings without the filenames
673 // and check if at least those match
674 upperpath = path;
675 while (upperpath.ReverseFind('\\')>0)
677 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
678 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
679 return upperpath;
682 return path;
685 int CPatch::CountMatches(const CString& path)
687 int matches = 0;
688 for (int i=0; i<GetNumberOfFiles(); ++i)
690 CString temp = GetFilename(i);
691 temp.Replace('/', '\\');
692 if (PathIsRelative(temp))
693 temp = path + _T("\\")+ temp;
694 if (PathFileExists(temp))
695 ++matches;
697 return matches;
700 int CPatch::CountDirMatches(const CString& path)
702 int matches = 0;
703 for (int i=0; i<GetNumberOfFiles(); ++i)
705 CString temp = GetFilename(i);
706 temp.Replace('/', '\\');
707 if (PathIsRelative(temp))
708 temp = path + _T("\\")+ temp;
709 // remove the filename
710 temp = temp.Left(temp.ReverseFind('\\'));
711 if (PathFileExists(temp))
712 ++matches;
714 return matches;
717 BOOL CPatch::StripPrefixes(const CString& path)
719 int nSlashesMax = 0;
720 for (int i = 0; i < GetNumberOfFiles(); ++i)
722 CString filename = GetFilename(i);
723 filename.Replace('/','\\');
724 int nSlashes = filename.Replace('\\','/');
725 nSlashesMax = max(nSlashesMax,nSlashes);
728 for (int nStrip = 1; nStrip < nSlashesMax; ++nStrip)
730 m_nStrip = nStrip;
731 if ( CountMatches(path) > GetNumberOfFiles()/3 )
733 // Use current m_nStrip
734 return TRUE;
738 // Stripping doesn't help so reset it again
739 m_nStrip = 0;
740 return FALSE;
743 CString CPatch::Strip(const CString& filename) const
745 CString s = filename;
746 if ( m_nStrip>0 )
748 // Remove windows drive letter "c:"
749 if ( s.GetLength()>2 && s[1]==':')
751 s = s.Mid(2);
754 for (int nStrip = 1; nStrip <= m_nStrip; ++nStrip)
756 // "/home/ts/my-working-copy/dir/file.txt"
757 // "home/ts/my-working-copy/dir/file.txt"
758 // "ts/my-working-copy/dir/file.txt"
759 // "my-working-copy/dir/file.txt"
760 // "dir/file.txt"
761 s = s.Mid(s.FindOneOf(_T("/\\"))+1);
764 return s;
767 CString CPatch::GetFullPath(const CString& sPath, int nIndex, int fileno /* = 0*/)
769 CString temp;
770 if (fileno == 0)
771 temp = GetFilename(nIndex);
772 else
773 temp = GetFilename2(nIndex);
775 temp.Replace('/', '\\');
776 if(temp == _T("NUL"))
777 return temp;
779 if (PathIsRelative(temp))
781 if (sPath.Right(1).Compare(_T("\\")) != 0)
782 temp = sPath + _T("\\") + temp;
783 else
784 temp = sPath + temp;
787 return temp;
790 CString CPatch::RemoveUnicodeBOM(const CString& str) const
792 if (str.IsEmpty())
793 return str;
794 if (str[0] == 0xFEFF)
795 return str.Mid(1);
796 return str;