Fix typos
[TortoiseGit.git] / src / TortoiseMerge / Patch.cpp
blob97aa6fb611fe241a55a42b20f966ad726f727b5f
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2009-2013, 2015-2023 - 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()
40 CPatch::~CPatch()
44 void CPatch::FreeMemory()
46 m_arFileDiffs.clear();
49 BOOL CPatch::ParsePatchFile(CFileTextLines &PatchLines)
51 CString sLine;
52 EOL ending = EOL::NoEnding;
54 int state = 0;
55 int nIndex = 0;
56 std::unique_ptr<Chunks> chunks;
57 std::unique_ptr<Chunk> chunk;
58 int nAddLineCount = 0;
59 int nRemoveLineCount = 0;
60 int nContextLineCount = 0;
61 std::map<CString, int> filenamesToPatch;
62 for ( ; nIndex < PatchLines.GetCount(); ++nIndex)
64 sLine = PatchLines.GetAt(nIndex);
65 ending = PatchLines.GetLineEnding(nIndex);
66 if (ending != EOL::NoEnding)
67 ending = EOL::AutoLine;
69 switch (state)
71 case 0:
73 // diff
74 if (CStringUtils::StartsWith(sLine, L"diff "))
76 if (chunks)
78 //this is a new file diff, so add the last one to
79 //our array.
80 if (!chunks->chunks.empty())
81 m_arFileDiffs.emplace_back(std::move(chunks));
83 chunks = std::make_unique<Chunks>();
84 state = 1;
85 break;
88 [[fallthrough]];
89 case 1:
91 //index
92 if (CStringUtils::StartsWith(sLine, L"index "))
94 int dotstart = sLine.Find(L"..", static_cast<int>(wcslen(L"index ")));
95 if (dotstart > 0 && chunks)
97 chunks->sRevision = sLine.Mid(static_cast<int>(wcslen(L"index ")), dotstart - static_cast<int>(wcslen(L"index ")));
98 int end = sLine.Find(L' ', dotstart + 2);
99 if (end > 0)
100 chunks->sRevision2 = sLine.Mid(dotstart + 2, end - (dotstart + 2));
102 break;
105 //---
106 if (CStringUtils::StartsWith(sLine, L"--- "))
108 if (state == 0)
110 if (chunks)
112 //this is a new file diff, so add the last one to
113 //our array.
114 if (!chunks->chunks.empty())
115 m_arFileDiffs.emplace_back(std::move(chunks));
117 chunks = std::make_unique<Chunks>();
120 sLine = sLine.Mid(static_cast<int>(wcslen(L"---"))); //remove the "---"
121 sLine =sLine.Trim();
122 //at the end of the filepath there's a revision number...
123 int bracket = sLine.ReverseFind('(');
124 if (bracket < 0)
125 // some patch files can have another '(' char, especially ones created in Chinese OS
126 bracket = sLine.ReverseFind(0xff08);
128 if (bracket < 0)
130 if (chunks->sFilePath.IsEmpty())
131 chunks->sFilePath = sLine.Trim();
133 else
134 chunks->sFilePath = sLine.Left(bracket-1).Trim();
136 if (chunks->sFilePath.Find('\t')>=0)
137 chunks->sFilePath = chunks->sFilePath.Left(chunks->sFilePath.Find('\t'));
138 if (CStringUtils::StartsWith(chunks->sFilePath, L"\"") && CStringUtils::EndsWith(chunks->sFilePath, L'"'))
139 chunks->sFilePath = CStringUtils::UnescapeGitQuotePath(chunks->sFilePath.Mid(1, chunks->sFilePath.GetLength() - 1));
140 if (CStringUtils::StartsWith(chunks->sFilePath, L"a/"))
141 chunks->sFilePath=chunks->sFilePath.Mid(static_cast<int>(wcslen(L"a/")));
143 if (CStringUtils::StartsWith(chunks->sFilePath, L"b/"))
144 chunks->sFilePath=chunks->sFilePath.Mid(static_cast<int>(wcslen(L"b/")));
147 chunks->sFilePath.Replace(L'/', L'\\');
149 if (chunks->sFilePath == L"\\dev\\null" || chunks->sFilePath == L"/dev/null")
150 chunks->sFilePath = L"NUL";
152 state = 3;
154 if (state == 0)
156 if (CStringUtils::StartsWith(sLine, L"@@"))
158 if (chunks)
160 nIndex--;
161 state = 4;
163 else
164 break;
168 break;
170 case 3:
172 // +++
173 if (!CStringUtils::StartsWith(sLine, L"+++"))
175 // no starting "+++" found
176 m_sErrorMessage.Format(IDS_ERR_PATCH_NOADDFILELINE, nIndex);
177 goto errorcleanup;
179 sLine = sLine.Mid(static_cast<int>(wcslen(L"---"))); //remove the "---"
180 sLine =sLine.Trim();
182 //at the end of the filepath there's a revision number...
183 int bracket = sLine.ReverseFind('(');
184 if (bracket < 0)
185 // some patch files can have another '(' char, especially ones created in Chinese OS
186 bracket = sLine.ReverseFind(0xff08);
188 if (bracket < 0)
189 chunks->sFilePath2 = sLine.Trim();
190 else
191 chunks->sFilePath2 = sLine.Left(bracket-1).Trim();
192 if (chunks->sFilePath2.Find('\t')>=0)
193 chunks->sFilePath2 = chunks->sFilePath2.Left(chunks->sFilePath2.Find('\t'));
194 if (CStringUtils::StartsWith(chunks->sFilePath2, L"\"") && chunks->sFilePath2.ReverseFind(L'"') == chunks->sFilePath2.GetLength() - 1)
195 chunks->sFilePath2 = CStringUtils::UnescapeGitQuotePath(chunks->sFilePath2.Mid(1, chunks->sFilePath2.GetLength() - 1));
196 if (CStringUtils::StartsWith(chunks->sFilePath2, L"a/"))
197 chunks->sFilePath2=chunks->sFilePath2.Mid(static_cast<int>(wcslen(L"a/")));
199 if (CStringUtils::StartsWith(chunks->sFilePath2, L"b/"))
200 chunks->sFilePath2=chunks->sFilePath2.Mid(static_cast<int>(wcslen(L"b/")));
202 chunks->sFilePath2.Replace(L'/', L'\\');
203 chunks->sFilePath2.Replace(L'/', L'\\');
205 if (chunks->sFilePath2 == L"\\dev\\null" || chunks->sFilePath2 == L"/dev/null")
206 chunks->sFilePath2 = L"NUL";
208 ++state;
210 break;
212 case 4:
214 //start of a new chunk
215 if (!CStringUtils::StartsWith(sLine, L"@@"))
217 //chunk doesn't start with "@@"
218 //so there's garbage in between two file diffs
219 state = 0;
220 chunk.reset();
221 chunks.reset();
222 break; //skip the garbage
225 //@@ -xxx,xxx +xxx,xxx @@
226 sLine = sLine.Mid(static_cast<int>(wcslen(L"@@")));
227 sLine = sLine.Trim();
228 chunk = std::make_unique<Chunk>();
229 CString sRemove = sLine.Left(sLine.Find(' '));
230 CString sAdd = sLine.Mid(sLine.Find(' '));
231 chunk->lRemoveStart = abs(_wtol(sRemove));
232 if (sRemove.Find(',')>=0)
234 sRemove = sRemove.Mid(sRemove.Find(',')+1);
235 chunk->lRemoveLength = _wtol(sRemove);
237 else
239 chunk->lRemoveStart = 0;
240 chunk->lRemoveLength = abs(_wtol(sRemove));
242 chunk->lAddStart = _wtol(sAdd);
243 if (sAdd.Find(',')>=0)
245 sAdd = sAdd.Mid(sAdd.Find(',')+1);
246 chunk->lAddLength = _wtol(sAdd);
248 else
250 chunk->lAddStart = 1;
251 chunk->lAddLength = _wtol(sAdd);
253 ++state;
255 break;
257 case 5: //[ |+|-] <sourceline>
259 //this line is either a context line (with a ' ' in front)
260 //a line added (with a '+' in front)
261 //or a removed line (with a '-' in front)
262 wchar_t type;
263 if (sLine.IsEmpty())
264 type = ' ';
265 else
266 type = sLine.GetAt(0);
267 if (type == ' ')
269 //it's a context line - we don't use them here right now
270 //but maybe in the future the patch algorithm can be
271 //extended to use those in case the file to patch has
272 //already changed and no base file is around...
273 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(static_cast<int>(wcslen(L" ")))));
274 chunk->arLinesStates.Add(PATCHSTATE_CONTEXT);
275 chunk->arEOLs.push_back(ending);
276 ++nContextLineCount;
278 else if (type == '\\')
280 //it's a context line (sort of):
281 //warnings start with a '\' char (e.g. "\ No newline at end of file")
282 //so just ignore this...
284 else if (type == '-')
286 //a removed line
287 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(static_cast<int>(wcslen(L"-")))));
288 if (chunk->lRemoveStart == 1 && nRemoveLineCount == 0)
290 if (HasUnicodeBOM(sLine.Mid(static_cast<int>(wcslen(L"-")))))
291 chunks->oldHasBom = 1;
292 else
293 chunks->oldHasBom = 0;
295 chunk->arLinesStates.Add(PATCHSTATE_REMOVED);
296 chunk->arEOLs.push_back(ending);
297 ++nRemoveLineCount;
299 else if (type == '+')
301 //an added line
302 chunk->arLines.Add(RemoveUnicodeBOM(sLine.Mid(static_cast<int>(wcslen(L"+")))));
303 if (chunk->lAddStart == 1 && nAddLineCount == 0)
305 if (HasUnicodeBOM(sLine.Mid(static_cast<int>(wcslen(L"-")))))
306 chunks->newHasBom = 1;
307 else
308 chunks->newHasBom = 0;
310 chunk->arLinesStates.Add(PATCHSTATE_ADDED);
311 chunk->arEOLs.push_back(ending);
312 ++nAddLineCount;
314 else
316 //none of those lines! what the hell happened here?
317 m_sErrorMessage.Format(IDS_ERR_PATCH_UNKNOWNLINETYPE, nIndex);
318 goto errorcleanup;
320 if ((chunk->lAddLength == (nAddLineCount + nContextLineCount)) &&
321 chunk->lRemoveLength == (nRemoveLineCount + nContextLineCount))
323 //chunk is finished
324 if (chunks)
325 chunks->chunks.emplace_back(std::move(chunk));
326 chunk.reset();
327 nAddLineCount = 0;
328 nContextLineCount = 0;
329 nRemoveLineCount = 0;
330 state = 0;
333 break;
334 default:
335 ASSERT(FALSE);
336 } // switch (state)
337 } // for ( ;nIndex<m_PatchLines.GetCount(); nIndex++)
338 if (chunk)
340 m_sErrorMessage.LoadString(IDS_ERR_PATCH_CHUNKMISMATCH);
341 goto errorcleanup;
343 if (chunks)
344 m_arFileDiffs.emplace_back(chunks.release());
346 for (size_t i = 0; i < m_arFileDiffs.size(); ++i)
348 if (filenamesToPatch[m_arFileDiffs[i]->sFilePath] > 1 && m_arFileDiffs[i]->sFilePath != L"NUL")
350 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENAMENOTUNIQUE, static_cast<LPCWSTR>(m_arFileDiffs[i]->sFilePath));
351 FreeMemory();
352 return FALSE;
354 ++filenamesToPatch[m_arFileDiffs[i]->sFilePath];
355 if (m_arFileDiffs[i]->sFilePath != m_arFileDiffs[i]->sFilePath2)
357 if (filenamesToPatch[m_arFileDiffs[i]->sFilePath2] > 1 && m_arFileDiffs[i]->sFilePath2 != L"NUL")
359 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENAMENOTUNIQUE, static_cast<LPCWSTR>(m_arFileDiffs[i]->sFilePath));
360 FreeMemory();
361 return FALSE;
363 ++filenamesToPatch[m_arFileDiffs[i]->sFilePath2];
367 return TRUE;
369 errorcleanup:
370 FreeMemory();
371 return FALSE;
374 BOOL CPatch::OpenUnifiedDiffFile(const CString& filename)
376 #ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_H_
377 CCrashReport::Instance().AddFile2(filename, nullptr, L"unified diff file", CR_AF_MAKE_FILE_COPY);
378 #endif
380 CFileTextLines PatchLines;
381 if (!PatchLines.Load(filename))
383 m_sErrorMessage = PatchLines.GetErrorString();
384 return FALSE;
386 FreeMemory();
388 //now we got all the lines of the patch file
389 //in our array - parsing can start...
390 return ParsePatchFile(PatchLines);
393 CString CPatch::GetFilename(int nIndex)
395 if (nIndex < 0 || nIndex >= static_cast<int>(m_arFileDiffs.size()))
396 return L"";
398 return Strip(m_arFileDiffs[nIndex]->sFilePath);
401 CString CPatch::GetRevision(int nIndex)
403 if (nIndex < 0 || nIndex >= static_cast<int>(m_arFileDiffs.size()))
404 return L"";
406 return m_arFileDiffs[nIndex]->sRevision;
409 CString CPatch::GetFilename2(int nIndex)
411 if (nIndex < 0 || nIndex >= static_cast<int>(m_arFileDiffs.size()))
412 return L"";
414 return Strip(m_arFileDiffs[nIndex]->sFilePath2);
417 CString CPatch::GetRevision2(int nIndex)
419 if (nIndex < 0 || nIndex >= static_cast<int>(m_arFileDiffs.size()))
420 return L"";
422 return m_arFileDiffs[nIndex]->sRevision2;
425 int CPatch::PatchFile(const int strip, int nIndex, const CString& sPatchPath, const CString& sSavePath, const CString& sBaseFile, const bool force)
427 m_nStrip = strip;
428 CString sPath = GetFullPath(sPatchPath, nIndex);
429 if (PathIsDirectory(sPath))
431 m_sErrorMessage.Format(IDS_ERR_PATCH_INVALIDPATCHFILE, static_cast<LPCWSTR>(sPath));
432 return FALSE;
434 if (nIndex < 0)
436 m_sErrorMessage.Format(IDS_ERR_PATCH_FILENOTINPATCH, static_cast<LPCWSTR>(sPath));
437 return FALSE;
440 if (!force && sPath == L"NUL" && PathFileExists(GetFullPath(sPatchPath, nIndex, 1)))
441 return FALSE;
443 if (GetFullPath(sPatchPath, nIndex, 1) == L"NUL" && !PathFileExists(sPath))
444 return 2;
446 CString sLine;
447 CString sPatchFile = sBaseFile.IsEmpty() ? sPath : sBaseFile;
448 #ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_H_
449 if (PathFileExists(sPatchFile))
451 CCrashReport::Instance().AddFile2(sPatchFile, nullptr, L"File to patch", CR_AF_MAKE_FILE_COPY);
453 #endif
454 CFileTextLines PatchLines;
455 CFileTextLines PatchLinesResult;
456 PatchLines.Load(sPatchFile);
457 PatchLinesResult = PatchLines; //.Copy(PatchLines);
458 PatchLines.CopySettings(&PatchLinesResult);
460 auto chunks = m_arFileDiffs[nIndex].get();
462 for (size_t i = 0; i < chunks->chunks.size(); ++i)
464 auto chunk = chunks->chunks[i].get();
465 LONG lRemoveLine = chunk->lRemoveStart;
466 LONG lAddLine = chunk->lAddStart;
467 for (int j = 0; j < chunk->arLines.GetCount(); ++j)
469 CString sPatchLine = chunk->arLines.GetAt(j);
470 EOL ending = chunk->arEOLs[j];
471 int nPatchState = static_cast<int>(chunk->arLinesStates.GetAt(j));
472 switch (nPatchState)
474 case PATCHSTATE_REMOVED:
476 if ((lAddLine > PatchLines.GetCount())||(PatchLines.GetCount()==0))
478 m_sErrorMessage.FormatMessage(IDS_ERR_PATCH_DOESNOTMATCH, L"", static_cast<LPCWSTR>(sPatchLine));
479 return FALSE;
481 if (lAddLine == 0)
482 lAddLine = 1;
483 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0)&&(!HasExpandedKeyWords(sPatchLine)))
485 m_sErrorMessage.FormatMessage(IDS_ERR_PATCH_DOESNOTMATCH, static_cast<LPCWSTR>(sPatchLine), static_cast<LPCWSTR>(PatchLines.GetAt(lAddLine-1)));
486 return FALSE;
488 if (lAddLine > PatchLines.GetCount())
490 m_sErrorMessage.FormatMessage(IDS_ERR_PATCH_DOESNOTMATCH, static_cast<LPCWSTR>(sPatchLine), L"");
491 return FALSE;
493 PatchLines.RemoveAt(lAddLine-1);
495 break;
496 case PATCHSTATE_ADDED:
498 if (lAddLine == 0)
499 lAddLine = 1;
500 // check context after insertions in order to avoid double insertions
501 bool insertOk = !(lAddLine < PatchLines.GetCount());
502 int k = j;
503 for (; k < chunk->arLines.GetCount(); ++k)
505 if (static_cast<int>(chunk->arLinesStates.GetAt(k)) == PATCHSTATE_ADDED)
506 continue;
507 if (PatchLines.GetCount() >= lAddLine && chunk->arLines.GetAt(k).Compare(PatchLines.GetAt(lAddLine - 1)) == 0)
508 insertOk = true;
509 else
510 break;
513 if (insertOk)
515 PatchLines.InsertAt(lAddLine-1, sPatchLine, ending);
516 ++lAddLine;
518 else
520 if (k >= chunk->arLines.GetCount())
521 k = j;
522 m_sErrorMessage.FormatMessage(IDS_ERR_PATCH_DOESNOTMATCH, static_cast<LPCWSTR>(PatchLines.GetAt(lAddLine) - 1), static_cast<LPCWSTR>(chunk->arLines.GetAt(k)));
523 return FALSE;
526 break;
527 case PATCHSTATE_CONTEXT:
529 if (lAddLine > PatchLines.GetCount())
531 m_sErrorMessage.FormatMessage(IDS_ERR_PATCH_DOESNOTMATCH, L"", static_cast<LPCWSTR>(sPatchLine));
532 return FALSE;
534 if (lAddLine == 0)
535 ++lAddLine;
536 if (lRemoveLine == 0)
537 ++lRemoveLine;
538 if ((sPatchLine.Compare(PatchLines.GetAt(lAddLine-1))!=0) &&
539 (!HasExpandedKeyWords(sPatchLine)) &&
540 (lRemoveLine <= PatchLines.GetCount()) &&
541 (sPatchLine.Compare(PatchLines.GetAt(lRemoveLine-1))!=0))
543 if ((lAddLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine))==0))
544 ++lAddLine;
545 else if (((lAddLine + 1) < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lAddLine+1))==0))
546 lAddLine += 2;
547 else if ((lRemoveLine < PatchLines.GetCount())&&(sPatchLine.Compare(PatchLines.GetAt(lRemoveLine))==0))
548 ++lRemoveLine;
549 else
551 m_sErrorMessage.FormatMessage(IDS_ERR_PATCH_DOESNOTMATCH, static_cast<LPCWSTR>(sPatchLine), static_cast<LPCWSTR>(PatchLines.GetAt(lAddLine-1)));
552 return FALSE;
555 ++lAddLine;
556 ++lRemoveLine;
558 break;
559 default:
560 ASSERT(FALSE);
561 break;
562 } // switch (nPatchState)
563 } // for (j=0; j<chunk->arLines.GetCount(); j++)
564 } // for (int i=0; i<chunks->chunks.GetCount(); i++)
565 if ((chunks->oldHasBom == 0 || (chunks->chunks.size() == 1 && chunks->chunks.at(0).get()->lRemoveStart == 0 && chunks->chunks.at(0).get()->lRemoveLength == 0)) && chunks->newHasBom == 1 && PatchLines.GetUnicodeType() != CFileTextLines::UnicodeType::UTF8BOM)
567 auto saveParams = PatchLines.GetSaveParams();
568 saveParams.m_UnicodeType = CFileTextLines::UnicodeType::UTF8BOM;
569 PatchLines.SetSaveParams(saveParams);
571 else if (chunks->oldHasBom == 1 && chunks->newHasBom == 0 && PatchLines.GetUnicodeType() == CFileTextLines::UnicodeType::UTF8BOM)
573 auto saveParams = PatchLines.GetSaveParams();
574 saveParams.m_UnicodeType = CFileTextLines::UnicodeType::UTF8;
575 PatchLines.SetSaveParams(saveParams);
577 if (!sSavePath.IsEmpty())
579 PatchLines.Save(sSavePath, false);
581 return TRUE;
584 BOOL CPatch::HasExpandedKeyWords(const CString& line) const
586 if (line.Find(L"$LastChangedDate") >= 0)
587 return TRUE;
588 if (line.Find(L"$Date") >= 0)
589 return TRUE;
590 if (line.Find(L"$LastChangedRevision") >= 0)
591 return TRUE;
592 if (line.Find(L"$Rev") >= 0)
593 return TRUE;
594 if (line.Find(L"$LastChangedBy") >= 0)
595 return TRUE;
596 if (line.Find(L"$Author") >= 0)
597 return TRUE;
598 if (line.Find(L"$HeadURL") >= 0)
599 return TRUE;
600 if (line.Find(L"$URL") >= 0)
601 return TRUE;
602 if (line.Find(L"$Id") >= 0)
603 return TRUE;
604 return FALSE;
607 CString CPatch::CheckPatchPath(const CString& path)
609 //first check if the path already matches
610 if (CountMatches(path) > (GetNumberOfFiles()/3))
611 return path;
612 //now go up the tree and try again
613 CString upperpath = path;
614 while (upperpath.ReverseFind('\\')>0)
616 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
617 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
618 return upperpath;
620 //still no match found. So try sub folders
621 CDirFileEnum filefinder(path);
622 while (auto file = filefinder.NextFile())
624 if (!file->IsDirectory())
625 continue;
626 CString subpath = file->GetFilePath();
627 if (GitAdminDir::IsAdminDirPath(subpath))
628 continue;
629 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
630 return subpath;
633 // if a patch file only contains newly added files
634 // we can't really find the correct path.
635 // But: we can compare paths strings without the filenames
636 // and check if at least those match
637 upperpath = path;
638 while (upperpath.ReverseFind('\\')>0)
640 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
641 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
642 return upperpath;
645 return path;
648 int CPatch::CountMatches(const CString& path)
650 int matches = 0;
651 for (int i=0; i<GetNumberOfFiles(); ++i)
653 CString temp = GetFilename(i);
654 temp.Replace('/', '\\');
655 if (PathIsRelative(temp))
656 temp = path + L'\\' + temp;
657 if (PathFileExists(temp))
658 ++matches;
660 return matches;
663 int CPatch::CountDirMatches(const CString& path)
665 int matches = 0;
666 for (int i=0; i<GetNumberOfFiles(); ++i)
668 CString temp = GetFilename(i);
669 temp.Replace('/', '\\');
670 if (PathIsRelative(temp))
671 temp = path + L'\\' + temp;
672 // remove the filename
673 temp = temp.Left(temp.ReverseFind('\\'));
674 if (PathFileExists(temp))
675 ++matches;
677 return matches;
680 CString CPatch::Strip(const CString& filename) const
682 CString s = filename;
683 if ( m_nStrip>0 )
685 // Remove windows drive letter "c:"
686 if ( s.GetLength()>2 && s[1]==':')
688 s = s.Mid(2);
691 for (int nStrip = 1; nStrip <= m_nStrip; ++nStrip)
693 // "/home/ts/my-working-copy/dir/file.txt"
694 // "home/ts/my-working-copy/dir/file.txt"
695 // "ts/my-working-copy/dir/file.txt"
696 // "my-working-copy/dir/file.txt"
697 // "dir/file.txt"
698 s = s.Mid(s.FindOneOf(L"/\\") + 1);
701 return s;
704 CString CPatch::GetFullPath(const CString& sPath, int nIndex, int fileno /* = 0*/)
706 CString temp;
707 if (fileno == 0)
708 temp = GetFilename(nIndex);
709 else
710 temp = GetFilename2(nIndex);
712 temp.Replace('/', '\\');
713 if(temp == L"NUL")
714 return temp;
716 if (PathIsRelative(temp))
718 if (sPath.Right(1) != L"\\")
719 temp = sPath + L'\\' + temp;
720 else
721 temp = sPath + temp;
724 return temp;
727 bool CPatch::HasUnicodeBOM(const CString& str) const
729 if (str.IsEmpty())
730 return false;
731 if (str[0] == 0xFEFF)
732 return true;
733 return false;
736 CString CPatch::RemoveUnicodeBOM(const CString& str) const
738 if (str.IsEmpty())
739 return str;
740 if (str[0] == 0xFEFF)
741 return str.Mid(1);
742 return str;