Make sure we also kill AsyncReadStdErrThread when we kill a thread
[TortoiseGit.git] / src / Git / TGitPath.cpp
blob3360be854502fccbea0323e45c40570c4e3958c2
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - TortoiseGit
4 // Copyright (C) 2003-2008 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TGitPath.h"
22 #include "UnicodeUtils.h"
23 #include "GitAdminDir.h"
24 #include "PathUtils.h"
25 #include <regex>
26 #include "Git.h"
27 #include "../TortoiseShell/Globals.h"
28 #include "StringUtils.h"
29 #include "SmartHandle.h"
30 #include "../Resources/LoglistCommonResource.h"
31 #include <memory>
33 extern CGit g_Git;
35 CTGitPath::CTGitPath(void)
36 : m_bDirectoryKnown(false)
37 , m_bIsDirectory(false)
38 , m_bURLKnown(false)
39 , m_bHasAdminDirKnown(false)
40 , m_bHasAdminDir(false)
41 , m_bIsValidOnWindowsKnown(false)
42 , m_bIsValidOnWindows(false)
43 , m_bIsReadOnly(false)
44 , m_bIsAdminDirKnown(false)
45 , m_bIsAdminDir(false)
46 , m_bExists(false)
47 , m_bExistsKnown(false)
48 , m_bLastWriteTimeKnown(0)
49 , m_lastWriteTime(0)
50 , m_bIsSpecialDirectoryKnown(false)
51 , m_bIsSpecialDirectory(false)
52 , m_bIsWCRootKnown(false)
53 , m_bIsWCRoot(false)
54 , m_fileSize(0)
55 , m_Checked(false)
57 m_Action=0;
58 m_ParentNo=0;
59 m_Stage = 0;
62 CTGitPath::~CTGitPath(void)
65 // Create a TGitPath object from an unknown path type (same as using SetFromUnknown)
66 CTGitPath::CTGitPath(const CString& sUnknownPath) : CTGitPath()
68 SetFromUnknown(sUnknownPath);
71 int CTGitPath::ParserAction(BYTE action)
73 //action=action.TrimLeft();
74 //TCHAR c=action.GetAt(0);
75 if(action == 'M')
76 m_Action|= LOGACTIONS_MODIFIED;
77 if(action == 'R')
78 m_Action|= LOGACTIONS_REPLACED;
79 if(action == 'A')
80 m_Action|= LOGACTIONS_ADDED;
81 if(action == 'D')
82 m_Action|= LOGACTIONS_DELETED;
83 if(action == 'U')
84 m_Action|= LOGACTIONS_UNMERGED;
85 if(action == 'K')
86 m_Action|= LOGACTIONS_DELETED;
87 if(action == 'C' )
88 m_Action|= LOGACTIONS_COPY;
89 if(action == 'T')
90 m_Action|= LOGACTIONS_MODIFIED;
92 return m_Action;
95 int CTGitPath::ParserAction(git_delta_t action)
97 if (action == GIT_DELTA_MODIFIED)
98 m_Action |= LOGACTIONS_MODIFIED;
99 if (action == GIT_DELTA_RENAMED)
100 m_Action |= LOGACTIONS_REPLACED;
101 if (action == GIT_DELTA_ADDED)
102 m_Action |= LOGACTIONS_ADDED;
103 if (action == GIT_DELTA_DELETED)
104 m_Action |= LOGACTIONS_DELETED;
105 if (action == GIT_DELTA_UNMODIFIED)
106 m_Action |= LOGACTIONS_UNMERGED;
107 if (action == GIT_DELTA_COPIED)
108 m_Action |= LOGACTIONS_COPY;
109 if (action == GIT_DELTA_TYPECHANGE)
110 m_Action |= LOGACTIONS_MODIFIED;
112 return m_Action;
115 void CTGitPath::SetFromGit(const char* pPath)
117 Reset();
118 if (!pPath)
119 return;
120 int len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, nullptr, 0);
121 if (len)
123 len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, m_sFwdslashPath.GetBuffer(len+1), len+1);
124 m_sFwdslashPath.ReleaseBuffer(len-1);
126 SanitizeRootPath(m_sFwdslashPath, true);
129 void CTGitPath::SetFromGit(const char* pPath, bool bIsDirectory)
131 SetFromGit(pPath);
132 m_bDirectoryKnown = true;
133 m_bIsDirectory = bIsDirectory;
136 void CTGitPath::SetFromGit(const TCHAR* pPath, bool bIsDirectory)
138 Reset();
139 if (pPath)
141 m_sFwdslashPath = pPath;
142 SanitizeRootPath(m_sFwdslashPath, true);
144 m_bDirectoryKnown = true;
145 m_bIsDirectory = bIsDirectory;
148 void CTGitPath::SetFromGit(const CString& sPath,CString *oldpath)
150 Reset();
151 m_sFwdslashPath = sPath;
152 SanitizeRootPath(m_sFwdslashPath, true);
153 if(oldpath)
154 m_sOldFwdslashPath = *oldpath;
157 void CTGitPath::SetFromWin(LPCTSTR pPath)
159 Reset();
160 m_sBackslashPath = pPath;
161 m_sBackslashPath.Replace(L"\\\\?\\", L"");
162 SanitizeRootPath(m_sBackslashPath, false);
163 ATLASSERT(m_sBackslashPath.Find('/')<0);
165 void CTGitPath::SetFromWin(const CString& sPath)
167 Reset();
168 m_sBackslashPath = sPath;
169 m_sBackslashPath.Replace(L"\\\\?\\", L"");
170 SanitizeRootPath(m_sBackslashPath, false);
172 void CTGitPath::SetFromWin(LPCTSTR pPath, bool bIsDirectory)
174 Reset();
175 m_sBackslashPath = pPath;
176 m_bIsDirectory = bIsDirectory;
177 m_bDirectoryKnown = true;
178 SanitizeRootPath(m_sBackslashPath, false);
180 void CTGitPath::SetFromWin(const CString& sPath, bool bIsDirectory)
182 Reset();
183 m_sBackslashPath = sPath;
184 m_bIsDirectory = bIsDirectory;
185 m_bDirectoryKnown = true;
186 SanitizeRootPath(m_sBackslashPath, false);
188 void CTGitPath::SetFromUnknown(const CString& sPath)
190 Reset();
191 // Just set whichever path we think is most likely to be used
192 // GitAdminDir admin;
193 // CString p;
194 // if(admin.HasAdminDir(sPath,&p))
195 // SetFwdslashPath(sPath.Right(sPath.GetLength()-p.GetLength()));
196 // else
197 SetFwdslashPath(sPath);
200 LPCTSTR CTGitPath::GetWinPath() const
202 if(IsEmpty())
203 return _T("");
204 if(m_sBackslashPath.IsEmpty())
205 SetBackslashPath(m_sFwdslashPath);
206 return m_sBackslashPath;
208 // This is a temporary function, to be used during the migration to
209 // the path class. Ultimately, functions consuming paths should take a CTGitPath&, not a CString
210 const CString& CTGitPath::GetWinPathString() const
212 if(m_sBackslashPath.IsEmpty())
213 SetBackslashPath(m_sFwdslashPath);
214 return m_sBackslashPath;
217 const CString& CTGitPath::GetGitPathString() const
219 if(m_sFwdslashPath.IsEmpty())
220 SetFwdslashPath(m_sBackslashPath);
221 return m_sFwdslashPath;
224 const CString &CTGitPath::GetGitOldPathString() const
226 return m_sOldFwdslashPath;
229 const CString& CTGitPath::GetUIPathString() const
231 if (m_sUIPath.IsEmpty())
232 m_sUIPath = GetWinPathString();
233 return m_sUIPath;
236 void CTGitPath::SetFwdslashPath(const CString& sPath) const
238 CString path = sPath;
239 path.Replace('\\', '/');
241 // We don't leave a trailing /
242 path.TrimRight('/');
243 path.Replace(L"//?/", L"");
245 SanitizeRootPath(path, true);
247 path.Replace(_T("file:////"), _T("file://"));
248 m_sFwdslashPath = path;
250 m_sUTF8FwdslashPath.Empty();
253 void CTGitPath::SetBackslashPath(const CString& sPath) const
255 CString path = sPath;
256 path.Replace('/', '\\');
257 path.TrimRight('\\');
258 SanitizeRootPath(path, false);
259 m_sBackslashPath = path;
262 void CTGitPath::SetUTF8FwdslashPath(const CString& sPath) const
264 m_sUTF8FwdslashPath = CUnicodeUtils::GetUTF8(sPath);
267 void CTGitPath::SanitizeRootPath(CString& sPath, bool bIsForwardPath) const
269 // Make sure to add the trailing slash to root paths such as 'C:'
270 if (sPath.GetLength() == 2 && sPath[1] == ':')
271 sPath += (bIsForwardPath) ? _T("/") : _T("\\");
274 bool CTGitPath::IsDirectory() const
276 if(!m_bDirectoryKnown)
277 UpdateAttributes();
278 return m_bIsDirectory;
281 bool CTGitPath::Exists() const
283 if (!m_bExistsKnown)
284 UpdateAttributes();
285 return m_bExists;
288 bool CTGitPath::Delete(bool bTrash, bool bShowErrorUI) const
290 EnsureBackslashPathSet();
291 ::SetFileAttributes(m_sBackslashPath, FILE_ATTRIBUTE_NORMAL);
292 bool bRet = false;
293 if (Exists())
295 if ((bTrash)||(IsDirectory()))
297 auto buf = std::make_unique<TCHAR[]>(m_sBackslashPath.GetLength() + 2);
298 _tcscpy_s(buf.get(), m_sBackslashPath.GetLength() + 2, m_sBackslashPath);
299 buf[m_sBackslashPath.GetLength()] = 0;
300 buf[m_sBackslashPath.GetLength()+1] = 0;
301 bRet = CTGitPathList::DeleteViaShell(buf.get(), bTrash, bShowErrorUI);
303 else
304 bRet = !!::DeleteFile(m_sBackslashPath);
306 m_bExists = false;
307 m_bExistsKnown = true;
308 return bRet;
311 __int64 CTGitPath::GetLastWriteTime() const
313 if(!m_bLastWriteTimeKnown)
314 UpdateAttributes();
315 return m_lastWriteTime;
318 __int64 CTGitPath::GetFileSize() const
320 if(!m_bDirectoryKnown)
321 UpdateAttributes();
322 return m_fileSize;
325 bool CTGitPath::IsReadOnly() const
327 if(!m_bLastWriteTimeKnown)
328 UpdateAttributes();
329 return m_bIsReadOnly;
332 void CTGitPath::UpdateAttributes() const
334 EnsureBackslashPathSet();
335 WIN32_FILE_ATTRIBUTE_DATA attribs;
336 if (m_sBackslashPath.GetLength() >= 248)
337 m_sLongBackslashPath = _T("\\\\?\\") + m_sBackslashPath;
338 if(GetFileAttributesEx(m_sBackslashPath.GetLength() >= 248 ? m_sLongBackslashPath : m_sBackslashPath, GetFileExInfoStandard, &attribs))
340 m_bIsDirectory = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
341 // don't cast directly to an __int64:
342 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284%28v=vs.85%29.aspx
343 // "Do not cast a pointer to a FILETIME structure to either a ULARGE_INTEGER* or __int64* value
344 // because it can cause alignment faults on 64-bit Windows."
345 m_lastWriteTime = static_cast<__int64>(attribs.ftLastWriteTime.dwHighDateTime) << 32 | attribs.ftLastWriteTime.dwLowDateTime;
346 if (m_bIsDirectory)
347 m_fileSize = 0;
348 else
349 m_fileSize = ((INT64)( (DWORD)(attribs.nFileSizeLow) ) | ( (INT64)( (DWORD)(attribs.nFileSizeHigh) )<<32 ));
350 m_bIsReadOnly = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
351 m_bExists = true;
353 else
355 m_bIsDirectory = false;
356 m_lastWriteTime = 0;
357 m_fileSize = 0;
358 DWORD err = GetLastError();
359 if ((err == ERROR_FILE_NOT_FOUND)||(err == ERROR_PATH_NOT_FOUND)||(err == ERROR_INVALID_NAME))
360 m_bExists = false;
361 else
363 m_bExists = true;
364 return;
367 m_bDirectoryKnown = true;
368 m_bLastWriteTimeKnown = true;
369 m_bExistsKnown = true;
372 CTGitPath CTGitPath::GetSubPath(const CTGitPath &root)
374 CTGitPath path;
376 if(GetWinPathString().Left(root.GetWinPathString().GetLength()) == root.GetWinPathString())
378 CString str=GetWinPathString();
379 path.SetFromWin(str.Right(str.GetLength()-root.GetWinPathString().GetLength()-1));
381 return path;
384 void CTGitPath::EnsureBackslashPathSet() const
386 if(m_sBackslashPath.IsEmpty())
388 SetBackslashPath(m_sFwdslashPath);
389 ATLASSERT(IsEmpty() || !m_sBackslashPath.IsEmpty());
392 void CTGitPath::EnsureFwdslashPathSet() const
394 if(m_sFwdslashPath.IsEmpty())
396 SetFwdslashPath(m_sBackslashPath);
397 ATLASSERT(IsEmpty() || !m_sFwdslashPath.IsEmpty());
402 // Reset all the caches
403 void CTGitPath::Reset()
405 m_bDirectoryKnown = false;
406 m_bURLKnown = false;
407 m_bLastWriteTimeKnown = false;
408 m_bHasAdminDirKnown = false;
409 m_bIsValidOnWindowsKnown = false;
410 m_bIsAdminDirKnown = false;
411 m_bExistsKnown = false;
412 m_bIsSpecialDirectoryKnown = false;
413 m_bIsSpecialDirectory = false;
415 m_sBackslashPath.Empty();
416 m_sFwdslashPath.Empty();
417 m_sUTF8FwdslashPath.Empty();
418 this->m_Action=0;
419 this->m_StatAdd.Empty();
420 this->m_StatDel.Empty();
421 m_ParentNo=0;
422 ATLASSERT(IsEmpty());
425 CTGitPath CTGitPath::GetDirectory() const
427 if ((IsDirectory())||(!Exists()))
428 return *this;
429 return GetContainingDirectory();
432 CTGitPath CTGitPath::GetContainingDirectory() const
434 EnsureBackslashPathSet();
436 CString sDirName = m_sBackslashPath.Left(m_sBackslashPath.ReverseFind('\\'));
437 if(sDirName.GetLength() == 2 && sDirName[1] == ':')
439 // This is a root directory, which needs a trailing slash
440 sDirName += '\\';
441 if(sDirName == m_sBackslashPath)
443 // We were clearly provided with a root path to start with - we should return nothing now
444 sDirName.Empty();
447 if(sDirName.GetLength() == 1 && sDirName[0] == '\\')
449 // We have an UNC path and we already are the root
450 sDirName.Empty();
452 CTGitPath retVal;
453 retVal.SetFromWin(sDirName);
454 return retVal;
457 CString CTGitPath::GetRootPathString() const
459 EnsureBackslashPathSet();
460 CString workingPath = m_sBackslashPath;
461 LPTSTR pPath = workingPath.GetBuffer(MAX_PATH); // MAX_PATH ok here.
462 ATLVERIFY(::PathStripToRoot(pPath));
463 workingPath.ReleaseBuffer();
464 return workingPath;
468 CString CTGitPath::GetFilename() const
470 //ATLASSERT(!IsDirectory());
471 return GetFileOrDirectoryName();
474 CString CTGitPath::GetFileOrDirectoryName() const
476 EnsureBackslashPathSet();
477 return m_sBackslashPath.Mid(m_sBackslashPath.ReverseFind('\\')+1);
480 CString CTGitPath::GetUIFileOrDirectoryName() const
482 GetUIPathString();
483 return m_sUIPath.Mid(m_sUIPath.ReverseFind('\\')+1);
486 CString CTGitPath::GetFileExtension() const
488 if(!IsDirectory())
490 EnsureBackslashPathSet();
491 int dotPos = m_sBackslashPath.ReverseFind('.');
492 int slashPos = m_sBackslashPath.ReverseFind('\\');
493 if (dotPos > slashPos)
494 return m_sBackslashPath.Mid(dotPos);
496 return CString();
498 CString CTGitPath::GetBaseFilename() const
500 int dot;
501 CString filename=GetFilename();
502 dot = filename.ReverseFind(_T('.'));
503 if(dot>0)
504 return filename.Left(dot);
505 else
506 return filename;
509 bool CTGitPath::ArePathStringsEqual(const CString& sP1, const CString& sP2)
511 int length = sP1.GetLength();
512 if(length != sP2.GetLength())
514 // Different lengths
515 return false;
517 // We work from the end of the strings, because path differences
518 // are more likely to occur at the far end of a string
519 LPCTSTR pP1Start = sP1;
520 LPCTSTR pP1 = pP1Start+(length-1);
521 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
522 while(length-- > 0)
524 if(_totlower(*pP1--) != _totlower(*pP2--))
525 return false;
527 return true;
530 bool CTGitPath::ArePathStringsEqualWithCase(const CString& sP1, const CString& sP2)
532 int length = sP1.GetLength();
533 if(length != sP2.GetLength())
535 // Different lengths
536 return false;
538 // We work from the end of the strings, because path differences
539 // are more likely to occur at the far end of a string
540 LPCTSTR pP1Start = sP1;
541 LPCTSTR pP1 = pP1Start+(length-1);
542 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
543 while(length-- > 0)
545 if((*pP1--) != (*pP2--))
546 return false;
548 return true;
551 bool CTGitPath::IsEmpty() const
553 // Check the backward slash path first, since the chance that this
554 // one is set is higher. In case of a 'false' return value it's a little
555 // bit faster.
556 return m_sBackslashPath.IsEmpty() && m_sFwdslashPath.IsEmpty();
559 // Test if both paths refer to the same item
560 // Ignores case and slash direction
561 bool CTGitPath::IsEquivalentTo(const CTGitPath& rhs) const
563 // Try and find a slash direction which avoids having to convert
564 // both filenames
565 if(!m_sBackslashPath.IsEmpty())
567 // *We've* got a \ path - make sure that the RHS also has a \ path
568 rhs.EnsureBackslashPathSet();
569 return ArePathStringsEqualWithCase(m_sBackslashPath, rhs.m_sBackslashPath);
571 else
573 // Assume we've got a fwdslash path and make sure that the RHS has one
574 rhs.EnsureFwdslashPathSet();
575 return ArePathStringsEqualWithCase(m_sFwdslashPath, rhs.m_sFwdslashPath);
579 bool CTGitPath::IsEquivalentToWithoutCase(const CTGitPath& rhs) const
581 // Try and find a slash direction which avoids having to convert
582 // both filenames
583 if(!m_sBackslashPath.IsEmpty())
585 // *We've* got a \ path - make sure that the RHS also has a \ path
586 rhs.EnsureBackslashPathSet();
587 return ArePathStringsEqual(m_sBackslashPath, rhs.m_sBackslashPath);
589 else
591 // Assume we've got a fwdslash path and make sure that the RHS has one
592 rhs.EnsureFwdslashPathSet();
593 return ArePathStringsEqual(m_sFwdslashPath, rhs.m_sFwdslashPath);
597 bool CTGitPath::IsAncestorOf(const CTGitPath& possibleDescendant) const
599 possibleDescendant.EnsureBackslashPathSet();
600 EnsureBackslashPathSet();
602 bool bPathStringsEqual = ArePathStringsEqual(m_sBackslashPath, possibleDescendant.m_sBackslashPath.Left(m_sBackslashPath.GetLength()));
603 if (m_sBackslashPath.GetLength() >= possibleDescendant.GetWinPathString().GetLength())
605 return bPathStringsEqual;
608 return (bPathStringsEqual &&
609 ((possibleDescendant.m_sBackslashPath[m_sBackslashPath.GetLength()] == '\\')||
610 (m_sBackslashPath.GetLength()==3 && m_sBackslashPath[1]==':')));
613 // Get a string representing the file path, optionally with a base
614 // section stripped off the front.
615 CString CTGitPath::GetDisplayString(const CTGitPath* pOptionalBasePath /* = nullptr*/) const
617 EnsureFwdslashPathSet();
618 if (pOptionalBasePath)
620 // Find the length of the base-path without having to do an 'ensure' on it
621 int baseLength = max(pOptionalBasePath->m_sBackslashPath.GetLength(), pOptionalBasePath->m_sFwdslashPath.GetLength());
623 // Now, chop that baseLength of the front of the path
624 return m_sFwdslashPath.Mid(baseLength).TrimLeft('/');
626 return m_sFwdslashPath;
629 int CTGitPath::Compare(const CTGitPath& left, const CTGitPath& right)
631 left.EnsureBackslashPathSet();
632 right.EnsureBackslashPathSet();
633 return left.m_sBackslashPath.CompareNoCase(right.m_sBackslashPath);
636 bool operator<(const CTGitPath& left, const CTGitPath& right)
638 return CTGitPath::Compare(left, right) < 0;
641 bool CTGitPath::PredLeftEquivalentToRight(const CTGitPath& left, const CTGitPath& right)
643 return left.IsEquivalentTo(right);
646 bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right)
648 if (left.IsAdminDir() && right.IsAdminDir())
650 CTGitPath l = left;
651 CTGitPath r = right;
654 l = l.GetContainingDirectory();
655 } while(l.HasAdminDir());
658 r = r.GetContainingDirectory();
659 } while(r.HasAdminDir());
660 return l.GetContainingDirectory().IsEquivalentTo(r.GetContainingDirectory());
662 return left.GetDirectory().IsEquivalentTo(right.GetDirectory());
665 bool CTGitPath::CheckChild(const CTGitPath &parent, const CTGitPath& child)
667 return parent.IsAncestorOf(child);
670 void CTGitPath::AppendRawString(const CString& sAppend)
672 EnsureFwdslashPathSet();
673 CString strCopy = m_sFwdslashPath += sAppend;
674 SetFromUnknown(strCopy);
677 void CTGitPath::AppendPathString(const CString& sAppend)
679 EnsureBackslashPathSet();
680 CString cleanAppend(sAppend);
681 cleanAppend.Replace('/', '\\');
682 cleanAppend.TrimLeft('\\');
683 m_sBackslashPath.TrimRight('\\');
684 CString strCopy = m_sBackslashPath;
685 strCopy += _T('\\');
686 strCopy += cleanAppend;
687 SetFromWin(strCopy);
690 bool CTGitPath::IsWCRoot() const
692 if (m_bIsWCRootKnown)
693 return m_bIsWCRoot;
695 m_bIsWCRootKnown = true;
696 m_bIsWCRoot = false;
698 CString topDirectory;
699 if (!IsDirectory() || !HasAdminDir(&topDirectory))
700 return m_bIsWCRoot;
702 if (IsEquivalentToWithoutCase(topDirectory))
703 m_bIsWCRoot = true;
705 return m_bIsWCRoot;
708 bool CTGitPath::HasAdminDir() const
710 if (m_bHasAdminDirKnown)
711 return m_bHasAdminDir;
713 EnsureBackslashPathSet();
714 m_bHasAdminDir = GitAdminDir::HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
715 m_bHasAdminDirKnown = true;
716 return m_bHasAdminDir;
719 bool CTGitPath::HasSubmodules() const
721 if (HasAdminDir())
723 CString path = m_sProjectRoot;
724 path += _T("\\.gitmodules");
725 if( PathFileExists(path) )
726 return true;
728 return false;
731 int CTGitPath::GetAdminDirMask() const
733 int status = 0;
734 CString topdir;
735 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
736 return status;
738 // ITEMIS_INGIT will be revoked if necessary in TortoiseShell/ContextMenu.cpp
739 status |= ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER;
741 if (IsDirectory())
743 status |= ITEMIS_FOLDERINGIT;
744 if (IsWCRoot())
746 status |= ITEMIS_WCROOT;
748 CString topProjectDir;
749 if (GitAdminDir::HasAdminDir(GetWinPathString(), false, &topProjectDir))
751 if (PathFileExists(topProjectDir + _T("\\.gitmodules")))
753 CAutoConfig config(true);
754 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(topProjectDir + _T("\\.gitmodules")), GIT_CONFIG_LEVEL_APP, FALSE);
755 CString relativePath = GetWinPathString().Mid(topProjectDir.GetLength());
756 relativePath.Replace(_T("\\"), _T("/"));
757 relativePath.Trim(_T("/"));
758 CStringA submodulePath = CUnicodeUtils::GetUTF8(relativePath);
759 if (git_config_foreach_match(config, "submodule\\..*\\.path",
760 [](const git_config_entry *entry, void *data) { return entry->value == *(CStringA *)data ? GIT_EUSER : 0; }, &submodulePath) == GIT_EUSER)
761 status |= ITEMIS_SUBMODULE;
767 CString dotGitPath;
768 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
770 if (PathFileExists(dotGitPath + _T("BISECT_START")))
771 status |= ITEMIS_BISECT;
773 if (PathFileExists(dotGitPath + _T("MERGE_HEAD")))
774 status |= ITEMIS_MERGEACTIVE;
776 if (HasStashDir(dotGitPath))
777 status |= ITEMIS_STASH;
779 if (PathFileExists(dotGitPath + _T("svn")))
780 status |= ITEMIS_GITSVN;
782 if (PathFileExists(topdir + _T("\\.gitmodules")))
783 status |= ITEMIS_SUBMODULECONTAINER;
785 return status;
788 bool CTGitPath::HasStashDir(const CString& dotGitPath) const
790 if (PathFileExists(dotGitPath + L"refs\\stash"))
791 return true;
793 CAutoFile hfile = CreateFile(dotGitPath + L"packed-refs", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
794 if (!hfile)
795 return false;
797 DWORD filesize = ::GetFileSize(hfile, nullptr);
798 if (filesize == 0)
799 return false;
801 DWORD size = 0;
802 auto buff = std::make_unique<char[]>(filesize + 1);
803 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
804 buff.get()[filesize] = '\0';
806 if (size != filesize)
807 return false;
809 for (DWORD i = 0; i < filesize;)
811 if (buff[i] == '#' || buff[i] == '^')
813 while (buff[i] != '\n')
815 ++i;
816 if (i == filesize)
817 break;
819 ++i;
822 if (i >= filesize)
823 break;
825 while (buff[i] != ' ')
827 ++i;
828 if (i == filesize)
829 break;
832 ++i;
833 if (i >= filesize)
834 break;
836 if (i <= filesize - 10 && (buff[i + 10] == '\n' || buff[i + 10] == '\0') && !strncmp("refs/stash", buff.get() + i, 10))
837 return true;
838 while (buff[i] != '\n')
840 ++i;
841 if (i == filesize)
842 break;
845 while (buff[i] == '\n')
847 ++i;
848 if (i == filesize)
849 break;
852 return false;
855 bool CTGitPath::HasStashDir() const
857 CString topdir;
858 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
859 return false;
861 CString dotGitPath;
862 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
864 return HasStashDir(dotGitPath);
867 bool CTGitPath::HasGitSVNDir() const
869 CString topdir;
870 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
871 return false;
873 CString dotGitPath;
874 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
876 return !!PathFileExists(dotGitPath + _T("svn"));
878 bool CTGitPath::IsBisectActive() const
880 CString topdir;
881 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
882 return false;
884 CString dotGitPath;
885 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
887 return !!PathFileExists(dotGitPath + _T("BISECT_START"));
889 bool CTGitPath::IsMergeActive() const
891 CString topdir;
892 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
893 return false;
895 CString dotGitPath;
896 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
898 return !!PathFileExists(dotGitPath + _T("MERGE_HEAD"));
900 bool CTGitPath::HasRebaseApply() const
902 CString topdir;
903 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
904 return false;
906 CString dotGitPath;
907 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
909 return !!PathFileExists(dotGitPath + _T("rebase-apply"));
912 bool CTGitPath::HasAdminDir(CString *ProjectTopDir) const
914 if (m_bHasAdminDirKnown)
916 if (ProjectTopDir)
917 *ProjectTopDir = m_sProjectRoot;
918 return m_bHasAdminDir;
921 EnsureBackslashPathSet();
922 m_bHasAdminDir = GitAdminDir::HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
923 m_bHasAdminDirKnown = true;
924 if (ProjectTopDir)
925 *ProjectTopDir = m_sProjectRoot;
926 return m_bHasAdminDir;
929 bool CTGitPath::IsAdminDir() const
931 if (m_bIsAdminDirKnown)
932 return m_bIsAdminDir;
934 EnsureBackslashPathSet();
935 m_bIsAdminDir = GitAdminDir::IsAdminDirPath(m_sBackslashPath);
936 m_bIsAdminDirKnown = true;
937 return m_bIsAdminDir;
940 bool CTGitPath::IsValidOnWindows() const
942 if (m_bIsValidOnWindowsKnown)
943 return m_bIsValidOnWindows;
945 m_bIsValidOnWindows = false;
946 EnsureBackslashPathSet();
947 CString sMatch = m_sBackslashPath + _T("\r\n");
948 std::wstring sPattern;
949 // the 'file://' URL is just a normal windows path:
950 if (sMatch.Left(7).CompareNoCase(_T("file:\\\\"))==0)
952 sMatch = sMatch.Mid(7);
953 sMatch.TrimLeft(_T("\\"));
954 sPattern = _T("^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$");
956 else
957 sPattern = _T("^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$");
961 std::tr1::wregex rx(sPattern, std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
962 std::tr1::wsmatch match;
964 std::wstring rmatch = std::wstring((LPCTSTR)sMatch);
965 if (std::tr1::regex_match(rmatch, match, rx))
967 if (std::wstring(match[0]).compare(sMatch)==0)
968 m_bIsValidOnWindows = true;
970 if (m_bIsValidOnWindows)
972 // now check for illegal filenames
973 std::tr1::wregex rx2(_T("\\\\(lpt\\d|com\\d|aux|nul|prn|con)(\\\\|$)"), std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
974 rmatch = m_sBackslashPath;
975 if (std::tr1::regex_search(rmatch, rx2, std::tr1::regex_constants::match_default))
976 m_bIsValidOnWindows = false;
979 catch (std::exception) {}
981 m_bIsValidOnWindowsKnown = true;
982 return m_bIsValidOnWindows;
985 //////////////////////////////////////////////////////////////////////////
987 CTGitPathList::CTGitPathList()
989 m_Action = 0;
992 // A constructor which allows a path list to be easily built which one initial entry in
993 CTGitPathList::CTGitPathList(const CTGitPath& firstEntry)
995 m_Action = 0;
996 AddPath(firstEntry);
998 int CTGitPathList::ParserFromLsFile(BYTE_VECTOR &out,bool /*staged*/)
1000 int pos=0;
1001 CString one;
1002 CTGitPath path;
1003 CString part;
1004 this->Clear();
1006 while (pos >= 0 && pos < (int)out.size())
1008 one.Empty();
1009 path.Reset();
1011 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1012 int tabstart=0;
1013 // m_Action is never used and propably never worked (needs to be set after path.SetFromGit)
1014 // also dropped LOGACTIONS_CACHE for 'H'
1015 // path.m_Action=path.ParserAction(out[pos]);
1016 one.Tokenize(_T("\t"),tabstart);
1018 if(tabstart>=0)
1019 path.SetFromGit(one.Right(one.GetLength()-tabstart));
1020 else
1021 return -1;
1023 tabstart=0;
1025 part=one.Tokenize(_T(" "),tabstart); //Tag
1026 if (tabstart < 0)
1027 return -1;
1029 part=one.Tokenize(_T(" "),tabstart); //Mode
1030 if (tabstart < 0)
1031 return -1;
1033 part=one.Tokenize(_T(" "),tabstart); //Hash
1034 if (tabstart < 0)
1035 return -1;
1037 part=one.Tokenize(_T("\t"),tabstart); //Stage
1038 if (tabstart < 0)
1039 return -1;
1041 path.m_Stage=_ttol(part);
1043 this->AddPath(path);
1045 pos=out.findNextString(pos);
1047 return 0;
1049 int CTGitPathList::FillUnRev(unsigned int action, CTGitPathList *list, CString *err)
1051 this->Clear();
1052 CTGitPath path;
1054 int count;
1055 if (!list)
1056 count=1;
1057 else
1058 count=list->GetCount();
1059 for (int i = 0; i < count; ++i)
1061 CString cmd;
1062 int pos = 0;
1064 CString ignored;
1065 if(action & CTGitPath::LOGACTIONS_IGNORE)
1066 ignored= _T(" -i");
1068 if (!list)
1070 cmd=_T("git.exe ls-files --exclude-standard --full-name --others -z");
1071 cmd+=ignored;
1074 else
1075 { cmd.Format(_T("git.exe ls-files --exclude-standard --full-name --others -z%s -- \"%s\""),
1076 (LPCTSTR)ignored,
1077 (*list)[i].GetWinPath());
1080 BYTE_VECTOR out, errb;
1081 out.clear();
1082 if (g_Git.Run(cmd, &out, &errb))
1084 if (err != nullptr)
1085 CGit::StringAppend(err, &errb[0], CP_UTF8, (int)errb.size());
1086 return -1;
1089 pos=0;
1090 CString one;
1091 while (pos >= 0 && pos < (int)out.size())
1093 one.Empty();
1094 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1095 if(!one.IsEmpty())
1097 //SetFromGit will clear all status
1098 path.SetFromGit(one);
1099 path.m_Action=action;
1100 AddPath(path);
1102 pos=out.findNextString(pos);
1106 return 0;
1108 int CTGitPathList::FillBasedOnIndexFlags(unsigned short flag, unsigned short flagextended, CTGitPathList* list /*nullptr*/)
1110 Clear();
1111 CTGitPath path;
1113 CAutoRepository repository(g_Git.GetGitRepository());
1114 if (!repository)
1115 return -1;
1117 CAutoIndex index;
1118 if (git_repository_index(index.GetPointer(), repository))
1119 return -1;
1121 int count;
1122 if (list == nullptr)
1123 count = 1;
1124 else
1125 count = list->GetCount();
1126 for (int j = 0; j < count; ++j)
1128 for (size_t i = 0, ecount = git_index_entrycount(index); i < ecount; ++i)
1130 const git_index_entry *e = git_index_get_byindex(index, i);
1132 if (!e || !((e->flags & flag) || (e->flags_extended & flagextended)) || !e->path)
1133 continue;
1135 CString one = CUnicodeUtils::GetUnicode(e->path);
1137 if (!(!list || (*list)[j].GetWinPathString().IsEmpty() || one == (*list)[j].GetGitPathString() || (PathIsDirectory(g_Git.CombinePath((*list)[j].GetWinPathString())) && one.Find((*list)[j].GetGitPathString() + _T("/")) == 0)))
1138 continue;
1140 //SetFromGit will clear all status
1141 path.SetFromGit(one);
1142 if (e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE)
1143 path.m_Action = CTGitPath::LOGACTIONS_SKIPWORKTREE;
1144 else if (e->flags & GIT_IDXENTRY_VALID)
1145 path.m_Action = CTGitPath::LOGACTIONS_ASSUMEVALID;
1146 AddPath(path);
1149 RemoveDuplicates();
1150 return 0;
1152 int CTGitPathList::ParserFromLog(BYTE_VECTOR &log, bool parseDeletes /*false*/)
1154 this->Clear();
1155 std::map<CString, size_t> duplicateMap;
1156 int pos=0;
1157 CTGitPath path;
1158 m_Action=0;
1159 int logend = (int)log.size();
1160 while (pos >= 0 && pos < logend)
1162 path.Reset();
1163 if(log[pos]=='\n')
1164 ++pos;
1166 if (pos >= logend)
1167 return -1;
1169 if(log[pos]==':')
1171 bool merged=false;
1172 if (pos + 1 >= logend)
1173 return -1;
1174 if(log[pos+1] ==':')
1175 merged=true;
1176 int end=log.find(0,pos);
1177 int actionstart=-1;
1178 int file1=-1,file2=-1;
1179 if( end>0 )
1181 actionstart=log.find(' ',end-6);
1182 pos=actionstart;
1184 if( actionstart>0 )
1186 ++actionstart;
1187 if (actionstart >= logend)
1188 return -1;
1190 file1 = log.find(0,actionstart);
1191 if( file1>=0 )
1193 ++file1;
1194 pos=file1;
1196 if( log[actionstart] == 'C' || log[actionstart] == 'R' )
1198 file2=file1;
1199 file1 = log.find(0,file1);
1200 if(file1>=0 )
1202 ++file1;
1203 pos=file1;
1208 CString pathname1;
1209 CString pathname2;
1211 if( file1>=0 )
1212 CGit::StringAppend(&pathname1, &log[file1], CP_UTF8);
1213 if( file2>=0 )
1214 CGit::StringAppend(&pathname2, &log[file2], CP_UTF8);
1216 if (actionstart < 0)
1217 return -1;
1219 auto existing = duplicateMap.find(pathname1);
1220 if (existing != duplicateMap.end())
1222 CTGitPath& p = m_paths[existing->second];
1223 p.ParserAction(log[actionstart]);
1225 if(merged)
1226 p.m_Action |= CTGitPath::LOGACTIONS_MERGED;
1227 m_Action |= p.m_Action;
1229 else
1231 int ac=path.ParserAction(log[actionstart] );
1232 ac |= merged?CTGitPath::LOGACTIONS_MERGED:0;
1234 path.SetFromGit(pathname1,&pathname2);
1235 path.m_Action=ac;
1236 //action must be set after setfromgit. SetFromGit will clear all status.
1237 this->m_Action|=ac;
1239 AddPath(path);
1240 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1243 else
1245 int tabstart=0;
1246 path.Reset();
1247 CString StatAdd;
1248 CString StatDel;
1249 CString file1;
1250 CString file2;
1252 tabstart=log.find('\t',pos);
1253 if(tabstart >=0)
1255 log[tabstart]=0;
1256 CGit::StringAppend(&StatAdd, &log[pos], CP_UTF8);
1257 pos=tabstart+1;
1260 tabstart=log.find('\t',pos);
1261 if(tabstart >=0)
1263 log[tabstart]=0;
1265 CGit::StringAppend(&StatDel, &log[pos], CP_UTF8);
1266 pos=tabstart+1;
1269 if(log[pos] == 0) //rename
1271 ++pos;
1272 CGit::StringAppend(&file2, &log[pos], CP_UTF8);
1273 int sec=log.find(0,pos);
1274 if(sec>=0)
1276 ++sec;
1277 CGit::StringAppend(&file1, &log[sec], CP_UTF8);
1279 pos=sec;
1281 else
1283 CGit::StringAppend(&file1, &log[pos], CP_UTF8);
1285 path.SetFromGit(file1,&file2);
1287 auto existing = duplicateMap.find(path.GetGitPathString());
1288 if (existing != duplicateMap.end())
1290 CTGitPath& p = m_paths[existing->second];
1291 p.m_StatAdd = StatAdd;
1292 p.m_StatDel = StatDel;
1294 else
1296 //path.SetFromGit(pathname);
1297 if (parseDeletes)
1299 path.m_StatAdd=_T("0");
1300 path.m_StatDel=_T("0");
1301 path.m_Action |= CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_MISSING;
1303 else
1305 path.m_StatAdd=StatAdd;
1306 path.m_StatDel=StatDel;
1308 AddPath(path);
1309 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1312 pos=log.findNextString(pos);
1314 return 0;
1317 void CTGitPathList::AddPath(const CTGitPath& newPath)
1319 m_paths.push_back(newPath);
1320 m_commonBaseDirectory.Reset();
1322 int CTGitPathList::GetCount() const
1324 return (int)m_paths.size();
1326 bool CTGitPathList::IsEmpty() const
1328 return m_paths.empty();
1330 void CTGitPathList::Clear()
1332 m_paths.clear();
1333 m_commonBaseDirectory.Reset();
1336 const CTGitPath& CTGitPathList::operator[](INT_PTR index) const
1338 ATLASSERT(index >= 0 && index < (INT_PTR)m_paths.size());
1339 return m_paths[index];
1342 bool CTGitPathList::AreAllPathsFiles() const
1344 // Look through the vector for any directories - if we find them, return false
1345 return std::find_if(m_paths.cbegin(), m_paths.cend(), std::mem_fun_ref(&CTGitPath::IsDirectory)) == m_paths.end();
1348 #if defined(_MFC_VER)
1350 bool CTGitPathList::LoadFromFile(const CTGitPath& filename)
1352 Clear();
1355 CString strLine;
1356 CStdioFile file(filename.GetWinPath(), CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite);
1358 // for every selected file/folder
1359 CTGitPath path;
1360 while (file.ReadString(strLine))
1362 path.SetFromUnknown(strLine);
1363 AddPath(path);
1365 file.Close();
1367 catch (CFileException* pE)
1369 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException loading target file list\n");
1370 TCHAR error[10000] = {0};
1371 pE->GetErrorMessage(error, 10000);
1372 // CMessageBox::Show(nullptr, error, _T("TortoiseGit"), MB_ICONERROR);
1373 pE->Delete();
1374 return false;
1376 return true;
1379 bool CTGitPathList::WriteToFile(const CString& sFilename, bool bANSI /* = false */) const
1383 if (bANSI)
1385 CStdioFile file(sFilename, CFile::typeText | CFile::modeReadWrite | CFile::modeCreate);
1386 for (const auto& path : m_paths)
1388 CStringA line = CStringA(path.GetGitPathString()) + '\n';
1389 file.Write(line, line.GetLength());
1391 file.Close();
1393 else
1395 CStdioFile file(sFilename, CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
1396 for (const auto& path : m_paths)
1397 file.WriteString(path.GetGitPathString() + _T("\n"));
1398 file.Close();
1401 catch (CFileException* pE)
1403 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in writing temp file\n");
1404 pE->Delete();
1405 return false;
1407 return true;
1410 void CTGitPathList::LoadFromAsteriskSeparatedString(const CString& sPathString)
1412 int pos = 0;
1413 CString temp;
1414 for(;;)
1416 temp = sPathString.Tokenize(_T("*"),pos);
1417 if(temp.IsEmpty())
1418 break;
1419 AddPath(CTGitPath(CPathUtils::GetLongPathname(temp)));
1423 CString CTGitPathList::CreateAsteriskSeparatedString() const
1425 CString sRet;
1426 for (const auto& path : m_paths)
1428 if (!sRet.IsEmpty())
1429 sRet += _T("*");
1430 sRet += path.GetWinPathString();
1432 return sRet;
1434 #endif // _MFC_VER
1436 bool
1437 CTGitPathList::AreAllPathsFilesInOneDirectory() const
1439 // Check if all the paths are files and in the same directory
1440 m_commonBaseDirectory.Reset();
1441 for (const auto& path : m_paths)
1443 if (path.IsDirectory())
1444 return false;
1445 const CTGitPath& baseDirectory = path.GetDirectory();
1446 if(m_commonBaseDirectory.IsEmpty())
1447 m_commonBaseDirectory = baseDirectory;
1448 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1450 // Different path
1451 m_commonBaseDirectory.Reset();
1452 return false;
1455 return true;
1458 CTGitPath CTGitPathList::GetCommonDirectory() const
1460 if (m_commonBaseDirectory.IsEmpty())
1462 for (const auto& path : m_paths)
1464 const CTGitPath& baseDirectory = path.GetDirectory();
1465 if(m_commonBaseDirectory.IsEmpty())
1466 m_commonBaseDirectory = baseDirectory;
1467 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1469 // Different path
1470 m_commonBaseDirectory.Reset();
1471 break;
1475 // since we only checked strings, not paths,
1476 // we have to make sure now that we really return a *path* here
1477 for (const auto& path : m_paths)
1479 if (!m_commonBaseDirectory.IsAncestorOf(path))
1481 m_commonBaseDirectory = m_commonBaseDirectory.GetContainingDirectory();
1482 break;
1485 return m_commonBaseDirectory;
1488 CTGitPath CTGitPathList::GetCommonRoot() const
1490 if (IsEmpty())
1491 return CTGitPath();
1493 if (GetCount() == 1)
1494 return m_paths[0];
1496 // first entry is common root for itself
1497 // (add trailing '\\' to detect partial matches of the last path element)
1498 CString root = m_paths[0].GetWinPathString() + _T('\\');
1499 int rootLength = root.GetLength();
1501 // determine common path string prefix
1502 for (auto it = m_paths.cbegin() + 1; it != m_paths.cend(); ++it)
1504 CString path = it->GetWinPathString() + _T('\\');
1506 int newLength = CStringUtils::GetMatchingLength(root, path);
1507 if (newLength != rootLength)
1509 root.Delete(newLength, rootLength);
1510 rootLength = newLength;
1514 // remove the last (partial) path element
1515 if (rootLength > 0)
1516 root.Delete(root.ReverseFind(_T('\\')), rootLength);
1518 // done
1519 return CTGitPath(root);
1522 void CTGitPathList::SortByPathname(bool bReverse /*= false*/)
1524 std::sort(m_paths.begin(), m_paths.end());
1525 if (bReverse)
1526 std::reverse(m_paths.begin(), m_paths.end());
1529 void CTGitPathList::DeleteAllFiles(bool bTrash, bool bFilesOnly, bool bShowErrorUI)
1531 if (m_paths.empty())
1532 return;
1533 PathVector::const_iterator it;
1534 SortByPathname(true); // nested ones first
1536 CString sPaths;
1537 for (it = m_paths.cbegin(); it != m_paths.cend(); ++it)
1539 if ((it->Exists()) && ((it->IsDirectory() != bFilesOnly) || !bFilesOnly))
1541 if (!it->IsDirectory())
1542 ::SetFileAttributes(it->GetWinPath(), FILE_ATTRIBUTE_NORMAL);
1544 sPaths += it->GetWinPath();
1545 sPaths += '\0';
1548 if (sPaths.IsEmpty())
1549 return;
1550 sPaths += '\0';
1551 sPaths += '\0';
1552 DeleteViaShell((LPCTSTR)sPaths, bTrash, bShowErrorUI);
1553 Clear();
1556 bool CTGitPathList::DeleteViaShell(LPCTSTR path, bool bTrash, bool bShowErrorUI)
1558 SHFILEOPSTRUCT shop = {0};
1559 shop.wFunc = FO_DELETE;
1560 shop.pFrom = path;
1561 shop.fFlags = FOF_NOCONFIRMATION|FOF_NO_CONNECTED_ELEMENTS;
1562 if (!bShowErrorUI)
1563 shop.fFlags |= FOF_NOERRORUI | FOF_SILENT;
1564 if (bTrash)
1565 shop.fFlags |= FOF_ALLOWUNDO;
1566 const bool bRet = (SHFileOperation(&shop) == 0);
1567 return bRet;
1570 void CTGitPathList::RemoveDuplicates()
1572 SortByPathname();
1573 // Remove the duplicates
1574 // (Unique moves them to the end of the vector, then erase chops them off)
1575 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::PredLeftEquivalentToRight), m_paths.end());
1578 void CTGitPathList::RemoveAdminPaths()
1580 PathVector::iterator it;
1581 for(it = m_paths.begin(); it != m_paths.end(); )
1583 if (it->IsAdminDir())
1585 m_paths.erase(it);
1586 it = m_paths.begin();
1588 else
1589 ++it;
1593 void CTGitPathList::RemovePath(const CTGitPath& path)
1595 PathVector::iterator it;
1596 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1598 if (it->IsEquivalentTo(path))
1600 m_paths.erase(it);
1601 return;
1606 void CTGitPathList::RemoveItem(CTGitPath & path)
1608 PathVector::iterator it;
1609 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1611 if (it->GetGitPathString()==path.GetGitPathString())
1613 m_paths.erase(it);
1614 return;
1618 void CTGitPathList::RemoveChildren()
1620 SortByPathname();
1621 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::CheckChild), m_paths.end());
1624 bool CTGitPathList::IsEqual(const CTGitPathList& list)
1626 if (list.GetCount() != GetCount())
1627 return false;
1628 for (int i=0; i<list.GetCount(); ++i)
1630 if (!list[i].IsEquivalentTo(m_paths[i]))
1631 return false;
1633 return true;
1636 const CTGitPath* CTGitPathList::LookForGitPath(const CString& path)
1638 int i=0;
1639 for (i = 0; i < this->GetCount(); ++i)
1641 if((*this)[i].GetGitPathString() == path )
1642 return (CTGitPath*)&(*this)[i];
1644 return nullptr;
1647 CString CTGitPath::GetActionName(int action)
1649 if(action & CTGitPath::LOGACTIONS_UNMERGED)
1650 return MAKEINTRESOURCE(IDS_PATHACTIONS_CONFLICT);
1651 if(action & CTGitPath::LOGACTIONS_ADDED)
1652 return MAKEINTRESOURCE(IDS_PATHACTIONS_ADD);
1653 if (action & CTGitPath::LOGACTIONS_MISSING)
1654 return MAKEINTRESOURCE(IDS_PATHACTIONS_MISSING);
1655 if(action & CTGitPath::LOGACTIONS_DELETED)
1656 return MAKEINTRESOURCE(IDS_PATHACTIONS_DELETE);
1657 if(action & CTGitPath::LOGACTIONS_MERGED )
1658 return MAKEINTRESOURCE(IDS_PATHACTIONS_MERGED);
1660 if(action & CTGitPath::LOGACTIONS_MODIFIED)
1661 return MAKEINTRESOURCE(IDS_PATHACTIONS_MODIFIED);
1662 if(action & CTGitPath::LOGACTIONS_REPLACED)
1663 return MAKEINTRESOURCE(IDS_PATHACTIONS_RENAME);
1664 if(action & CTGitPath::LOGACTIONS_COPY)
1665 return MAKEINTRESOURCE(IDS_PATHACTIONS_COPY);
1667 if (action & CTGitPath::LOGACTIONS_ASSUMEVALID)
1668 return MAKEINTRESOURCE(IDS_PATHACTIONS_ASSUMEUNCHANGED);
1669 if (action & CTGitPath::LOGACTIONS_SKIPWORKTREE)
1670 return MAKEINTRESOURCE(IDS_PATHACTIONS_SKIPWORKTREE);
1672 if (action & CTGitPath::LOGACTIONS_IGNORE)
1673 return MAKEINTRESOURCE(IDS_PATHACTIONS_IGNORED);
1675 return MAKEINTRESOURCE(IDS_PATHACTIONS_UNKNOWN);
1677 CString CTGitPath::GetActionName()
1679 return GetActionName(m_Action);
1682 int CTGitPathList::GetAction()
1684 return m_Action;
1687 CString CTGitPath::GetAbbreviatedRename()
1689 if (GetGitOldPathString().IsEmpty())
1690 return GetFileOrDirectoryName();
1692 // Find common prefix which ends with a slash
1693 int prefix_length = 0;
1694 for (int i = 0, maxLength = min(m_sOldFwdslashPath.GetLength(), m_sFwdslashPath.GetLength()); i < maxLength; ++i)
1696 if (m_sOldFwdslashPath[i] != m_sFwdslashPath[i])
1697 break;
1698 if (m_sOldFwdslashPath[i] == L'/')
1699 prefix_length = i + 1;
1702 LPCTSTR oldName = (LPCTSTR)m_sOldFwdslashPath + m_sOldFwdslashPath.GetLength();
1703 LPCTSTR newName = (LPCTSTR)m_sFwdslashPath + m_sFwdslashPath.GetLength();
1705 int suffix_length = 0;
1706 int prefix_adjust_for_slash = (prefix_length ? 1 : 0);
1707 while ((LPCTSTR)m_sOldFwdslashPath + prefix_length - prefix_adjust_for_slash <= oldName &&
1708 (LPCTSTR)m_sFwdslashPath + prefix_length - prefix_adjust_for_slash <= newName &&
1709 *oldName == *newName)
1711 if (*oldName == L'/')
1712 suffix_length = m_sOldFwdslashPath.GetLength() - (int)(oldName - (LPCTSTR)m_sOldFwdslashPath);
1713 --oldName;
1714 --newName;
1718 * pfx{old_midlen => new_midlen}sfx
1719 * {pfx-old => pfx-new}sfx
1720 * pfx{sfx-old => sfx-new}
1721 * name-old => name-new
1723 int old_midlen = m_sOldFwdslashPath.GetLength() - prefix_length - suffix_length;
1724 int new_midlen = m_sFwdslashPath.GetLength() - prefix_length - suffix_length;
1725 if (old_midlen < 0)
1726 old_midlen = 0;
1727 if (new_midlen < 0)
1728 new_midlen = 0;
1730 CString ret;
1731 if (prefix_length + suffix_length)
1733 ret = m_sOldFwdslashPath.Left(prefix_length);
1734 ret += L'{';
1736 ret += m_sOldFwdslashPath.Mid(prefix_length, old_midlen);
1737 ret += L" => ";
1738 ret += m_sFwdslashPath.Mid(prefix_length, new_midlen);
1739 if (prefix_length + suffix_length)
1741 ret += L'}';
1742 ret += m_sFwdslashPath.Mid(m_sFwdslashPath.GetLength() - suffix_length, suffix_length);
1744 return ret;