Use wcsncmp instead of Find for StartsWith checks
[TortoiseGit.git] / src / TortoiseMerge / Patch.cpp
blobfad27d3ea47879b5ad3672fad075475cf339b15f
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"
29 #ifdef _DEBUG
30 #define new DEBUG_NEW
31 #undef THIS_FILE
32 static char THIS_FILE[] = __FILE__;
33 #endif
35 CPatch::CPatch(void)
36 : m_nStrip(0)
37 , m_UnicodeType(CFileTextLines::AUTOTYPE)
41 CPatch::~CPatch(void)
43 FreeMemory();
46 void CPatch::FreeMemory()
48 for (int i=0; i<m_arFileDiffs.GetCount(); ++i)
50 Chunks * chunks = m_arFileDiffs.GetAt(i);
51 for (int j=0; j<chunks->chunks.GetCount(); ++j)
53 delete chunks->chunks.GetAt(j);
55 chunks->chunks.RemoveAll();
56 delete chunks;
58 m_arFileDiffs.RemoveAll();
61 BOOL CPatch::ParsePatchFile(CFileTextLines &PatchLines)
63 CString sLine;
64 EOL ending = EOL_NOENDING;
66 int state = 0;
67 int nIndex = 0;
68 Chunks* chunks = nullptr;
69 Chunk* chunk = nullptr;
70 int nAddLineCount = 0;
71 int nRemoveLineCount = 0;
72 int nContextLineCount = 0;
73 std::map<CString, int> filenamesToPatch;
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
86 if (wcsncmp(sLine, L"diff ", 5) == 0)
88 if (chunks)
90 //this is a new file diff, so add the last one to
91 //our array.
92 if (chunks->chunks.GetCount() > 0)
93 m_arFileDiffs.Add(chunks);
94 else
95 delete chunks;
97 chunks = new Chunks();
98 state = 1;
99 break;
102 // fallthrough!
103 case 1:
105 //index
106 if (wcsncmp(sLine, L"index", 5) == 0)
108 int dotstart=sLine.Find(_T(".."));
109 if(dotstart>=0 && chunks)
111 chunks->sRevision = sLine.Mid(dotstart-7,7);
112 chunks->sRevision2 = sLine.Mid(dotstart+2,7);
114 break;
117 //---
118 if (wcsncmp(sLine, L"--- ", 4) == 0)
120 if (state == 0)
122 if (chunks)
124 //this is a new file diff, so add the last one to
125 //our array.
126 if (chunks->chunks.GetCount() > 0)
127 m_arFileDiffs.Add(chunks);
128 else
129 delete chunks;
131 chunks = new Chunks();
134 sLine = sLine.Mid(3); //remove the "---"
135 sLine =sLine.Trim();
136 //at the end of the filepath there's a revision number...
137 int bracket = sLine.ReverseFind('(');
138 if (bracket < 0)
139 // some patch files can have another '(' char, especially ones created in Chinese OS
140 bracket = sLine.ReverseFind(0xff08);
142 if (bracket < 0)
144 if (chunks->sFilePath.IsEmpty())
145 chunks->sFilePath = sLine.Trim();
147 else
148 chunks->sFilePath = sLine.Left(bracket-1).Trim();
150 if (chunks->sFilePath.Find('\t')>=0)
151 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
152 if (wcsncmp(chunks->sFilePath, L"\"", 1) == 0 && chunks->sFilePath.ReverseFind(_T('"')) == chunks->sFilePath.GetLength() - 1)
153 chunks->sFilePath=chunks->sFilePath.Mid(1, chunks->sFilePath.GetLength() - 2);
154 if (wcsncmp(chunks->sFilePath, L"a/", 2) == 0)
155 chunks->sFilePath=chunks->sFilePath.Mid(2);
157 if (wcsncmp(chunks->sFilePath, L"b/", 2) == 0)
158 chunks->sFilePath=chunks->sFilePath.Mid(2);
161 chunks->sFilePath.Replace(_T('/'),_T('\\'));
163 if (chunks->sFilePath == _T("\\dev\\null") || chunks->sFilePath == _T("/dev/null"))
164 chunks->sFilePath = _T("NUL");
166 state = 3;
168 if (state == 0)
170 if (wcsncmp(sLine,L"@@", 2) == 0)
172 if (chunks)
174 nIndex--;
175 state = 4;
177 else
178 break;
182 break;
184 case 3:
186 // +++
187 if (wcsncmp(sLine, L"+++", 3) != 0)
189 // no starting "+++" found
190 m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);
191 goto errorcleanup;
193 sLine = sLine.Mid(3); //remove the "---"
194 sLine =sLine.Trim();
196 //at the end of the filepath there's a revision number...
197 int bracket = sLine.ReverseFind('(');
198 if (bracket < 0)
199 // some patch files can have another '(' char, especially ones created in Chinese OS
200 bracket = sLine.ReverseFind(0xff08);
202 if (bracket < 0)
203 chunks->sFilePath2 = sLine.Trim();
204 else
205 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
206 if (chunks->sFilePath2.Find('\t')>=0)
207 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
208 if (wcsncmp(chunks->sFilePath2, L"\"", 1) == 0 && chunks->sFilePath2.ReverseFind(_T('"')) == chunks->sFilePath2.GetLength() - 1)
209 chunks->sFilePath2=chunks->sFilePath2.Mid(1, chunks->sFilePath2.GetLength() - 2);
210 if (wcsncmp(chunks->sFilePath2, L"a/", 2) == 0)
211 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
213 if (wcsncmp(chunks->sFilePath2, L"b/", 2) == 0)
214 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
216 chunks->sFilePath2.Replace(_T('/'),_T('\\'));
218 if (chunks->sFilePath2 == _T("\\dev\\null") || chunks->sFilePath2 == _T("/dev/null"))
219 chunks->sFilePath2 = _T("NUL");
221 ++state;
223 break;
225 case 4:
227 //start of a new chunk
228 if (wcsncmp(sLine, L"@@", 2) != 0)
230 //chunk doesn't start with "@@"
231 //so there's garbage in between two file diffs
232 state = 0;
233 if (chunk)
235 delete chunk;
236 chunk = nullptr;
237 if (chunks)
239 for (int i = 0; i < chunks->chunks.GetCount(); ++i)
241 delete chunks->chunks.GetAt(i);
243 chunks->chunks.RemoveAll();
244 delete chunks;
245 chunks = nullptr;
248 break; //skip the garbage
251 //@@ -xxx,xxx +xxx,xxx @@
252 sLine = sLine.Mid(2);
253 sLine = sLine.Trim();
254 chunk = new Chunk();
255 CString sRemove = sLine.Left(sLine.Find(' '));
256 CString sAdd = sLine.Mid(sLine.Find(' '));
257 chunk->lRemoveStart = (-_ttol(sRemove));
258 if (sRemove.Find(',')>=0)
260 sRemove = sRemove.Mid(sRemove.Find(',')+1);
261 chunk->lRemoveLength = _ttol(sRemove);
263 else
265 chunk->lRemoveStart = 0;
266 chunk->lRemoveLength = (-_ttol(sRemove));
268 chunk->lAddStart = _ttol(sAdd);
269 if (sAdd.Find(',')>=0)
271 sAdd = sAdd.Mid(sAdd.Find(',')+1);
272 chunk->lAddLength = _ttol(sAdd);
274 else
276 chunk->lAddStart = 1;
277 chunk->lAddLength = _ttol(sAdd);
279 ++state;
281 break;
283 case 5: //[ |+|-] <sourceline>
285 //this line is either a context line (with a ' ' in front)
286 //a line added (with a '+' in front)
287 //or a removed line (with a '-' in front)
288 TCHAR type;
289 if (sLine.IsEmpty())
290 type = ' ';
291 else
292 type = sLine.GetAt(0);
293 if (type == ' ')
295 //it's a context line - we don't use them here right now
296 //but maybe in the future the patch algorithm can be
297 //extended to use those in case the file to patch has
298 //already changed and no base file is around...
299 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
300 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
301 chunk->arEOLs.push_back(ending);
302 ++nContextLineCount;
304 else if (type == '\\')
306 //it's a context line (sort of):
307 //warnings start with a '\' char (e.g. "\ No newline at end of file")
308 //so just ignore this...
310 else if (type == '-')
312 //a removed line
313 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
314 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
315 chunk->arEOLs.push_back(ending);
316 ++nRemoveLineCount;
318 else if (type == '+')
320 //an added line
321 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
322 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
323 chunk->arEOLs.push_back(ending);
324 ++nAddLineCount;
326 else
328 //none of those lines! what the hell happened here?
329 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
330 goto errorcleanup;
332 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
333 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
335 //chunk is finished
336 if (chunks)
337 chunks->chunks.Add(chunk);
338 else
339 delete chunk;
340 chunk = nullptr;
341 nAddLineCount = 0;
342 nContextLineCount = 0;
343 nRemoveLineCount = 0;
344 state = 0;
347 break;
348 default:
349 ASSERT(FALSE);
350 } // switch (state)
351 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
352 if (chunk)
354 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
355 goto errorcleanup;
357 if (chunks)
358 m_arFileDiffs.Add(chunks);
360 for (int i = 0; i < m_arFileDiffs.GetCount(); ++i)
362 if (filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath] > 1 && m_arFileDiffs.GetAt(i)->sFilePath != _T("NUL"))
364 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENAMENOTUNIQUE, (LPCTSTR)m_arFileDiffs.GetAt(i)->sFilePath);
365 FreeMemory();
366 return FALSE;
368 ++filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath];
369 if (m_arFileDiffs.GetAt(i)->sFilePath != m_arFileDiffs.GetAt(i)->sFilePath2)
371 if (filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath2] > 1 && m_arFileDiffs.GetAt(i)->sFilePath2 != _T("NUL"))
373 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENAMENOTUNIQUE, (LPCTSTR)m_arFileDiffs.GetAt(i)->sFilePath);
374 FreeMemory();
375 return FALSE;
377 ++filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath2];
381 return TRUE;
383 errorcleanup:
384 if (chunk)
385 delete chunk;
386 if (chunks)
388 for (int i = 0; i < chunks->chunks.GetCount(); ++i)
390 delete chunks->chunks.GetAt(i);
392 chunks->chunks.RemoveAll();
393 delete chunks;
395 FreeMemory();
396 return FALSE;
399 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)
401 CCrashReport::Instance().AddFile2(filename, nullptr, _T("unified diff file"), CR_AF_MAKE_FILE_COPY);
403 CFileTextLines PatchLines;
404 if (!PatchLines.Load(filename))
406 m_sErrorMessage = PatchLines.GetErrorString();
407 return FALSE;
409 FreeMemory();
411 //now we got all the lines of the patch file
412 //in our array - parsing can start...
413 return ParsePatchFile(PatchLines);
416 CString CPatch::GetFilename(int nIndex)
418 if (nIndex < 0)
419 return _T("");
420 if (nIndex < m_arFileDiffs.GetCount())
422 Chunks * c = m_arFileDiffs.GetAt(nIndex);
423 CString filepath = Strip(c->sFilePath);
424 return filepath;
426 return _T("");
429 CString CPatch::GetRevision(int nIndex)
431 if (nIndex < 0)
432 return 0;
433 if (nIndex < m_arFileDiffs.GetCount())
435 Chunks * c = m_arFileDiffs.GetAt(nIndex);
436 return c->sRevision;
438 return 0;
441 CString CPatch::GetFilename2(int nIndex)
443 if (nIndex < 0)
444 return _T("");
445 if (nIndex < m_arFileDiffs.GetCount())
447 Chunks * c = m_arFileDiffs.GetAt(nIndex);
448 CString filepath = Strip(c->sFilePath2);
449 return filepath;
451 return _T("");
454 CString CPatch::GetRevision2(int nIndex)
456 if (nIndex < 0)
457 return 0;
458 if (nIndex < m_arFileDiffs.GetCount())
460 Chunks * c = m_arFileDiffs.GetAt(nIndex);
461 return c->sRevision2;
463 return 0;
466 int CPatch::PatchFile(const int strip, int nIndex, const CString& sPatchPath, const CString& sSavePath, const CString& sBaseFile, const bool force)
468 m_nStrip = strip;
469 CString sPath = GetFullPath(sPatchPath, nIndex);
470 if (PathIsDirectory(sPath))
472 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);
473 return FALSE;
475 if (nIndex < 0)
477 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);
478 return FALSE;
481 if (!force && sPath == _T("NUL") && PathFileExists(GetFullPath(sPatchPath, nIndex, 1)))
482 return FALSE;
484 if (GetFullPath(sPatchPath, nIndex, 1) == _T("NUL") && !PathFileExists(sPath))
485 return 2;
487 CString sLine;
488 CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;
489 if (PathFileExists(sPatchFile))
491 CCrashReport::Instance().AddFile2(sPatchFile, nullptr, _T("File to patch"), CR_AF_MAKE_FILE_COPY);
493 CFileTextLines PatchLines;
494 CFileTextLines PatchLinesResult;
495 PatchLines.Load(sPatchFile);
496 PatchLinesResult = PatchLines; //.Copy(PatchLines);
497 PatchLines.CopySettings(&PatchLinesResult);
499 Chunks * chunks = m_arFileDiffs.GetAt(nIndex);
501 for (int i = 0; i < chunks->chunks.GetCount(); ++i)
503 Chunk * chunk = chunks->chunks.GetAt(i);
504 LONG lRemoveLine = chunk->lRemoveStart;
505 LONG lAddLine = chunk->lAddStart;
506 for (int j = 0; j < chunk->arLines.GetCount(); ++j)
508 CString sPatchLine = chunk->arLines.GetAt(j);
509 EOL ending = chunk->arEOLs[j];
510 if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))
512 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))
514 // convert the UTF-8 contents in CString sPatchLine into a CStringA
515 sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));
518 int nPatchState = (int)chunk->arLinesStates.GetAt(j);
519 switch (nPatchState)
521 case PATCHSTATE_REMOVED:
523 if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))
525 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
526 return FALSE;
528 if (lAddLine == 0)
529 lAddLine = 1;
530 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))
532 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
533 return FALSE;
535 if (lAddLine > PatchLines.GetCount())
537 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));
538 return FALSE;
540 PatchLines.RemoveAt(lAddLine-1);
542 break;
543 case PATCHSTATE_ADDED:
545 if (lAddLine == 0)
546 lAddLine = 1;
547 // check context after insertions in order to avoid double insertions
548 bool insertOk = !(lAddLine < PatchLines.GetCount());
549 int k = j;
550 for (; k < chunk->arLines.GetCount(); ++k)
552 if ((int)chunk->arLinesStates.GetAt(k) == PATCHSTATE_ADDED)
553 continue;
554 if (PatchLines.GetCount() >= lAddLine && chunk->arLines.GetAt(k).Compare(PatchLines.GetAt(lAddLine - 1)) == 0)
555 insertOk = true;
556 else
557 break;
560 if (insertOk)
562 PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);
563 ++lAddLine;
565 else
567 if (k >= chunk->arLines.GetCount())
568 k = j;
569 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)PatchLines.GetAt(lAddLine - 1), (LPCTSTR)chunk->arLines.GetAt(k));
570 return FALSE;
573 break;
574 case PATCHSTATE_CONTEXT:
576 if (lAddLine > PatchLines.GetCount())
578 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
579 return FALSE;
581 if (lAddLine == 0)
582 ++lAddLine;
583 if (lRemoveLine == 0)
584 ++lRemoveLine;
585 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&
586 (!HasExpandedKeyWords(sPatchLine)) &&
587 (lRemoveLine <= PatchLines.GetCount()) &&
588 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))
590 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))
591 ++lAddLine;
592 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))
593 lAddLine += 2;
594 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))
595 ++lRemoveLine;
596 else
598 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
599 return FALSE;
602 ++lAddLine;
603 ++lRemoveLine;
605 break;
606 default:
607 ASSERT(FALSE);
608 break;
609 } // switch (nPatchState)
610 } // for (j=0; j<chunk->arLines.GetCount(); j++)
611 } // for (int i=0; i<chunks->chunks.GetCount(); i++)
612 if (!sSavePath.IsEmpty())
614 PatchLines.Save(sSavePath, false);
616 return TRUE;
619 BOOL CPatch::HasExpandedKeyWords(const CString& line) const
621 if (line.Find(_T("$LastChangedDate"))>=0)
622 return TRUE;
623 if (line.Find(_T("$Date"))>=0)
624 return TRUE;
625 if (line.Find(_T("$LastChangedRevision"))>=0)
626 return TRUE;
627 if (line.Find(_T("$Rev"))>=0)
628 return TRUE;
629 if (line.Find(_T("$LastChangedBy"))>=0)
630 return TRUE;
631 if (line.Find(_T("$Author"))>=0)
632 return TRUE;
633 if (line.Find(_T("$HeadURL"))>=0)
634 return TRUE;
635 if (line.Find(_T("$URL"))>=0)
636 return TRUE;
637 if (line.Find(_T("$Id"))>=0)
638 return TRUE;
639 return FALSE;
642 CString CPatch::CheckPatchPath(const CString& path)
644 //first check if the path already matches
645 if (CountMatches(path) > (GetNumberOfFiles()/3))
646 return path;
647 //now go up the tree and try again
648 CString upperpath = path;
649 while (upperpath.ReverseFind('\\')>0)
651 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
652 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
653 return upperpath;
655 //still no match found. So try sub folders
656 bool isDir = false;
657 CString subpath;
658 CDirFileEnum filefinder(path);
659 while (filefinder.NextFile(subpath, &isDir))
661 if (!isDir)
662 continue;
663 if (GitAdminDir::IsAdminDirPath(subpath))
664 continue;
665 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
666 return subpath;
669 // if a patch file only contains newly added files
670 // we can't really find the correct path.
671 // But: we can compare paths strings without the filenames
672 // and check if at least those match
673 upperpath = path;
674 while (upperpath.ReverseFind('\\')>0)
676 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
677 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
678 return upperpath;
681 return path;
684 int CPatch::CountMatches(const CString& path)
686 int matches = 0;
687 for (int i=0; i<GetNumberOfFiles(); ++i)
689 CString temp = GetFilename(i);
690 temp.Replace('/', '\\');
691 if (PathIsRelative(temp))
692 temp = path + _T("\\")+ temp;
693 if (PathFileExists(temp))
694 ++matches;
696 return matches;
699 int CPatch::CountDirMatches(const CString& path)
701 int matches = 0;
702 for (int i=0; i<GetNumberOfFiles(); ++i)
704 CString temp = GetFilename(i);
705 temp.Replace('/', '\\');
706 if (PathIsRelative(temp))
707 temp = path + _T("\\")+ temp;
708 // remove the filename
709 temp = temp.Left(temp.ReverseFind('\\'));
710 if (PathFileExists(temp))
711 ++matches;
713 return matches;
716 BOOL CPatch::StripPrefixes(const CString& path)
718 int nSlashesMax = 0;
719 for (int i = 0; i < GetNumberOfFiles(); ++i)
721 CString filename = GetFilename(i);
722 filename.Replace('/','\\');
723 int nSlashes = filename.Replace('\\','/');
724 nSlashesMax = max(nSlashesMax,nSlashes);
727 for (int nStrip = 1; nStrip < nSlashesMax; ++nStrip)
729 m_nStrip = nStrip;
730 if ( CountMatches(path) > GetNumberOfFiles()/3 )
732 // Use current m_nStrip
733 return TRUE;
737 // Stripping doesn't help so reset it again
738 m_nStrip = 0;
739 return FALSE;
742 CString CPatch::Strip(const CString& filename) const
744 CString s = filename;
745 if ( m_nStrip>0 )
747 // Remove windows drive letter "c:"
748 if ( s.GetLength()>2 && s[1]==':')
750 s = s.Mid(2);
753 for (int nStrip = 1; nStrip <= m_nStrip; ++nStrip)
755 // "/home/ts/my-working-copy/dir/file.txt"
756 // "home/ts/my-working-copy/dir/file.txt"
757 // "ts/my-working-copy/dir/file.txt"
758 // "my-working-copy/dir/file.txt"
759 // "dir/file.txt"
760 s = s.Mid(s.FindOneOf(_T("/\\"))+1);
763 return s;
766 CString CPatch::GetFullPath(const CString& sPath, int nIndex, int fileno /* = 0*/)
768 CString temp;
769 if (fileno == 0)
770 temp = GetFilename(nIndex);
771 else
772 temp = GetFilename2(nIndex);
774 temp.Replace('/', '\\');
775 if(temp == _T("NUL"))
776 return temp;
778 if (PathIsRelative(temp))
780 if (sPath.Right(1).Compare(_T("\\")) != 0)
781 temp = sPath + _T("\\") + temp;
782 else
783 temp = sPath + temp;
786 return temp;
789 CString CPatch::RemoveUnicodeBOM(const CString& str) const
791 if (str.IsEmpty())
792 return str;
793 if (str[0] == 0xFEFF)
794 return str.Mid(1);
795 return str;