Success build TortoiseMerge.
[TortoiseGit.git] / src / TortoiseMerge / Patch.cpp
blob79ea336f5547ef956543f39f49da72fae8c6673f
1 // TortoiseMerge - a Diff/Patch program
3 // Copyright (C) 2004-2009 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "StdAfx.h"
20 #include "Resource.h"
21 #include "UnicodeUtils.h"
22 #include "DirFileEnum.h"
23 #include "TortoiseMerge.h"
24 #include "svn_wc.h"
25 #include "GitAdminDir.h"
26 #include "Patch.h"
28 #ifdef _DEBUG
29 #define new DEBUG_NEW
30 #undef THIS_FILE
31 static char THIS_FILE[] = __FILE__;
32 #endif
34 CPatch::CPatch(void)
36 m_nStrip = 0;
39 CPatch::~CPatch(void)
41 FreeMemory();
44 void CPatch::FreeMemory()
46 for (int i=0; i<m_arFileDiffs.GetCount(); i++)
48 Chunks * chunks = m_arFileDiffs.GetAt(i);
49 for (int j=0; j<chunks->chunks.GetCount(); j++)
51 delete chunks->chunks.GetAt(j);
53 chunks->chunks.RemoveAll();
54 delete chunks;
56 m_arFileDiffs.RemoveAll();
59 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)
61 CString sLine;
62 EOL ending = EOL_NOENDING;
63 INT_PTR nIndex = 0;
64 INT_PTR nLineCount = 0;
65 g_crasher.AddFile((LPCSTR)(LPCTSTR)filename, (LPCSTR)(LPCTSTR)_T("unified diff file"));
67 CFileTextLines PatchLines;
68 if (!PatchLines.Load(filename))
70 m_sErrorMessage = PatchLines.GetErrorString();
71 return FALSE;
73 m_UnicodeType = PatchLines.GetUnicodeType();
74 FreeMemory();
75 nLineCount = PatchLines.GetCount();
76 //now we got all the lines of the patch file
77 //in our array - parsing can start...
79 //first, skip possible garbage at the beginning
80 //garbage is finished when a line starts with "Index: "
81 //and the next line consists of only "=" characters
82 for (; nIndex<PatchLines.GetCount(); nIndex++)
84 sLine = PatchLines.GetAt(nIndex);
85 if (sLine.Left(4).Compare(_T("--- "))==0)
86 break;
87 if ((nIndex+1)<PatchLines.GetCount())
89 sLine = PatchLines.GetAt(nIndex+1);
90 sLine.Replace(_T("="), _T(""));
91 if (sLine.IsEmpty())
92 break;
95 if ((PatchLines.GetCount()-nIndex) < 2)
97 //no file entry found.
98 m_sErrorMessage.LoadString(IDS_ERR_PATCH_NOINDEX);
99 return FALSE;
102 //from this point on we have the real unified diff data
103 int state = 0;
104 Chunks * chunks = NULL;
105 Chunk * chunk = NULL;
106 int nAddLineCount = 0;
107 int nRemoveLineCount = 0;
108 int nContextLineCount = 0;
109 for ( ;nIndex<PatchLines.GetCount(); nIndex++)
111 sLine = PatchLines.GetAt(nIndex);
112 ending = PatchLines.GetLineEnding(nIndex);
113 if (ending != EOL_NOENDING)
114 ending = EOL_AUTOLINE;
115 if (state == 0)
117 if ((sLine.Left(4).Compare(_T("--- "))==0)&&(sLine.Find('\t') >= 0))
119 state = 2;
120 if (chunks)
122 //this is a new file diff, so add the last one to
123 //our array.
124 m_arFileDiffs.Add(chunks);
126 chunks = new Chunks();
127 int nTab = sLine.Find('\t');
128 if (nTab >= 0)
130 chunks->sFilePath = sLine.Mid(4, nTab-4).Trim();
134 switch (state)
136 case 0: //Index: <filepath>
138 CString nextLine;
139 if ((nIndex+1)<PatchLines.GetCount())
141 nextLine = PatchLines.GetAt(nIndex+1);
142 if (!nextLine.IsEmpty())
144 nextLine.Replace(_T("="), _T(""));
145 if (nextLine.IsEmpty())
147 if (chunks)
149 //this is a new file diff, so add the last one to
150 //our array.
151 m_arFileDiffs.Add(chunks);
153 chunks = new Chunks();
154 int nColon = sLine.Find(':');
155 if (nColon >= 0)
157 chunks->sFilePath = sLine.Mid(nColon+1).Trim();
158 if (chunks->sFilePath.Find('\t')>=0)
159 chunks->sFilePath.Left(chunks->sFilePath.Find('\t')).TrimRight();
160 if (chunks->sFilePath.Right(9).Compare(_T("(deleted)"))==0)
161 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-9).TrimRight();
162 if (chunks->sFilePath.Right(7).Compare(_T("(added)"))==0)
163 chunks->sFilePath.Left(chunks->sFilePath.GetLength()-7).TrimRight();
165 state++;
169 if (state == 0)
171 if (nIndex > 0)
173 nIndex--;
174 state = 4;
175 if (chunks == NULL)
177 //the line
178 //Index: <filepath>
179 //was not found at the start of a file diff!
180 break;
185 break;
186 case 1: //====================
188 sLine.Replace(_T("="), _T(""));
189 if (sLine.IsEmpty())
191 // if the next line is already the start of the chunk,
192 // then the patch/diff file was not created by svn. But we
193 // still try to use it
194 if (PatchLines.GetCount() > (nIndex + 1))
197 if (PatchLines.GetAt(nIndex+1).Left(2).Compare(_T("@@"))==0)
199 state += 2;
202 state++;
204 else
206 //the line
207 //=========================
208 //was not found
209 m_sErrorMessage.Format(IDS_ERR_PATCH_NOEQUATIONCHARLINE, nIndex);
210 goto errorcleanup;
213 break;
214 case 2: //--- <filepath>
216 if (sLine.Left(3).Compare(_T("---"))!=0)
218 //no starting "---" found
219 //seems to be either garbage or just
220 //a binary file. So start over...
221 state = 0;
222 nIndex--;
223 if (chunks)
225 delete chunks;
226 chunks = NULL;
228 break;
230 sLine = sLine.Mid(3); //remove the "---"
231 sLine =sLine.Trim();
232 //at the end of the filepath there's a revision number...
233 int bracket = sLine.ReverseFind('(');
234 if (bracket < 0)
235 // some patch files can have another '(' char, especially ones created in Chinese OS
236 bracket = sLine.ReverseFind(0xff08);
237 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
238 num = num.Mid(num.Find(' '));
239 num = num.Trim(_T(" )"));
240 // here again, check for the Chinese bracket
241 num = num.Trim(0xff09);
242 chunks->sRevision = num;
243 if (bracket < 0)
245 if (chunks->sFilePath.IsEmpty())
246 chunks->sFilePath = sLine.Trim();
248 else
249 chunks->sFilePath = sLine.Left(bracket-1).Trim();
250 if (chunks->sFilePath.Find('\t')>=0)
252 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
254 state++;
256 break;
257 case 3: //+++ <filepath>
259 if (sLine.Left(3).Compare(_T("+++"))!=0)
261 //no starting "+++" found
262 m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);
263 goto errorcleanup;
265 sLine = sLine.Mid(3); //remove the "---"
266 sLine =sLine.Trim();
267 //at the end of the filepath there's a revision number...
268 int bracket = sLine.ReverseFind('(');
269 if (bracket < 0)
270 // some patch files can have another '(' char, especially ones created in Chinese OS
271 bracket = sLine.ReverseFind(0xff08);
272 CString num = sLine.Mid(bracket); //num = "(revision xxxxx)"
273 num = num.Mid(num.Find(' '));
274 num = num.Trim(_T(" )"));
275 // here again, check for the Chinese bracket
276 num = num.Trim(0xff09);
277 chunks->sRevision2 = num;
278 if (bracket < 0)
279 chunks->sFilePath2 = sLine.Trim();
280 else
281 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
282 if (chunks->sFilePath2.Find('\t')>=0)
284 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
286 state++;
288 break;
289 case 4: //@@ -xxx,xxx +xxx,xxx @@
291 //start of a new chunk
292 if (sLine.Left(2).Compare(_T("@@"))!=0)
294 //chunk doesn't start with "@@"
295 //so there's garbage in between two file diffs
296 state = 0;
297 if (chunk)
299 delete chunk;
300 chunk = 0;
301 if (chunks)
303 for (int i=0; i<chunks->chunks.GetCount(); i++)
305 delete chunks->chunks.GetAt(i);
307 chunks->chunks.RemoveAll();
308 delete chunks;
309 chunks = NULL;
312 break; //skip the garbage
314 sLine = sLine.Mid(2);
315 sLine = sLine.Trim();
316 chunk = new Chunk();
317 CString sRemove = sLine.Left(sLine.Find(' '));
318 CString sAdd = sLine.Mid(sLine.Find(' '));
319 chunk->lRemoveStart = (-_ttol(sRemove));
320 if (sRemove.Find(',')>=0)
322 sRemove = sRemove.Mid(sRemove.Find(',')+1);
323 chunk->lRemoveLength = _ttol(sRemove);
325 else
327 chunk->lRemoveStart = 0;
328 chunk->lRemoveLength = (-_ttol(sRemove));
330 chunk->lAddStart = _ttol(sAdd);
331 if (sAdd.Find(',')>=0)
333 sAdd = sAdd.Mid(sAdd.Find(',')+1);
334 chunk->lAddLength = _ttol(sAdd);
336 else
338 chunk->lAddStart = 1;
339 chunk->lAddLength = _ttol(sAdd);
341 state++;
343 break;
344 case 5: //[ |+|-] <sourceline>
346 //this line is either a context line (with a ' ' in front)
347 //a line added (with a '+' in front)
348 //or a removed line (with a '-' in front)
349 TCHAR type;
350 if (sLine.IsEmpty())
351 type = ' ';
352 else
353 type = sLine.GetAt(0);
354 if (type == ' ')
356 //it's a context line - we don't use them here right now
357 //but maybe in the future the patch algorithm can be
358 //extended to use those in case the file to patch has
359 //already changed and no base file is around...
360 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
361 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
362 chunk->arEOLs.push_back(ending);
363 nContextLineCount++;
365 else if (type == '\\')
367 //it's a context line (sort of):
368 //warnings start with a '\' char (e.g. "\ No newline at end of file")
369 //so just ignore this...
371 else if (type == '-')
373 //a removed line
374 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
375 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
376 chunk->arEOLs.push_back(ending);
377 nRemoveLineCount++;
379 else if (type == '+')
381 //an added line
382 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(1)));
383 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
384 chunk->arEOLs.push_back(ending);
385 nAddLineCount++;
387 else
389 //none of those lines! what the hell happened here?
390 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKOWNLINETYPE, nIndex);
391 goto errorcleanup;
393 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
394 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
396 //chunk is finished
397 if (chunks)
398 chunks->chunks.Add(chunk);
399 else
400 delete chunk;
401 chunk = NULL;
402 nAddLineCount = 0;
403 nContextLineCount = 0;
404 nRemoveLineCount = 0;
405 state = 0;
408 break;
409 default:
410 ASSERT(FALSE);
411 } // switch (state)
412 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
413 if (chunk)
415 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
416 goto errorcleanup;
418 if (chunks)
419 m_arFileDiffs.Add(chunks);
420 return TRUE;
421 errorcleanup:
422 if (chunk)
423 delete chunk;
424 if (chunks)
426 for (int i=0; i<chunks->chunks.GetCount(); i++)
428 delete chunks->chunks.GetAt(i);
430 chunks->chunks.RemoveAll();
431 delete chunks;
433 FreeMemory();
434 return FALSE;
437 CString CPatch::GetFilename(int nIndex)
439 if (nIndex < 0)
440 return _T("");
441 if (nIndex < m_arFileDiffs.GetCount())
443 Chunks * c = m_arFileDiffs.GetAt(nIndex);
444 CString filepath = Strip(c->sFilePath);
445 return filepath;
447 return _T("");
450 CString CPatch::GetRevision(int nIndex)
452 if (nIndex < 0)
453 return 0;
454 if (nIndex < m_arFileDiffs.GetCount())
456 Chunks * c = m_arFileDiffs.GetAt(nIndex);
457 return c->sRevision;
459 return 0;
462 CString CPatch::GetFilename2(int nIndex)
464 if (nIndex < 0)
465 return _T("");
466 if (nIndex < m_arFileDiffs.GetCount())
468 Chunks * c = m_arFileDiffs.GetAt(nIndex);
469 CString filepath = Strip(c->sFilePath2);
470 return filepath;
472 return _T("");
475 CString CPatch::GetRevision2(int nIndex)
477 if (nIndex < 0)
478 return 0;
479 if (nIndex < m_arFileDiffs.GetCount())
481 Chunks * c = m_arFileDiffs.GetAt(nIndex);
482 return c->sRevision2;
484 return 0;
487 BOOL CPatch::PatchFile(const CString& sPath, const CString& sSavePath, const CString& sBaseFile)
489 if (PathIsDirectory(sPath))
491 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, (LPCTSTR)sPath);
492 return FALSE;
494 // find the entry in the patch file which matches the full path given in sPath.
495 int nIndex = -1;
496 // use the longest path that matches
497 int nMaxMatch = 0;
498 for (int i=0; i<GetNumberOfFiles(); i++)
500 CString temppath = sPath;
501 CString temp = GetFilename(i);
502 temppath.Replace('/', '\\');
503 temp.Replace('/', '\\');
504 if (temppath.Mid(temppath.GetLength()-temp.GetLength()-1, 1).CompareNoCase(_T("\\"))==0)
506 temppath = temppath.Right(temp.GetLength());
507 if ((temp.CompareNoCase(temppath)==0))
509 if (nMaxMatch < temp.GetLength())
511 nMaxMatch = temp.GetLength();
512 nIndex = i;
516 else if (temppath.CompareNoCase(temp)==0)
518 if ((nIndex < 0)&&(! temp.IsEmpty()))
520 nIndex = i;
524 if (nIndex < 0)
526 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, (LPCTSTR)sPath);
527 return FALSE;
530 CString sLine;
531 CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;
532 if (PathFileExists(sPatchFile))
534 g_crasher.AddFile((LPCSTR)(LPCTSTR)sPatchFile, (LPCSTR)(LPCTSTR)_T("File to patch"));
536 CFileTextLines PatchLines;
537 CFileTextLines PatchLinesResult;
538 PatchLines.Load(sPatchFile);
539 PatchLinesResult = PatchLines; //.Copy(PatchLines);
540 PatchLines.CopySettings(&PatchLinesResult);
542 Chunks * chunks = m_arFileDiffs.GetAt(nIndex);
544 for (int i=0; i<chunks->chunks.GetCount(); i++)
546 Chunk * chunk = chunks->chunks.GetAt(i);
547 LONG lRemoveLine = chunk->lRemoveStart;
548 LONG lAddLine = chunk->lAddStart;
549 for (int j=0; j<chunk->arLines.GetCount(); j++)
551 CString sPatchLine = chunk->arLines.GetAt(j);
552 EOL ending = chunk->arEOLs[j];
553 if ((m_UnicodeType != CFileTextLines::UTF8)&&(m_UnicodeType != CFileTextLines::UTF8BOM))
555 if ((PatchLines.GetUnicodeType()==CFileTextLines::UTF8)||(m_UnicodeType == CFileTextLines::UTF8BOM))
557 // convert the UTF-8 contents in CString sPatchLine into a CStringA
558 sPatchLine = CUnicodeUtils::GetUnicode(CStringA(sPatchLine));
561 int nPatchState = (int)chunk->arLinesStates.GetAt(j);
562 switch (nPatchState)
564 case PATCHSTATE_REMOVED:
566 if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))
568 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
569 return FALSE;
571 if (lAddLine == 0)
572 lAddLine = 1;
573 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))
575 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
576 return FALSE;
578 if (lAddLine > PatchLines.GetCount())
580 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, _T(""));
581 return FALSE;
583 PatchLines.RemoveAt(lAddLine-1);
585 break;
586 case PATCHSTATE_ADDED:
588 if (lAddLine == 0)
589 lAddLine = 1;
590 PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);
591 lAddLine++;
593 break;
594 case PATCHSTATE_CONTEXT:
596 if (lAddLine > PatchLines.GetCount())
598 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, _T(""), (LPCTSTR)sPatchLine);
599 return FALSE;
601 if (lAddLine == 0)
602 lAddLine++;
603 if (lRemoveLine == 0)
604 lRemoveLine++;
605 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&
606 (!HasExpandedKeyWords(sPatchLine)) &&
607 (lRemoveLine <= PatchLines.GetCount()) &&
608 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))
610 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))
611 lAddLine++;
612 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))
613 lAddLine += 2;
614 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))
615 lRemoveLine++;
616 else
618 m_sErrorMessage.Format(IDS_ERR_PATCH_DOESNOTMATCH, (LPCTSTR)sPatchLine, (LPCTSTR)PatchLines.GetAt(lAddLine-1));
619 return FALSE;
622 lAddLine++;
623 lRemoveLine++;
625 break;
626 default:
627 ASSERT(FALSE);
628 break;
629 } // switch (nPatchState)
630 } // for (j=0; j<chunk->arLines.GetCount(); j++)
631 } // for (int i=0; i<chunks->chunks.GetCount(); i++)
632 if (!sSavePath.IsEmpty())
634 PatchLines.Save(sSavePath, false);
636 return TRUE;
639 BOOL CPatch::HasExpandedKeyWords(const CString& line)
641 if (line.Find(_T("$LastChangedDate"))>=0)
642 return TRUE;
643 if (line.Find(_T("$Date"))>=0)
644 return TRUE;
645 if (line.Find(_T("$LastChangedRevision"))>=0)
646 return TRUE;
647 if (line.Find(_T("$Rev"))>=0)
648 return TRUE;
649 if (line.Find(_T("$LastChangedBy"))>=0)
650 return TRUE;
651 if (line.Find(_T("$Author"))>=0)
652 return TRUE;
653 if (line.Find(_T("$HeadURL"))>=0)
654 return TRUE;
655 if (line.Find(_T("$URL"))>=0)
656 return TRUE;
657 if (line.Find(_T("$Id"))>=0)
658 return TRUE;
659 return FALSE;
662 CString CPatch::CheckPatchPath(const CString& path)
664 //first check if the path already matches
665 if (CountMatches(path) > (GetNumberOfFiles()/3))
666 return path;
667 //now go up the tree and try again
668 CString upperpath = path;
669 while (upperpath.ReverseFind('\\')>0)
671 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
672 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
673 return upperpath;
675 //still no match found. So try sub folders
676 bool isDir = false;
677 CString subpath;
678 CDirFileEnum filefinder(path);
679 while (filefinder.NextFile(subpath, &isDir))
681 if (!isDir)
682 continue;
683 if (g_GitAdminDir.IsAdminDirPath(subpath))
684 continue;
685 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
686 return subpath;
689 // if a patch file only contains newly added files
690 // we can't really find the correct path.
691 // But: we can compare paths strings without the filenames
692 // and check if at least those match
693 upperpath = path;
694 while (upperpath.ReverseFind('\\')>0)
696 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
697 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
698 return upperpath;
701 return path;
704 int CPatch::CountMatches(const CString& path)
706 int matches = 0;
707 for (int i=0; i<GetNumberOfFiles(); ++i)
709 CString temp = GetFilename(i);
710 temp.Replace('/', '\\');
711 if (PathIsRelative(temp))
712 temp = path + _T("\\")+ temp;
713 if (PathFileExists(temp))
714 matches++;
716 return matches;
719 int CPatch::CountDirMatches(const CString& path)
721 int matches = 0;
722 for (int i=0; i<GetNumberOfFiles(); ++i)
724 CString temp = GetFilename(i);
725 temp.Replace('/', '\\');
726 if (PathIsRelative(temp))
727 temp = path + _T("\\")+ temp;
728 // remove the filename
729 temp = temp.Left(temp.ReverseFind('\\'));
730 if (PathFileExists(temp))
731 matches++;
733 return matches;
736 BOOL CPatch::StripPrefixes(const CString& path)
738 int nSlashesMax = 0;
739 for (int i=0; i<GetNumberOfFiles(); i++)
741 CString filename = GetFilename(i);
742 filename.Replace('/','\\');
743 int nSlashes = filename.Replace('\\','/');
744 nSlashesMax = max(nSlashesMax,nSlashes);
747 for (int nStrip=1;nStrip<nSlashesMax;nStrip++)
749 m_nStrip = nStrip;
750 if ( CountMatches(path) > GetNumberOfFiles()/3 )
752 // Use current m_nStrip
753 return TRUE;
757 // Stripping doesn't help so reset it again
758 m_nStrip = 0;
759 return FALSE;
762 CString CPatch::Strip(const CString& filename)
764 CString s = filename;
765 if ( m_nStrip>0 )
767 // Remove windows drive letter "c:"
768 if ( s.GetLength()>2 && s[1]==':')
770 s = s.Mid(2);
773 for (int nStrip=1;nStrip<=m_nStrip;nStrip++)
775 // "/home/ts/my-working-copy/dir/file.txt"
776 // "home/ts/my-working-copy/dir/file.txt"
777 // "ts/my-working-copy/dir/file.txt"
778 // "my-working-copy/dir/file.txt"
779 // "dir/file.txt"
780 s = s.Mid(s.FindOneOf(_T("/\\"))+1);
783 return s;
786 CString CPatch::RemoveUnicodeBOM(const CString& str)
788 if (str.GetLength()==0)
789 return str;
790 if (str[0] == 0xFEFF)
791 return str.Mid(1);
792 return str;