If CTGitPathList::ParserFromLsFile() fails, write the output to temp file for inspection
[TortoiseGit.git] / src / TortoiseMerge / Patch.cpp
blob246c1d14ea2cac5517279fee642ac4012d0b2d46
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2009-2013 - TortoiseGit
4 // Copyright (C) 2012-2013 - 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 "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 = NULL;
69 Chunk * chunk = NULL;
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 (sLine.Find(_T("diff ")) == 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( sLine.Find(_T("index"))==0 )
108 int dotstart=sLine.Find(_T(".."));
109 if(dotstart>=0)
111 chunks->sRevision = sLine.Mid(dotstart-7,7);
112 chunks->sRevision2 = sLine.Mid(dotstart+2,7);
114 break;
117 //---
118 if (sLine.Find(_T("--- ")) == 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)
152 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
154 if (chunks->sFilePath.Find(_T('"')) == 0 && chunks->sFilePath.ReverseFind(_T('"')) == chunks->sFilePath.GetLength() - 1)
155 chunks->sFilePath=chunks->sFilePath.Mid(1, chunks->sFilePath.GetLength() - 2);
156 if( chunks->sFilePath.Find(_T("a/")) == 0 )
157 chunks->sFilePath=chunks->sFilePath.Mid(2);
159 if( chunks->sFilePath.Find(_T("b/")) == 0 )
160 chunks->sFilePath=chunks->sFilePath.Mid(2);
163 chunks->sFilePath.Replace(_T('/'),_T('\\'));
165 if (chunks->sFilePath == _T("\\dev\\null") || chunks->sFilePath == _T("/dev/null"))
166 chunks->sFilePath = _T("NUL");
168 state = 3;
170 if (state == 0)
172 if (sLine.Find(_T("@@")) == 0)
174 if (chunks != NULL)
176 nIndex--;
177 state = 4;
179 else
180 break;
184 break;
186 case 3:
188 // +++
189 if (sLine.Left(3).Compare(_T("+++")) != 0)
191 // no starting "+++" found
192 m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);
193 goto errorcleanup;
195 sLine = sLine.Mid(3); //remove the "---"
196 sLine =sLine.Trim();
198 //at the end of the filepath there's a revision number...
199 int bracket = sLine.ReverseFind('(');
200 if (bracket < 0)
201 // some patch files can have another '(' char, especially ones created in Chinese OS
202 bracket = sLine.ReverseFind(0xff08);
204 if (bracket < 0)
205 chunks->sFilePath2 = sLine.Trim();
206 else
207 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
208 if (chunks->sFilePath2.Find('\t')>=0)
210 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
212 if (chunks->sFilePath2.Find(_T('"')) == 0 && chunks->sFilePath2.ReverseFind(_T('"')) == chunks->sFilePath2.GetLength() - 1)
213 chunks->sFilePath2=chunks->sFilePath2.Mid(1, chunks->sFilePath2.GetLength() - 2);
214 if( chunks->sFilePath2.Find(_T("a/")) == 0 )
215 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
217 if( chunks->sFilePath2.Find(_T("b/")) == 0 )
218 chunks->sFilePath2=chunks->sFilePath2.Mid(2);
220 chunks->sFilePath2.Replace(_T('/'),_T('\\'));
222 if (chunks->sFilePath2 == _T("\\dev\\null") || chunks->sFilePath2 == _T("/dev/null"))
223 chunks->sFilePath2 = _T("NUL");
225 ++state;
227 break;
229 case 4:
231 //start of a new chunk
232 if (sLine.Left(2).Compare(_T("@@")) != 0)
234 //chunk doesn't start with "@@"
235 //so there's garbage in between two file diffs
236 state = 0;
237 if (chunk)
239 delete chunk;
240 chunk = nullptr;
241 if (chunks)
243 for (int i = 0; i < chunks->chunks.GetCount(); ++i)
245 delete chunks->chunks.GetAt(i);
247 chunks->chunks.RemoveAll();
248 delete chunks;
249 chunks = nullptr;
252 break; //skip the garbage
255 //@@ -xxx,xxx +xxx,xxx @@
256 sLine = sLine.Mid(2);
257 sLine = sLine.Trim();
258 chunk = new Chunk();
259 CString sRemove = sLine.Left(sLine.Find(' '));
260 CString sAdd = sLine.Mid(sLine.Find(' '));
261 chunk->lRemoveStart = (-_ttol(sRemove));
262 if (sRemove.Find(',')>=0)
264 sRemove = sRemove.Mid(sRemove.Find(',')+1);
265 chunk->lRemoveLength = _ttol(sRemove);
267 else
269 chunk->lRemoveStart = 0;
270 chunk->lRemoveLength = (-_ttol(sRemove));
272 chunk->lAddStart = _ttol(sAdd);
273 if (sAdd.Find(',')>=0)
275 sAdd = sAdd.Mid(sAdd.Find(',')+1);
276 chunk->lAddLength = _ttol(sAdd);
278 else
280 chunk->lAddStart = 1;
281 chunk->lAddLength = _ttol(sAdd);
283 ++state;
285 break;
287 case 5: //[ |+|-] <sourceline>
289 //this line is either a context line (with a ' ' in front)
290 //a line added (with a '+' in front)
291 //or a removed line (with a '-' in front)
292 TCHAR type;
293 if (sLine.IsEmpty())
294 type = ' ';
295 else
296 type = sLine.GetAt(0);
297 if (type == ' ')
299 //it's a context line - we don't use them here right now
300 //but maybe in the future the patch algorithm can be
301 //extended to use those in case the file to patch has
302 //already changed and no base file is around...
303 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
304 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
305 chunk->arEOLs.push_back(ending);
306 ++nContextLineCount;
308 else if (type == '\\')
310 //it's a context line (sort of):
311 //warnings start with a '\' char (e.g. "\ No newline at end of file")
312 //so just ignore this...
314 else if (type == '-')
316 //a removed line
317 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
318 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
319 chunk->arEOLs.push_back(ending);
320 ++nRemoveLineCount;
322 else if (type == '+')
324 //an added line
325 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
326 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
327 chunk->arEOLs.push_back(ending);
328 ++nAddLineCount;
330 else
332 //none of those lines! what the hell happened here?
333 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
334 goto errorcleanup;
336 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
337 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
339 //chunk is finished
340 if (chunks)
341 chunks->chunks.Add(chunk);
342 else
343 delete chunk;
344 chunk = NULL;
345 nAddLineCount = 0;
346 nContextLineCount = 0;
347 nRemoveLineCount = 0;
348 state = 0;
351 break;
352 default:
353 ASSERT(FALSE);
354 } // switch (state)
355 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
356 if (chunk)
358 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
359 goto errorcleanup;
361 if (chunks)
362 m_arFileDiffs.Add(chunks);
364 for (int i = 0; i < m_arFileDiffs.GetCount(); ++i)
366 if (filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath] > 1 && m_arFileDiffs.GetAt(i)->sFilePath != _T("NUL"))
368 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENAMENOTUNIQUE, m_arFileDiffs.GetAt(i)->sFilePath);
369 FreeMemory();
370 return FALSE;
372 ++filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath];
373 if (m_arFileDiffs.GetAt(i)->sFilePath != m_arFileDiffs.GetAt(i)->sFilePath2)
375 if (filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath2] > 1 && m_arFileDiffs.GetAt(i)->sFilePath2 != _T("NUL"))
377 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENAMENOTUNIQUE, m_arFileDiffs.GetAt(i)->sFilePath);
378 FreeMemory();
379 return FALSE;
381 ++filenamesToPatch[m_arFileDiffs.GetAt(i)->sFilePath2];
385 return TRUE;
387 errorcleanup:
388 if (chunk)
389 delete chunk;
390 if (chunks)
392 for (int i = 0; i < chunks->chunks.GetCount(); ++i)
394 delete chunks->chunks.GetAt(i);
396 chunks->chunks.RemoveAll();
397 delete chunks;
399 FreeMemory();
400 return FALSE;
403 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)
405 CCrashReport::Instance().AddFile2(filename, NULL, _T("unified diff file"), CR_AF_MAKE_FILE_COPY);
407 CFileTextLines PatchLines;
408 if (!PatchLines.Load(filename))
410 m_sErrorMessage = PatchLines.GetErrorString();
411 return FALSE;
413 FreeMemory();
415 //now we got all the lines of the patch file
416 //in our array - parsing can start...
417 return ParsePatchFile(PatchLines);
420 CString CPatch::GetFilename(int nIndex)
422 if (nIndex < 0)
423 return _T("");
424 if (nIndex < m_arFileDiffs.GetCount())
426 Chunks * c = m_arFileDiffs.GetAt(nIndex);
427 CString filepath = Strip(c->sFilePath);
428 return filepath;
430 return _T("");
433 CString CPatch::GetRevision(int nIndex)
435 if (nIndex < 0)
436 return 0;
437 if (nIndex < m_arFileDiffs.GetCount())
439 Chunks * c = m_arFileDiffs.GetAt(nIndex);
440 return c->sRevision;
442 return 0;
445 CString CPatch::GetFilename2(int nIndex)
447 if (nIndex < 0)
448 return _T("");
449 if (nIndex < m_arFileDiffs.GetCount())
451 Chunks * c = m_arFileDiffs.GetAt(nIndex);
452 CString filepath = Strip(c->sFilePath2);
453 return filepath;
455 return _T("");
458 CString CPatch::GetRevision2(int nIndex)
460 if (nIndex < 0)
461 return 0;
462 if (nIndex < m_arFileDiffs.GetCount())
464 Chunks * c = m_arFileDiffs.GetAt(nIndex);
465 return c->sRevision2;
467 return 0;
470 int CPatch::PatchFile(const int strip, int nIndex, const CString& sPatchPath, const CString& sSavePath, const CString& sBaseFile, const bool force)
472 m_nStrip = strip;
473 CString sPath = GetFullPath(sPatchPath, nIndex);
474 if (PathIsDirectory(sPath))
476 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);
477 return FALSE;
479 if (nIndex < 0)
481 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);
482 return FALSE;
485 if (!force && sPath == _T("NUL") && PathFileExists(GetFullPath(sPatchPath, nIndex, 1)))
486 return FALSE;
488 if (GetFullPath(sPatchPath, nIndex, 1) == _T("NUL") && !PathFileExists(sPath))
489 return 2;
491 CString sLine;
492 CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;
493 if (PathFileExists(sPatchFile))
495 CCrashReport::Instance().AddFile2(sPatchFile, NULL, _T("File to patch"), CR_AF_MAKE_FILE_COPY);
497 CFileTextLines PatchLines;
498 CFileTextLines PatchLinesResult;
499 PatchLines.Load(sPatchFile);
500 PatchLinesResult = PatchLines; //.Copy(PatchLines);
501 PatchLines.CopySettings(&PatchLinesResult);
503 Chunks * chunks = m_arFileDiffs.GetAt(nIndex);
505 for (int i = 0; i < chunks->chunks.GetCount(); ++i)
507 Chunk * chunk = chunks->chunks.GetAt(i);
508 LONG lRemoveLine = chunk->lRemoveStart;
509 LONG lAddLine = chunk->lAddStart;
510 for (int j = 0; j < chunk->arLines.GetCount(); ++j)
512 CString sPatchLine = chunk->arLines.GetAt(j);
513 EOL ending = chunk->arEOLs[j];
514 if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))
516 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))
518 // convert the UTF-8 contents in CString sPatchLine into a CStringA
519 sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));
522 int nPatchState = (int)chunk->arLinesStates.GetAt(j);
523 switch (nPatchState)
525 case PATCHSTATE_REMOVED:
527 if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))
529 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
530 return FALSE;
532 if (lAddLine == 0)
533 lAddLine = 1;
534 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))
536 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
537 return FALSE;
539 if (lAddLine > PatchLines.GetCount())
541 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));
542 return FALSE;
544 PatchLines.RemoveAt(lAddLine-1);
546 break;
547 case PATCHSTATE_ADDED:
549 if (lAddLine == 0)
550 lAddLine = 1;
551 // check context after insertions in order to avoid double insertions
552 bool insertOk = !(lAddLine < PatchLines.GetCount());
553 int k = j;
554 for (; k < chunk->arLines.GetCount(); ++k)
556 if ((int)chunk->arLinesStates.GetAt(k) == PATCHSTATE_ADDED)
557 continue;
558 if (chunk->arLines.GetAt(k).Compare(PatchLines.GetAt(lAddLine - 1)) == 0)
559 insertOk = true;
560 else
561 break;
564 if (insertOk)
566 PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);
567 ++lAddLine;
569 else
571 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)PatchLines.GetAt(lAddLine - 1), chunk->arLines.GetAt(k));
572 return FALSE;
575 break;
576 case PATCHSTATE_CONTEXT:
578 if (lAddLine > PatchLines.GetCount())
580 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
581 return FALSE;
583 if (lAddLine == 0)
584 ++lAddLine;
585 if (lRemoveLine == 0)
586 ++lRemoveLine;
587 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&
588 (!HasExpandedKeyWords(sPatchLine)) &&
589 (lRemoveLine <= PatchLines.GetCount()) &&
590 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))
592 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))
593 ++lAddLine;
594 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))
595 lAddLine += 2;
596 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))
597 ++lRemoveLine;
598 else
600 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
601 return FALSE;
604 ++lAddLine;
605 ++lRemoveLine;
607 break;
608 default:
609 ASSERT(FALSE);
610 break;
611 } // switch (nPatchState)
612 } // for (j=0; j<chunk->arLines.GetCount(); j++)
613 } // for (int i=0; i<chunks->chunks.GetCount(); i++)
614 if (!sSavePath.IsEmpty())
616 PatchLines.Save(sSavePath, false);
618 return TRUE;
621 BOOL CPatch::HasExpandedKeyWords(const CString& line)
623 if (line.Find(_T("$LastChangedDate"))>=0)
624 return TRUE;
625 if (line.Find(_T("$Date"))>=0)
626 return TRUE;
627 if (line.Find(_T("$LastChangedRevision"))>=0)
628 return TRUE;
629 if (line.Find(_T("$Rev"))>=0)
630 return TRUE;
631 if (line.Find(_T("$LastChangedBy"))>=0)
632 return TRUE;
633 if (line.Find(_T("$Author"))>=0)
634 return TRUE;
635 if (line.Find(_T("$HeadURL"))>=0)
636 return TRUE;
637 if (line.Find(_T("$URL"))>=0)
638 return TRUE;
639 if (line.Find(_T("$Id"))>=0)
640 return TRUE;
641 return FALSE;
644 CString CPatch::CheckPatchPath(const CString& path)
646 //first check if the path already matches
647 if (CountMatches(path) > (GetNumberOfFiles()/3))
648 return path;
649 //now go up the tree and try again
650 CString upperpath = path;
651 while (upperpath.ReverseFind('\\')>0)
653 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
654 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
655 return upperpath;
657 //still no match found. So try sub folders
658 bool isDir = false;
659 CString subpath;
660 CDirFileEnum filefinder(path);
661 while (filefinder.NextFile(subpath, &isDir))
663 if (!isDir)
664 continue;
665 if (g_GitAdminDir.IsAdminDirPath(subpath))
666 continue;
667 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
668 return subpath;
671 // if a patch file only contains newly added files
672 // we can't really find the correct path.
673 // But: we can compare paths strings without the filenames
674 // and check if at least those match
675 upperpath = path;
676 while (upperpath.ReverseFind('\\')>0)
678 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
679 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
680 return upperpath;
683 return path;
686 int CPatch::CountMatches(const CString& path)
688 int matches = 0;
689 for (int i=0; i<GetNumberOfFiles(); ++i)
691 CString temp = GetFilename(i);
692 temp.Replace('/', '\\');
693 if (PathIsRelative(temp))
694 temp = path + _T("\\")+ temp;
695 if (PathFileExists(temp))
696 ++matches;
698 return matches;
701 int CPatch::CountDirMatches(const CString& path)
703 int matches = 0;
704 for (int i=0; i<GetNumberOfFiles(); ++i)
706 CString temp = GetFilename(i);
707 temp.Replace('/', '\\');
708 if (PathIsRelative(temp))
709 temp = path + _T("\\")+ temp;
710 // remove the filename
711 temp = temp.Left(temp.ReverseFind('\\'));
712 if (PathFileExists(temp))
713 ++matches;
715 return matches;
718 BOOL CPatch::StripPrefixes(const CString& path)
720 int nSlashesMax = 0;
721 for (int i = 0; i < GetNumberOfFiles(); ++i)
723 CString filename = GetFilename(i);
724 filename.Replace('/','\\');
725 int nSlashes = filename.Replace('\\','/');
726 nSlashesMax = max(nSlashesMax,nSlashes);
729 for (int nStrip = 1; nStrip < nSlashesMax; ++nStrip)
731 m_nStrip = nStrip;
732 if ( CountMatches(path) > GetNumberOfFiles()/3 )
734 // Use current m_nStrip
735 return TRUE;
739 // Stripping doesn't help so reset it again
740 m_nStrip = 0;
741 return FALSE;
744 CString CPatch::Strip(const CString& filename)
746 CString s = filename;
747 if ( m_nStrip>0 )
749 // Remove windows drive letter "c:"
750 if ( s.GetLength()>2 && s[1]==':')
752 s = s.Mid(2);
755 for (int nStrip = 1; nStrip <= m_nStrip; ++nStrip)
757 // "/home/ts/my-working-copy/dir/file.txt"
758 // "home/ts/my-working-copy/dir/file.txt"
759 // "ts/my-working-copy/dir/file.txt"
760 // "my-working-copy/dir/file.txt"
761 // "dir/file.txt"
762 s = s.Mid(s.FindOneOf(_T("/\\"))+1);
765 return s;
768 CString CPatch::GetFullPath(const CString& sPath, int nIndex, int fileno /* = 0*/)
770 CString temp;
771 if (fileno == 0)
772 temp = GetFilename(nIndex);
773 else
774 temp = GetFilename2(nIndex);
776 temp.Replace('/', '\\');
777 if(temp == _T("NUL"))
778 return temp;
780 if (PathIsRelative(temp))
782 if (sPath.Right(1).Compare(_T("\\")) != 0)
783 temp = sPath + _T("\\") + temp;
784 else
785 temp = sPath + temp;
788 return temp;
791 CString CPatch::RemoveUnicodeBOM(const CString& str)
793 if (str.GetLength()==0)
794 return str;
795 if (str[0] == 0xFEFF)
796 return str.Mid(1);
797 return str;