Correctly display "Ignored" status on Check for Modifications Dialog
[TortoiseGit.git] / src / Git / TGitPath.cpp
blob8d92a713145d0a75db3c86b20b9a8a995e0822cf
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2015 - 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 "../Resources/LoglistCommonResource.h"
30 #include <memory>
32 extern CGit g_Git;
34 CTGitPath::CTGitPath(void)
35 : m_bDirectoryKnown(false)
36 , m_bIsDirectory(false)
37 , m_bURLKnown(false)
38 , m_bHasAdminDirKnown(false)
39 , m_bHasAdminDir(false)
40 , m_bIsValidOnWindowsKnown(false)
41 , m_bIsValidOnWindows(false)
42 , m_bIsReadOnly(false)
43 , m_bIsAdminDirKnown(false)
44 , m_bIsAdminDir(false)
45 , m_bExists(false)
46 , m_bExistsKnown(false)
47 , m_bLastWriteTimeKnown(0)
48 , m_lastWriteTime(0)
49 , m_customData(NULL)
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) :
67 m_bDirectoryKnown(false)
68 , m_bIsDirectory(false)
69 , m_bURLKnown(false)
70 , m_bHasAdminDirKnown(false)
71 , m_bHasAdminDir(false)
72 , m_bIsValidOnWindowsKnown(false)
73 , m_bIsValidOnWindows(false)
74 , m_bIsReadOnly(false)
75 , m_bIsAdminDirKnown(false)
76 , m_bIsAdminDir(false)
77 , m_bExists(false)
78 , m_bExistsKnown(false)
79 , m_bLastWriteTimeKnown(0)
80 , m_lastWriteTime(0)
81 , m_customData(NULL)
82 , m_bIsSpecialDirectoryKnown(false)
83 , m_bIsSpecialDirectory(false)
84 , m_bIsWCRootKnown(false)
85 , m_bIsWCRoot(false)
86 , m_fileSize(0)
87 , m_Checked(false)
89 SetFromUnknown(sUnknownPath);
90 m_Action=0;
91 m_Stage=0;
92 m_ParentNo=0;
95 int CTGitPath::ParserAction(BYTE action)
97 //action=action.TrimLeft();
98 //TCHAR c=action.GetAt(0);
99 if(action == 'M')
100 m_Action|= LOGACTIONS_MODIFIED;
101 if(action == 'R')
102 m_Action|= LOGACTIONS_REPLACED;
103 if(action == 'A')
104 m_Action|= LOGACTIONS_ADDED;
105 if(action == 'D')
106 m_Action|= LOGACTIONS_DELETED;
107 if(action == 'U')
108 m_Action|= LOGACTIONS_UNMERGED;
109 if(action == 'K')
110 m_Action|= LOGACTIONS_DELETED;
111 if(action == 'C' )
112 m_Action|= LOGACTIONS_COPY;
113 if(action == 'T')
114 m_Action|= LOGACTIONS_MODIFIED;
116 return m_Action;
119 int CTGitPath::ParserAction(git_delta_t action)
121 if (action == GIT_DELTA_MODIFIED)
122 m_Action |= LOGACTIONS_MODIFIED;
123 if (action == GIT_DELTA_RENAMED)
124 m_Action |= LOGACTIONS_REPLACED;
125 if (action == GIT_DELTA_ADDED)
126 m_Action |= LOGACTIONS_ADDED;
127 if (action == GIT_DELTA_DELETED)
128 m_Action |= LOGACTIONS_DELETED;
129 if (action == GIT_DELTA_UNMODIFIED)
130 m_Action |= LOGACTIONS_UNMERGED;
131 if (action == GIT_DELTA_COPIED)
132 m_Action |= LOGACTIONS_COPY;
133 if (action == GIT_DELTA_TYPECHANGE)
134 m_Action |= LOGACTIONS_MODIFIED;
136 return m_Action;
139 void CTGitPath::SetFromGit(const char* pPath)
141 Reset();
142 if (pPath == NULL)
143 return;
144 int len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, NULL, 0);
145 if (len)
147 len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, m_sFwdslashPath.GetBuffer(len+1), len+1);
148 m_sFwdslashPath.ReleaseBuffer(len-1);
150 SanitizeRootPath(m_sFwdslashPath, true);
153 void CTGitPath::SetFromGit(const char* pPath, bool bIsDirectory)
155 SetFromGit(pPath);
156 m_bDirectoryKnown = true;
157 m_bIsDirectory = bIsDirectory;
160 void CTGitPath::SetFromGit(const TCHAR* pPath, bool bIsDirectory)
162 Reset();
163 if (pPath)
165 m_sFwdslashPath = pPath;
166 SanitizeRootPath(m_sFwdslashPath, true);
168 m_bDirectoryKnown = true;
169 m_bIsDirectory = bIsDirectory;
172 void CTGitPath::SetFromGit(const CString& sPath,CString *oldpath)
174 Reset();
175 m_sFwdslashPath = sPath;
176 SanitizeRootPath(m_sFwdslashPath, true);
177 if(oldpath)
178 m_sOldFwdslashPath = *oldpath;
181 void CTGitPath::SetFromWin(LPCTSTR pPath)
183 Reset();
184 m_sBackslashPath = pPath;
185 m_sBackslashPath.Replace(L"\\\\?\\", L"");
186 SanitizeRootPath(m_sBackslashPath, false);
187 ATLASSERT(m_sBackslashPath.Find('/')<0);
189 void CTGitPath::SetFromWin(const CString& sPath)
191 Reset();
192 m_sBackslashPath = sPath;
193 m_sBackslashPath.Replace(L"\\\\?\\", L"");
194 SanitizeRootPath(m_sBackslashPath, false);
196 void CTGitPath::SetFromWin(LPCTSTR pPath, bool bIsDirectory)
198 Reset();
199 m_sBackslashPath = pPath;
200 m_bIsDirectory = bIsDirectory;
201 m_bDirectoryKnown = true;
202 SanitizeRootPath(m_sBackslashPath, false);
204 void CTGitPath::SetFromWin(const CString& sPath, bool bIsDirectory)
206 Reset();
207 m_sBackslashPath = sPath;
208 m_bIsDirectory = bIsDirectory;
209 m_bDirectoryKnown = true;
210 SanitizeRootPath(m_sBackslashPath, false);
212 void CTGitPath::SetFromUnknown(const CString& sPath)
214 Reset();
215 // Just set whichever path we think is most likely to be used
216 // GitAdminDir admin;
217 // CString p;
218 // if(admin.HasAdminDir(sPath,&p))
219 // SetFwdslashPath(sPath.Right(sPath.GetLength()-p.GetLength()));
220 // else
221 SetFwdslashPath(sPath);
224 LPCTSTR CTGitPath::GetWinPath() const
226 if(IsEmpty())
228 return _T("");
230 if(m_sBackslashPath.IsEmpty())
232 SetBackslashPath(m_sFwdslashPath);
234 return m_sBackslashPath;
236 // This is a temporary function, to be used during the migration to
237 // the path class. Ultimately, functions consuming paths should take a CTGitPath&, not a CString
238 const CString& CTGitPath::GetWinPathString() const
240 if(m_sBackslashPath.IsEmpty())
242 SetBackslashPath(m_sFwdslashPath);
244 return m_sBackslashPath;
247 const CString& CTGitPath::GetGitPathString() const
249 if(m_sFwdslashPath.IsEmpty())
251 SetFwdslashPath(m_sBackslashPath);
253 return m_sFwdslashPath;
256 const CString &CTGitPath::GetGitOldPathString() const
258 return m_sOldFwdslashPath;
261 const CString& CTGitPath::GetUIPathString() const
263 if (m_sUIPath.IsEmpty())
265 m_sUIPath = GetWinPathString();
267 return m_sUIPath;
270 void CTGitPath::SetFwdslashPath(const CString& sPath) const
272 CString path = sPath;
273 path.Replace('\\', '/');
275 // We don't leave a trailing /
276 path.TrimRight('/');
277 path.Replace(L"//?/", L"");
279 SanitizeRootPath(path, true);
281 path.Replace(_T("file:////"), _T("file://"));
282 m_sFwdslashPath = path;
284 m_sUTF8FwdslashPath.Empty();
287 void CTGitPath::SetBackslashPath(const CString& sPath) const
289 CString path = sPath;
290 path.Replace('/', '\\');
291 path.TrimRight('\\');
292 SanitizeRootPath(path, false);
293 m_sBackslashPath = path;
296 void CTGitPath::SetUTF8FwdslashPath(const CString& sPath) const
298 m_sUTF8FwdslashPath = CUnicodeUtils::GetUTF8(sPath);
301 void CTGitPath::SanitizeRootPath(CString& sPath, bool bIsForwardPath) const
303 // Make sure to add the trailing slash to root paths such as 'C:'
304 if (sPath.GetLength() == 2 && sPath[1] == ':')
306 sPath += (bIsForwardPath) ? _T("/") : _T("\\");
310 bool CTGitPath::IsDirectory() const
312 if(!m_bDirectoryKnown)
314 UpdateAttributes();
316 return m_bIsDirectory;
319 bool CTGitPath::Exists() const
321 if (!m_bExistsKnown)
323 UpdateAttributes();
325 return m_bExists;
328 bool CTGitPath::Delete(bool bTrash) const
330 EnsureBackslashPathSet();
331 ::SetFileAttributes(m_sBackslashPath, FILE_ATTRIBUTE_NORMAL);
332 bool bRet = false;
333 if (Exists())
335 if ((bTrash)||(IsDirectory()))
337 std::unique_ptr<TCHAR[]> buf(new TCHAR[m_sBackslashPath.GetLength() + 2]);
338 _tcscpy_s(buf.get(), m_sBackslashPath.GetLength() + 2, m_sBackslashPath);
339 buf[m_sBackslashPath.GetLength()] = 0;
340 buf[m_sBackslashPath.GetLength()+1] = 0;
341 bRet = CTGitPathList::DeleteViaShell(buf.get(), bTrash);
343 else
345 bRet = !!::DeleteFile(m_sBackslashPath);
348 m_bExists = false;
349 m_bExistsKnown = true;
350 return bRet;
353 __int64 CTGitPath::GetLastWriteTime() const
355 if(!m_bLastWriteTimeKnown)
357 UpdateAttributes();
359 return m_lastWriteTime;
362 __int64 CTGitPath::GetFileSize() const
364 if(!m_bDirectoryKnown)
366 UpdateAttributes();
368 return m_fileSize;
371 bool CTGitPath::IsReadOnly() const
373 if(!m_bLastWriteTimeKnown)
375 UpdateAttributes();
377 return m_bIsReadOnly;
380 void CTGitPath::UpdateAttributes() const
382 EnsureBackslashPathSet();
383 WIN32_FILE_ATTRIBUTE_DATA attribs;
384 if (m_sBackslashPath.GetLength() >= 248)
385 m_sLongBackslashPath = _T("\\\\?\\") + m_sBackslashPath;
386 if(GetFileAttributesEx(m_sBackslashPath.GetLength() >= 248 ? m_sLongBackslashPath : m_sBackslashPath, GetFileExInfoStandard, &attribs))
388 m_bIsDirectory = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
389 // don't cast directly to an __int64:
390 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284%28v=vs.85%29.aspx
391 // "Do not cast a pointer to a FILETIME structure to either a ULARGE_INTEGER* or __int64* value
392 // because it can cause alignment faults on 64-bit Windows."
393 m_lastWriteTime = static_cast<__int64>(attribs.ftLastWriteTime.dwHighDateTime) << 32 | attribs.ftLastWriteTime.dwLowDateTime;
394 if (m_bIsDirectory)
396 m_fileSize = 0;
398 else
400 m_fileSize = ((INT64)( (DWORD)(attribs.nFileSizeLow) ) | ( (INT64)( (DWORD)(attribs.nFileSizeHigh) )<<32 ));
402 m_bIsReadOnly = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
403 m_bExists = true;
405 else
407 m_bIsDirectory = false;
408 m_lastWriteTime = 0;
409 m_fileSize = 0;
410 DWORD err = GetLastError();
411 if ((err == ERROR_FILE_NOT_FOUND)||(err == ERROR_PATH_NOT_FOUND)||(err == ERROR_INVALID_NAME))
413 m_bExists = false;
415 else
417 m_bExists = true;
418 return;
421 m_bDirectoryKnown = true;
422 m_bLastWriteTimeKnown = true;
423 m_bExistsKnown = true;
426 CTGitPath CTGitPath::GetSubPath(const CTGitPath &root)
428 CTGitPath path;
430 if(GetWinPathString().Left(root.GetWinPathString().GetLength()) == root.GetWinPathString())
432 CString str=GetWinPathString();
433 path.SetFromWin(str.Right(str.GetLength()-root.GetWinPathString().GetLength()-1));
435 return path;
438 void CTGitPath::EnsureBackslashPathSet() const
440 if(m_sBackslashPath.IsEmpty())
442 SetBackslashPath(m_sFwdslashPath);
443 ATLASSERT(IsEmpty() || !m_sBackslashPath.IsEmpty());
446 void CTGitPath::EnsureFwdslashPathSet() const
448 if(m_sFwdslashPath.IsEmpty())
450 SetFwdslashPath(m_sBackslashPath);
451 ATLASSERT(IsEmpty() || !m_sFwdslashPath.IsEmpty());
456 // Reset all the caches
457 void CTGitPath::Reset()
459 m_bDirectoryKnown = false;
460 m_bURLKnown = false;
461 m_bLastWriteTimeKnown = false;
462 m_bHasAdminDirKnown = false;
463 m_bIsValidOnWindowsKnown = false;
464 m_bIsAdminDirKnown = false;
465 m_bExistsKnown = false;
466 m_bIsSpecialDirectoryKnown = false;
467 m_bIsSpecialDirectory = false;
469 m_sBackslashPath.Empty();
470 m_sFwdslashPath.Empty();
471 m_sUTF8FwdslashPath.Empty();
472 this->m_Action=0;
473 this->m_StatAdd=_T("");
474 this->m_StatDel=_T("");
475 m_ParentNo=0;
476 ATLASSERT(IsEmpty());
479 CTGitPath CTGitPath::GetDirectory() const
481 if ((IsDirectory())||(!Exists()))
483 return *this;
485 return GetContainingDirectory();
488 CTGitPath CTGitPath::GetContainingDirectory() const
490 EnsureBackslashPathSet();
492 CString sDirName = m_sBackslashPath.Left(m_sBackslashPath.ReverseFind('\\'));
493 if(sDirName.GetLength() == 2 && sDirName[1] == ':')
495 // This is a root directory, which needs a trailing slash
496 sDirName += '\\';
497 if(sDirName == m_sBackslashPath)
499 // We were clearly provided with a root path to start with - we should return nothing now
500 sDirName.Empty();
503 if(sDirName.GetLength() == 1 && sDirName[0] == '\\')
505 // We have an UNC path and we already are the root
506 sDirName.Empty();
508 CTGitPath retVal;
509 retVal.SetFromWin(sDirName);
510 return retVal;
513 CString CTGitPath::GetRootPathString() const
515 EnsureBackslashPathSet();
516 CString workingPath = m_sBackslashPath;
517 LPTSTR pPath = workingPath.GetBuffer(MAX_PATH); // MAX_PATH ok here.
518 ATLVERIFY(::PathStripToRoot(pPath));
519 workingPath.ReleaseBuffer();
520 return workingPath;
524 CString CTGitPath::GetFilename() const
526 //ATLASSERT(!IsDirectory());
527 return GetFileOrDirectoryName();
530 CString CTGitPath::GetFileOrDirectoryName() const
532 EnsureBackslashPathSet();
533 return m_sBackslashPath.Mid(m_sBackslashPath.ReverseFind('\\')+1);
536 CString CTGitPath::GetUIFileOrDirectoryName() const
538 GetUIPathString();
539 return m_sUIPath.Mid(m_sUIPath.ReverseFind('\\')+1);
542 CString CTGitPath::GetFileExtension() const
544 if(!IsDirectory())
546 EnsureBackslashPathSet();
547 int dotPos = m_sBackslashPath.ReverseFind('.');
548 int slashPos = m_sBackslashPath.ReverseFind('\\');
549 if (dotPos > slashPos)
550 return m_sBackslashPath.Mid(dotPos);
552 return CString();
554 CString CTGitPath::GetBaseFilename() const
556 int dot;
557 CString filename=GetFilename();
558 dot = filename.ReverseFind(_T('.'));
559 if(dot>0)
560 return filename.Left(dot);
561 else
562 return filename;
565 bool CTGitPath::ArePathStringsEqual(const CString& sP1, const CString& sP2)
567 int length = sP1.GetLength();
568 if(length != sP2.GetLength())
570 // Different lengths
571 return false;
573 // We work from the end of the strings, because path differences
574 // are more likely to occur at the far end of a string
575 LPCTSTR pP1Start = sP1;
576 LPCTSTR pP1 = pP1Start+(length-1);
577 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
578 while(length-- > 0)
580 if(_totlower(*pP1--) != _totlower(*pP2--))
582 return false;
585 return true;
588 bool CTGitPath::ArePathStringsEqualWithCase(const CString& sP1, const CString& sP2)
590 int length = sP1.GetLength();
591 if(length != sP2.GetLength())
593 // Different lengths
594 return false;
596 // We work from the end of the strings, because path differences
597 // are more likely to occur at the far end of a string
598 LPCTSTR pP1Start = sP1;
599 LPCTSTR pP1 = pP1Start+(length-1);
600 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
601 while(length-- > 0)
603 if((*pP1--) != (*pP2--))
605 return false;
608 return true;
611 bool CTGitPath::IsEmpty() const
613 // Check the backward slash path first, since the chance that this
614 // one is set is higher. In case of a 'false' return value it's a little
615 // bit faster.
616 return m_sBackslashPath.IsEmpty() && m_sFwdslashPath.IsEmpty();
619 // Test if both paths refer to the same item
620 // Ignores case and slash direction
621 bool CTGitPath::IsEquivalentTo(const CTGitPath& rhs) const
623 // Try and find a slash direction which avoids having to convert
624 // both filenames
625 if(!m_sBackslashPath.IsEmpty())
627 // *We've* got a \ path - make sure that the RHS also has a \ path
628 rhs.EnsureBackslashPathSet();
629 return ArePathStringsEqualWithCase(m_sBackslashPath, rhs.m_sBackslashPath);
631 else
633 // Assume we've got a fwdslash path and make sure that the RHS has one
634 rhs.EnsureFwdslashPathSet();
635 return ArePathStringsEqualWithCase(m_sFwdslashPath, rhs.m_sFwdslashPath);
639 bool CTGitPath::IsEquivalentToWithoutCase(const CTGitPath& rhs) const
641 // Try and find a slash direction which avoids having to convert
642 // both filenames
643 if(!m_sBackslashPath.IsEmpty())
645 // *We've* got a \ path - make sure that the RHS also has a \ path
646 rhs.EnsureBackslashPathSet();
647 return ArePathStringsEqual(m_sBackslashPath, rhs.m_sBackslashPath);
649 else
651 // Assume we've got a fwdslash path and make sure that the RHS has one
652 rhs.EnsureFwdslashPathSet();
653 return ArePathStringsEqual(m_sFwdslashPath, rhs.m_sFwdslashPath);
657 bool CTGitPath::IsAncestorOf(const CTGitPath& possibleDescendant) const
659 possibleDescendant.EnsureBackslashPathSet();
660 EnsureBackslashPathSet();
662 bool bPathStringsEqual = ArePathStringsEqual(m_sBackslashPath, possibleDescendant.m_sBackslashPath.Left(m_sBackslashPath.GetLength()));
663 if (m_sBackslashPath.GetLength() >= possibleDescendant.GetWinPathString().GetLength())
665 return bPathStringsEqual;
668 return (bPathStringsEqual &&
669 ((possibleDescendant.m_sBackslashPath[m_sBackslashPath.GetLength()] == '\\')||
670 (m_sBackslashPath.GetLength()==3 && m_sBackslashPath[1]==':')));
673 // Get a string representing the file path, optionally with a base
674 // section stripped off the front.
675 CString CTGitPath::GetDisplayString(const CTGitPath* pOptionalBasePath /* = NULL*/) const
677 EnsureFwdslashPathSet();
678 if(pOptionalBasePath != NULL)
680 // Find the length of the base-path without having to do an 'ensure' on it
681 int baseLength = max(pOptionalBasePath->m_sBackslashPath.GetLength(), pOptionalBasePath->m_sFwdslashPath.GetLength());
683 // Now, chop that baseLength of the front of the path
684 return m_sFwdslashPath.Mid(baseLength).TrimLeft('/');
686 return m_sFwdslashPath;
689 int CTGitPath::Compare(const CTGitPath& left, const CTGitPath& right)
691 left.EnsureBackslashPathSet();
692 right.EnsureBackslashPathSet();
693 return left.m_sBackslashPath.CompareNoCase(right.m_sBackslashPath);
696 bool operator<(const CTGitPath& left, const CTGitPath& right)
698 return CTGitPath::Compare(left, right) < 0;
701 bool CTGitPath::PredLeftEquivalentToRight(const CTGitPath& left, const CTGitPath& right)
703 return left.IsEquivalentTo(right);
706 bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right)
708 if (left.IsAdminDir() && right.IsAdminDir())
710 CTGitPath l = left;
711 CTGitPath r = right;
714 l = l.GetContainingDirectory();
715 } while(l.HasAdminDir());
718 r = r.GetContainingDirectory();
719 } while(r.HasAdminDir());
720 return l.GetContainingDirectory().IsEquivalentTo(r.GetContainingDirectory());
722 return left.GetDirectory().IsEquivalentTo(right.GetDirectory());
725 bool CTGitPath::CheckChild(const CTGitPath &parent, const CTGitPath& child)
727 return parent.IsAncestorOf(child);
730 void CTGitPath::AppendRawString(const CString& sAppend)
732 EnsureFwdslashPathSet();
733 CString strCopy = m_sFwdslashPath += sAppend;
734 SetFromUnknown(strCopy);
737 void CTGitPath::AppendPathString(const CString& sAppend)
739 EnsureBackslashPathSet();
740 CString cleanAppend(sAppend);
741 cleanAppend.Replace('/', '\\');
742 cleanAppend.TrimLeft('\\');
743 m_sBackslashPath.TrimRight('\\');
744 CString strCopy = m_sBackslashPath + _T("\\") + cleanAppend;
745 SetFromWin(strCopy);
748 bool CTGitPath::IsWCRoot() const
750 if (m_bIsWCRootKnown)
751 return m_bIsWCRoot;
753 m_bIsWCRootKnown = true;
754 m_bIsWCRoot = false;
756 CString topDirectory;
757 if (!IsDirectory() || !HasAdminDir(&topDirectory))
759 return m_bIsWCRoot;
762 if (IsEquivalentToWithoutCase(topDirectory))
764 m_bIsWCRoot = true;
767 return m_bIsWCRoot;
770 bool CTGitPath::HasAdminDir() const
772 if (m_bHasAdminDirKnown)
773 return m_bHasAdminDir;
775 EnsureBackslashPathSet();
776 m_bHasAdminDir = GitAdminDir::HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
777 m_bHasAdminDirKnown = true;
778 return m_bHasAdminDir;
781 bool CTGitPath::HasSubmodules() const
783 if (HasAdminDir())
785 CString path = m_sProjectRoot;
786 path += _T("\\.gitmodules");
787 if( PathFileExists(path) )
788 return true;
790 return false;
793 int CTGitPath::GetAdminDirMask() const
795 int status = 0;
796 CString topdir;
797 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
799 return status;
802 // ITEMIS_INGIT will be revoked if necessary in TortoiseShell/ContextMenu.cpp
803 status |= ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER;
805 if (IsDirectory())
807 status |= ITEMIS_FOLDERINGIT;
808 if (IsWCRoot())
810 status |= ITEMIS_WCROOT;
812 CString topProjectDir;
813 if (GitAdminDir::HasAdminDir(GetWinPathString(), false, &topProjectDir))
815 if (PathFileExists(topProjectDir + _T("\\.gitmodules")))
817 CAutoConfig config(true);
818 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(topProjectDir + _T("\\.gitmodules")), GIT_CONFIG_LEVEL_APP, FALSE);
819 CString relativePath = GetWinPathString().Mid(topProjectDir.GetLength());
820 relativePath.Replace(_T("\\"), _T("/"));
821 relativePath.Trim(_T("/"));
822 CStringA submodulePath = CUnicodeUtils::GetUTF8(relativePath);
823 if (git_config_foreach_match(config, "submodule\\..*\\.path",
824 [](const git_config_entry *entry, void *data) { return entry->value == *(CStringA *)data ? GIT_EUSER : 0; }, &submodulePath) == GIT_EUSER)
825 status |= ITEMIS_SUBMODULE;
831 CString dotGitPath;
832 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
834 if (PathFileExists(dotGitPath + _T("BISECT_START")))
835 status |= ITEMIS_BISECT;
837 if (PathFileExists(dotGitPath + _T("MERGE_HEAD")))
838 status |= ITEMIS_MERGEACTIVE;
840 if (PathFileExists(dotGitPath + _T("refs\\stash")))
841 status |= ITEMIS_STASH;
843 if (PathFileExists(dotGitPath + _T("svn")))
844 status |= ITEMIS_GITSVN;
846 if (PathFileExists(topdir + _T("\\.gitmodules")))
847 status |= ITEMIS_SUBMODULECONTAINER;
849 return status;
852 bool CTGitPath::HasStashDir() const
854 CString topdir;
855 if(!GitAdminDir::HasAdminDir(GetWinPathString(),&topdir))
857 return false;
860 CString dotGitPath;
861 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
863 return !!PathFileExists(dotGitPath + _T("\\refs\\stash"));
865 bool CTGitPath::HasGitSVNDir() const
867 CString topdir;
868 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
870 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))
883 return false;
886 CString dotGitPath;
887 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
889 return !!PathFileExists(dotGitPath + _T("BISECT_START"));
891 bool CTGitPath::IsMergeActive() const
893 CString topdir;
894 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
896 return false;
899 CString dotGitPath;
900 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
902 return !!PathFileExists(dotGitPath + _T("MERGE_HEAD"));
904 bool CTGitPath::HasRebaseApply() const
906 CString topdir;
907 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
909 return false;
912 CString dotGitPath;
913 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
915 return !!PathFileExists(dotGitPath + _T("rebase-apply"));
918 bool CTGitPath::HasAdminDir(CString *ProjectTopDir) const
920 if (m_bHasAdminDirKnown)
922 if (ProjectTopDir)
923 *ProjectTopDir = m_sProjectRoot;
924 return m_bHasAdminDir;
927 EnsureBackslashPathSet();
928 m_bHasAdminDir = GitAdminDir::HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
929 m_bHasAdminDirKnown = true;
930 if (ProjectTopDir)
931 *ProjectTopDir = m_sProjectRoot;
932 return m_bHasAdminDir;
935 bool CTGitPath::IsAdminDir() const
937 if (m_bIsAdminDirKnown)
938 return m_bIsAdminDir;
940 EnsureBackslashPathSet();
941 m_bIsAdminDir = GitAdminDir::IsAdminDirPath(m_sBackslashPath);
942 m_bIsAdminDirKnown = true;
943 return m_bIsAdminDir;
946 bool CTGitPath::IsValidOnWindows() const
948 if (m_bIsValidOnWindowsKnown)
949 return m_bIsValidOnWindows;
951 m_bIsValidOnWindows = false;
952 EnsureBackslashPathSet();
953 CString sMatch = m_sBackslashPath + _T("\r\n");
954 std::wstring sPattern;
955 // the 'file://' URL is just a normal windows path:
956 if (sMatch.Left(7).CompareNoCase(_T("file:\\\\"))==0)
958 sMatch = sMatch.Mid(7);
959 sMatch.TrimLeft(_T("\\"));
960 sPattern = _T("^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$");
962 else
964 sPattern = _T("^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$");
969 std::tr1::wregex rx(sPattern, std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
970 std::tr1::wsmatch match;
972 std::wstring rmatch = std::wstring((LPCTSTR)sMatch);
973 if (std::tr1::regex_match(rmatch, match, rx))
975 if (std::wstring(match[0]).compare(sMatch)==0)
976 m_bIsValidOnWindows = true;
978 if (m_bIsValidOnWindows)
980 // now check for illegal filenames
981 std::tr1::wregex rx2(_T("\\\\(lpt\\d|com\\d|aux|nul|prn|con)(\\\\|$)"), std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
982 rmatch = m_sBackslashPath;
983 if (std::tr1::regex_search(rmatch, rx2, std::tr1::regex_constants::match_default))
984 m_bIsValidOnWindows = false;
987 catch (std::exception) {}
989 m_bIsValidOnWindowsKnown = true;
990 return m_bIsValidOnWindows;
993 //////////////////////////////////////////////////////////////////////////
995 CTGitPathList::CTGitPathList()
997 m_Action = 0;
1000 // A constructor which allows a path list to be easily built which one initial entry in
1001 CTGitPathList::CTGitPathList(const CTGitPath& firstEntry)
1003 m_Action = 0;
1004 AddPath(firstEntry);
1006 int CTGitPathList::ParserFromLsFile(BYTE_VECTOR &out,bool /*staged*/)
1008 int pos=0;
1009 CString one;
1010 CTGitPath path;
1011 CString part;
1012 this->Clear();
1014 while (pos >= 0 && pos < (int)out.size())
1016 one.Empty();
1017 path.Reset();
1019 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1020 int tabstart=0;
1021 // m_Action is never used and propably never worked (needs to be set after path.SetFromGit)
1022 // also dropped LOGACTIONS_CACHE for 'H'
1023 // path.m_Action=path.ParserAction(out[pos]);
1024 one.Tokenize(_T("\t"),tabstart);
1026 if(tabstart>=0)
1027 path.SetFromGit(one.Right(one.GetLength()-tabstart));
1028 else
1029 return -1;
1031 tabstart=0;
1033 part=one.Tokenize(_T(" "),tabstart); //Tag
1034 if (tabstart < 0)
1035 return -1;
1037 part=one.Tokenize(_T(" "),tabstart); //Mode
1038 if (tabstart < 0)
1039 return -1;
1041 part=one.Tokenize(_T(" "),tabstart); //Hash
1042 if (tabstart < 0)
1043 return -1;
1045 part=one.Tokenize(_T("\t"),tabstart); //Stage
1046 if (tabstart < 0)
1047 return -1;
1049 path.m_Stage=_ttol(part);
1051 this->AddPath(path);
1053 pos=out.findNextString(pos);
1055 return 0;
1057 int CTGitPathList::FillUnRev(unsigned int action, CTGitPathList *list, CString *err)
1059 this->Clear();
1060 CTGitPath path;
1062 int count;
1063 if(list==NULL)
1064 count=1;
1065 else
1066 count=list->GetCount();
1067 for (int i = 0; i < count; ++i)
1069 CString cmd;
1070 int pos = 0;
1072 CString ignored;
1073 if(action & CTGitPath::LOGACTIONS_IGNORE)
1074 ignored= _T(" -i");
1076 if(list==NULL)
1078 cmd=_T("git.exe ls-files --exclude-standard --full-name --others -z");
1079 cmd+=ignored;
1082 else
1083 { cmd.Format(_T("git.exe ls-files --exclude-standard --full-name --others -z%s -- \"%s\""),
1084 ignored,
1085 (*list)[i].GetWinPathString());
1088 BYTE_VECTOR out, errb;
1089 out.clear();
1090 if (g_Git.Run(cmd, &out, &errb))
1092 if (err != nullptr)
1093 CGit::StringAppend(err, &errb[0], CP_UTF8, (int)errb.size());
1094 return -1;
1097 pos=0;
1098 CString one;
1099 while (pos >= 0 && pos < (int)out.size())
1101 one.Empty();
1102 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1103 if(!one.IsEmpty())
1105 //SetFromGit will clear all status
1106 path.SetFromGit(one);
1107 path.m_Action=action;
1108 AddPath(path);
1110 pos=out.findNextString(pos);
1114 return 0;
1116 int CTGitPathList::FillBasedOnIndexFlags(unsigned short flag, CTGitPathList* list /*nullptr*/)
1118 Clear();
1119 CTGitPath path;
1121 CAutoRepository repository(g_Git.GetGitRepository());
1122 if (!repository)
1123 return -1;
1125 CAutoIndex index;
1126 if (git_repository_index(index.GetPointer(), repository))
1127 return -1;
1129 int count;
1130 if (list == nullptr)
1131 count = 1;
1132 else
1133 count = list->GetCount();
1134 for (int j = 0; j < count; ++j)
1136 for (size_t i = 0, ecount = git_index_entrycount(index); i < ecount; ++i)
1138 const git_index_entry *e = git_index_get_byindex(index, i);
1140 if (!e || !((e->flags | e->flags_extended) & flag) || !e->path)
1141 continue;
1143 CString one = CUnicodeUtils::GetUnicode(e->path);
1145 if (!(!list || (*list)[j].GetWinPathString().IsEmpty() || one == (*list)[j].GetGitPathString() || (PathIsDirectory(g_Git.CombinePath((*list)[j].GetWinPathString())) && one.Find((*list)[j].GetGitPathString() + _T("/")) == 0)))
1146 continue;
1148 //SetFromGit will clear all status
1149 path.SetFromGit(one);
1150 if ((e->flags | e->flags_extended) & GIT_IDXENTRY_SKIP_WORKTREE)
1151 path.m_Action = CTGitPath::LOGACTIONS_SKIPWORKTREE;
1152 else if ((e->flags | e->flags_extended) & GIT_IDXENTRY_VALID)
1153 path.m_Action = CTGitPath::LOGACTIONS_ASSUMEVALID;
1154 AddPath(path);
1157 RemoveDuplicates();
1158 return 0;
1160 int CTGitPathList::ParserFromLog(BYTE_VECTOR &log, bool parseDeletes /*false*/)
1162 this->Clear();
1163 int pos=0;
1164 CTGitPath path;
1165 m_Action=0;
1166 int logend = (int)log.size();
1167 while (pos >= 0 && pos < logend)
1169 path.Reset();
1170 if(log[pos]=='\n')
1171 ++pos;
1173 if (pos >= logend)
1174 return -1;
1176 if(log[pos]==':')
1178 bool merged=false;
1179 if (pos + 1 >= logend)
1180 return -1;
1181 if(log[pos+1] ==':')
1183 merged=true;
1185 int end=log.find(0,pos);
1186 int actionstart=-1;
1187 int file1=-1,file2=-1;
1188 if( end>0 )
1190 actionstart=log.find(' ',end-6);
1191 pos=actionstart;
1193 if( actionstart>0 )
1195 ++actionstart;
1196 if (actionstart >= logend)
1197 return -1;
1199 file1 = log.find(0,actionstart);
1200 if( file1>=0 )
1202 ++file1;
1203 pos=file1;
1205 if( log[actionstart] == 'C' || log[actionstart] == 'R' )
1207 file2=file1;
1208 file1 = log.find(0,file1);
1209 if(file1>=0 )
1211 ++file1;
1212 pos=file1;
1218 CString pathname1;
1219 CString pathname2;
1221 if( file1>=0 )
1222 CGit::StringAppend(&pathname1, &log[file1], CP_UTF8);
1223 if( file2>=0 )
1224 CGit::StringAppend(&pathname2, &log[file2], CP_UTF8);
1226 CTGitPath *GitPath=LookForGitPath(pathname1);
1228 if (actionstart < 0)
1229 return -1;
1230 if(GitPath)
1232 GitPath->ParserAction( log[actionstart] );
1234 if(merged)
1236 GitPath->m_Action |= CTGitPath::LOGACTIONS_MERGED;
1238 m_Action |=GitPath->m_Action;
1241 else
1243 int ac=path.ParserAction(log[actionstart] );
1244 ac |= merged?CTGitPath::LOGACTIONS_MERGED:0;
1246 path.SetFromGit(pathname1,&pathname2);
1247 path.m_Action=ac;
1248 //action must be set after setfromgit. SetFromGit will clear all status.
1249 this->m_Action|=ac;
1251 AddPath(path);
1256 else
1258 int tabstart=0;
1259 path.Reset();
1260 CString StatAdd;
1261 CString StatDel;
1262 CString file1;
1263 CString file2;
1265 tabstart=log.find('\t',pos);
1266 if(tabstart >=0)
1268 log[tabstart]=0;
1269 CGit::StringAppend(&StatAdd, &log[pos], CP_UTF8);
1270 pos=tabstart+1;
1273 tabstart=log.find('\t',pos);
1274 if(tabstart >=0)
1276 log[tabstart]=0;
1278 CGit::StringAppend(&StatDel, &log[pos], CP_UTF8);
1279 pos=tabstart+1;
1282 if(log[pos] == 0) //rename
1284 ++pos;
1285 CGit::StringAppend(&file2, &log[pos], CP_UTF8);
1286 int sec=log.find(0,pos);
1287 if(sec>=0)
1289 ++sec;
1290 CGit::StringAppend(&file1, &log[sec], CP_UTF8);
1292 pos=sec;
1295 else
1297 CGit::StringAppend(&file1, &log[pos], CP_UTF8);
1299 path.SetFromGit(file1,&file2);
1301 CTGitPath *GitPath=LookForGitPath(path.GetGitPathString());
1302 if(GitPath)
1304 GitPath->m_StatAdd=StatAdd;
1305 GitPath->m_StatDel=StatDel;
1307 else
1309 //path.SetFromGit(pathname);
1310 if (parseDeletes)
1312 path.m_StatAdd=_T("0");
1313 path.m_StatDel=_T("0");
1314 path.m_Action |= CTGitPath::LOGACTIONS_DELETED;
1316 else
1318 path.m_StatAdd=StatAdd;
1319 path.m_StatDel=StatDel;
1321 AddPath(path);
1325 pos=log.findNextString(pos);
1327 return 0;
1330 void CTGitPathList::AddPath(const CTGitPath& newPath)
1332 m_paths.push_back(newPath);
1333 m_commonBaseDirectory.Reset();
1335 int CTGitPathList::GetCount() const
1337 return (int)m_paths.size();
1339 bool CTGitPathList::IsEmpty() const
1341 return m_paths.empty();
1343 void CTGitPathList::Clear()
1345 m_paths.clear();
1346 m_commonBaseDirectory.Reset();
1349 const CTGitPath& CTGitPathList::operator[](INT_PTR index) const
1351 ATLASSERT(index >= 0 && index < (INT_PTR)m_paths.size());
1352 return m_paths[index];
1355 bool CTGitPathList::AreAllPathsFiles() const
1357 // Look through the vector for any directories - if we find them, return false
1358 return std::find_if(m_paths.begin(), m_paths.end(), std::mem_fun_ref(&CTGitPath::IsDirectory)) == m_paths.end();
1362 #if defined(_MFC_VER)
1364 bool CTGitPathList::LoadFromFile(const CTGitPath& filename)
1366 Clear();
1369 CString strLine;
1370 CStdioFile file(filename.GetWinPath(), CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite);
1372 // for every selected file/folder
1373 CTGitPath path;
1374 while (file.ReadString(strLine))
1376 path.SetFromUnknown(strLine);
1377 AddPath(path);
1379 file.Close();
1381 catch (CFileException* pE)
1383 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException loading target file list\n");
1384 TCHAR error[10000] = {0};
1385 pE->GetErrorMessage(error, 10000);
1386 // CMessageBox::Show(NULL, error, _T("TortoiseGit"), MB_ICONERROR);
1387 pE->Delete();
1388 return false;
1390 return true;
1393 bool CTGitPathList::WriteToFile(const CString& sFilename, bool bANSI /* = false */) const
1397 if (bANSI)
1399 CStdioFile file(sFilename, CFile::typeText | CFile::modeReadWrite | CFile::modeCreate);
1400 PathVector::const_iterator it;
1401 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1403 CStringA line = CStringA(it->GetGitPathString()) + '\n';
1404 file.Write(line, line.GetLength());
1406 file.Close();
1408 else
1410 CStdioFile file(sFilename, CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
1411 PathVector::const_iterator it;
1412 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1414 file.WriteString(it->GetGitPathString()+_T("\n"));
1416 file.Close();
1419 catch (CFileException* pE)
1421 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in writing temp file\n");
1422 pE->Delete();
1423 return false;
1425 return true;
1429 void CTGitPathList::LoadFromAsteriskSeparatedString(const CString& sPathString)
1431 int pos = 0;
1432 CString temp;
1433 for(;;)
1435 temp = sPathString.Tokenize(_T("*"),pos);
1436 if(temp.IsEmpty())
1438 break;
1440 AddPath(CTGitPath(CPathUtils::GetLongPathname(temp)));
1444 CString CTGitPathList::CreateAsteriskSeparatedString() const
1446 CString sRet;
1447 PathVector::const_iterator it;
1448 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1450 if (!sRet.IsEmpty())
1451 sRet += _T("*");
1452 sRet += it->GetWinPathString();
1454 return sRet;
1456 #endif // _MFC_VER
1458 bool
1459 CTGitPathList::AreAllPathsFilesInOneDirectory() const
1461 // Check if all the paths are files and in the same directory
1462 PathVector::const_iterator it;
1463 m_commonBaseDirectory.Reset();
1464 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1466 if(it->IsDirectory())
1468 return false;
1470 const CTGitPath& baseDirectory = it->GetDirectory();
1471 if(m_commonBaseDirectory.IsEmpty())
1473 m_commonBaseDirectory = baseDirectory;
1475 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1477 // Different path
1478 m_commonBaseDirectory.Reset();
1479 return false;
1482 return true;
1485 CTGitPath CTGitPathList::GetCommonDirectory() const
1487 if (m_commonBaseDirectory.IsEmpty())
1489 PathVector::const_iterator it;
1490 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1492 const CTGitPath& baseDirectory = it->GetDirectory();
1493 if(m_commonBaseDirectory.IsEmpty())
1495 m_commonBaseDirectory = baseDirectory;
1497 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1499 // Different path
1500 m_commonBaseDirectory.Reset();
1501 break;
1505 // since we only checked strings, not paths,
1506 // we have to make sure now that we really return a *path* here
1507 PathVector::const_iterator iter;
1508 for(iter = m_paths.begin(); iter != m_paths.end(); ++iter)
1510 if (!m_commonBaseDirectory.IsAncestorOf(*iter))
1512 m_commonBaseDirectory = m_commonBaseDirectory.GetContainingDirectory();
1513 break;
1516 return m_commonBaseDirectory;
1519 CTGitPath CTGitPathList::GetCommonRoot() const
1521 if (IsEmpty())
1522 return CTGitPath();
1524 if (GetCount() == 1)
1525 return m_paths[0];
1527 // first entry is common root for itself
1528 // (add trailing '\\' to detect partial matches of the last path element)
1529 CString root = m_paths[0].GetWinPathString() + _T('\\');
1530 int rootLength = root.GetLength();
1532 // determine common path string prefix
1533 for (PathVector::const_iterator it = m_paths.begin() + 1; it != m_paths.end(); ++it)
1535 CString path = it->GetWinPathString() + _T('\\');
1537 int newLength = CStringUtils::GetMatchingLength(root, path);
1538 if (newLength != rootLength)
1540 root.Delete(newLength, rootLength);
1541 rootLength = newLength;
1545 // remove the last (partial) path element
1546 if (rootLength > 0)
1547 root.Delete(root.ReverseFind(_T('\\')), rootLength);
1549 // done
1550 return CTGitPath(root);
1553 void CTGitPathList::SortByPathname(bool bReverse /*= false*/)
1555 std::sort(m_paths.begin(), m_paths.end());
1556 if (bReverse)
1557 std::reverse(m_paths.begin(), m_paths.end());
1560 void CTGitPathList::DeleteAllFiles(bool bTrash, bool bFilesOnly)
1562 PathVector::const_iterator it;
1563 SortByPathname(true); // nested ones first
1565 CString sPaths;
1566 for (it = m_paths.begin(); it != m_paths.end(); ++it)
1568 if ((it->Exists()) && ((it->IsDirectory() != bFilesOnly) || !bFilesOnly))
1570 if (!it->IsDirectory())
1571 ::SetFileAttributes(it->GetWinPath(), FILE_ATTRIBUTE_NORMAL);
1573 sPaths += it->GetWinPath();
1574 sPaths += '\0';
1577 sPaths += '\0';
1578 sPaths += '\0';
1579 DeleteViaShell((LPCTSTR)sPaths, bTrash);
1580 Clear();
1583 bool CTGitPathList::DeleteViaShell(LPCTSTR path, bool bTrash)
1585 SHFILEOPSTRUCT shop = {0};
1586 shop.wFunc = FO_DELETE;
1587 shop.pFrom = path;
1588 shop.fFlags = FOF_NOCONFIRMATION|FOF_NOERRORUI|FOF_SILENT|FOF_NO_CONNECTED_ELEMENTS;
1589 if (bTrash)
1590 shop.fFlags |= FOF_ALLOWUNDO;
1591 const bool bRet = (SHFileOperation(&shop) == 0);
1592 return bRet;
1595 void CTGitPathList::RemoveDuplicates()
1597 SortByPathname();
1598 // Remove the duplicates
1599 // (Unique moves them to the end of the vector, then erase chops them off)
1600 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::PredLeftEquivalentToRight), m_paths.end());
1603 void CTGitPathList::RemoveAdminPaths()
1605 PathVector::iterator it;
1606 for(it = m_paths.begin(); it != m_paths.end(); )
1608 if (it->IsAdminDir())
1610 m_paths.erase(it);
1611 it = m_paths.begin();
1613 else
1614 ++it;
1618 void CTGitPathList::RemovePath(const CTGitPath& path)
1620 PathVector::iterator it;
1621 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1623 if (it->IsEquivalentTo(path))
1625 m_paths.erase(it);
1626 return;
1631 void CTGitPathList::RemoveItem(CTGitPath & path)
1633 PathVector::iterator it;
1634 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1636 if (it->GetGitPathString()==path.GetGitPathString())
1638 m_paths.erase(it);
1639 return;
1643 void CTGitPathList::RemoveChildren()
1645 SortByPathname();
1646 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::CheckChild), m_paths.end());
1649 bool CTGitPathList::IsEqual(const CTGitPathList& list)
1651 if (list.GetCount() != GetCount())
1652 return false;
1653 for (int i=0; i<list.GetCount(); ++i)
1655 if (!list[i].IsEquivalentTo(m_paths[i]))
1656 return false;
1658 return true;
1661 CTGitPath * CTGitPathList::LookForGitPath(CString path)
1663 int i=0;
1664 for (i = 0; i < this->GetCount(); ++i)
1666 if((*this)[i].GetGitPathString() == path )
1667 return (CTGitPath*)&(*this)[i];
1669 return NULL;
1671 CString CTGitPath::GetActionName(int action)
1673 if(action & CTGitPath::LOGACTIONS_UNMERGED)
1674 return MAKEINTRESOURCE(IDS_PATHACTIONS_CONFLICT);
1675 if(action & CTGitPath::LOGACTIONS_ADDED)
1676 return MAKEINTRESOURCE(IDS_PATHACTIONS_ADD);
1677 if(action & CTGitPath::LOGACTIONS_DELETED)
1678 return MAKEINTRESOURCE(IDS_PATHACTIONS_DELETE);
1679 if(action & CTGitPath::LOGACTIONS_MERGED )
1680 return MAKEINTRESOURCE(IDS_PATHACTIONS_MERGED);
1682 if(action & CTGitPath::LOGACTIONS_MODIFIED)
1683 return MAKEINTRESOURCE(IDS_PATHACTIONS_MODIFIED);
1684 if(action & CTGitPath::LOGACTIONS_REPLACED)
1685 return MAKEINTRESOURCE(IDS_PATHACTIONS_RENAME);
1686 if(action & CTGitPath::LOGACTIONS_COPY)
1687 return MAKEINTRESOURCE(IDS_PATHACTIONS_COPY);
1689 if (action & CTGitPath::LOGACTIONS_ASSUMEVALID)
1690 return MAKEINTRESOURCE(IDS_PATHACTIONS_ASSUMEUNCHANGED);
1691 if (action & CTGitPath::LOGACTIONS_SKIPWORKTREE)
1692 return MAKEINTRESOURCE(IDS_PATHACTIONS_SKIPWORKTREE);
1694 if (action & CTGitPath::LOGACTIONS_IGNORE)
1695 return MAKEINTRESOURCE(IDS_PATHACTIONS_IGNORED);
1697 return MAKEINTRESOURCE(IDS_PATHACTIONS_UNKNOWN);
1699 CString CTGitPath::GetActionName()
1701 return GetActionName(m_Action);
1704 int CTGitPathList::GetAction()
1706 return m_Action;