Minor cleanup
[TortoiseGit.git] / src / Git / TGitPath.cpp
blobf0c453cf39222a2f2711ae32b39ac6047964e063
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;
251 void CTGitPath::SetBackslashPath(const CString& sPath) const
253 CString path = sPath;
254 path.Replace('/', '\\');
255 path.TrimRight('\\');
256 SanitizeRootPath(path, false);
257 m_sBackslashPath = path;
260 void CTGitPath::SanitizeRootPath(CString& sPath, bool bIsForwardPath) const
262 // Make sure to add the trailing slash to root paths such as 'C:'
263 if (sPath.GetLength() == 2 && sPath[1] == ':')
264 sPath += (bIsForwardPath) ? _T("/") : _T("\\");
267 bool CTGitPath::IsDirectory() const
269 if(!m_bDirectoryKnown)
270 UpdateAttributes();
271 return m_bIsDirectory;
274 bool CTGitPath::Exists() const
276 if (!m_bExistsKnown)
277 UpdateAttributes();
278 return m_bExists;
281 bool CTGitPath::Delete(bool bTrash, bool bShowErrorUI) const
283 EnsureBackslashPathSet();
284 ::SetFileAttributes(m_sBackslashPath, FILE_ATTRIBUTE_NORMAL);
285 bool bRet = false;
286 if (Exists())
288 if ((bTrash)||(IsDirectory()))
290 auto buf = std::make_unique<TCHAR[]>(m_sBackslashPath.GetLength() + 2);
291 _tcscpy_s(buf.get(), m_sBackslashPath.GetLength() + 2, m_sBackslashPath);
292 buf[m_sBackslashPath.GetLength()] = 0;
293 buf[m_sBackslashPath.GetLength()+1] = 0;
294 bRet = CTGitPathList::DeleteViaShell(buf.get(), bTrash, bShowErrorUI);
296 else
297 bRet = !!::DeleteFile(m_sBackslashPath);
299 m_bExists = false;
300 m_bExistsKnown = true;
301 return bRet;
304 __int64 CTGitPath::GetLastWriteTime() const
306 if(!m_bLastWriteTimeKnown)
307 UpdateAttributes();
308 return m_lastWriteTime;
311 __int64 CTGitPath::GetFileSize() const
313 if(!m_bDirectoryKnown)
314 UpdateAttributes();
315 return m_fileSize;
318 bool CTGitPath::IsReadOnly() const
320 if(!m_bLastWriteTimeKnown)
321 UpdateAttributes();
322 return m_bIsReadOnly;
325 void CTGitPath::UpdateAttributes() const
327 EnsureBackslashPathSet();
328 WIN32_FILE_ATTRIBUTE_DATA attribs;
329 if (m_sBackslashPath.GetLength() >= 248)
330 m_sLongBackslashPath = _T("\\\\?\\") + m_sBackslashPath;
331 if(GetFileAttributesEx(m_sBackslashPath.GetLength() >= 248 ? m_sLongBackslashPath : m_sBackslashPath, GetFileExInfoStandard, &attribs))
333 m_bIsDirectory = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
334 // don't cast directly to an __int64:
335 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284%28v=vs.85%29.aspx
336 // "Do not cast a pointer to a FILETIME structure to either a ULARGE_INTEGER* or __int64* value
337 // because it can cause alignment faults on 64-bit Windows."
338 m_lastWriteTime = static_cast<__int64>(attribs.ftLastWriteTime.dwHighDateTime) << 32 | attribs.ftLastWriteTime.dwLowDateTime;
339 if (m_bIsDirectory)
340 m_fileSize = 0;
341 else
342 m_fileSize = ((INT64)( (DWORD)(attribs.nFileSizeLow) ) | ( (INT64)( (DWORD)(attribs.nFileSizeHigh) )<<32 ));
343 m_bIsReadOnly = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
344 m_bExists = true;
346 else
348 m_bIsDirectory = false;
349 m_lastWriteTime = 0;
350 m_fileSize = 0;
351 DWORD err = GetLastError();
352 if ((err == ERROR_FILE_NOT_FOUND)||(err == ERROR_PATH_NOT_FOUND)||(err == ERROR_INVALID_NAME))
353 m_bExists = false;
354 else
356 m_bExists = true;
357 return;
360 m_bDirectoryKnown = true;
361 m_bLastWriteTimeKnown = true;
362 m_bExistsKnown = true;
365 CTGitPath CTGitPath::GetSubPath(const CTGitPath &root)
367 CTGitPath path;
369 if (CStringUtils::StartsWith(GetWinPathString(), root.GetWinPathString()))
371 CString str=GetWinPathString();
372 path.SetFromWin(str.Right(str.GetLength()-root.GetWinPathString().GetLength()-1));
374 return path;
377 void CTGitPath::EnsureBackslashPathSet() const
379 if(m_sBackslashPath.IsEmpty())
381 SetBackslashPath(m_sFwdslashPath);
382 ATLASSERT(IsEmpty() || !m_sBackslashPath.IsEmpty());
385 void CTGitPath::EnsureFwdslashPathSet() const
387 if(m_sFwdslashPath.IsEmpty())
389 SetFwdslashPath(m_sBackslashPath);
390 ATLASSERT(IsEmpty() || !m_sFwdslashPath.IsEmpty());
395 // Reset all the caches
396 void CTGitPath::Reset()
398 m_bDirectoryKnown = false;
399 m_bURLKnown = false;
400 m_bLastWriteTimeKnown = false;
401 m_bHasAdminDirKnown = false;
402 m_bIsValidOnWindowsKnown = false;
403 m_bIsAdminDirKnown = false;
404 m_bExistsKnown = false;
405 m_bIsSpecialDirectoryKnown = false;
406 m_bIsSpecialDirectory = false;
408 m_sBackslashPath.Empty();
409 m_sFwdslashPath.Empty();
410 this->m_Action=0;
411 this->m_StatAdd.Empty();
412 this->m_StatDel.Empty();
413 m_ParentNo=0;
414 ATLASSERT(IsEmpty());
417 CTGitPath CTGitPath::GetDirectory() const
419 if ((IsDirectory())||(!Exists()))
420 return *this;
421 return GetContainingDirectory();
424 CTGitPath CTGitPath::GetContainingDirectory() const
426 EnsureBackslashPathSet();
428 CString sDirName = m_sBackslashPath.Left(m_sBackslashPath.ReverseFind('\\'));
429 if(sDirName.GetLength() == 2 && sDirName[1] == ':')
431 // This is a root directory, which needs a trailing slash
432 sDirName += '\\';
433 if(sDirName == m_sBackslashPath)
435 // We were clearly provided with a root path to start with - we should return nothing now
436 sDirName.Empty();
439 if(sDirName.GetLength() == 1 && sDirName[0] == '\\')
441 // We have an UNC path and we already are the root
442 sDirName.Empty();
444 CTGitPath retVal;
445 retVal.SetFromWin(sDirName);
446 return retVal;
449 CString CTGitPath::GetRootPathString() const
451 EnsureBackslashPathSet();
452 CString workingPath = m_sBackslashPath;
453 LPTSTR pPath = workingPath.GetBuffer(MAX_PATH); // MAX_PATH ok here.
454 ATLVERIFY(::PathStripToRoot(pPath));
455 workingPath.ReleaseBuffer();
456 return workingPath;
460 CString CTGitPath::GetFilename() const
462 //ATLASSERT(!IsDirectory());
463 return GetFileOrDirectoryName();
466 CString CTGitPath::GetFileOrDirectoryName() const
468 EnsureBackslashPathSet();
469 return m_sBackslashPath.Mid(m_sBackslashPath.ReverseFind('\\')+1);
472 CString CTGitPath::GetUIFileOrDirectoryName() const
474 GetUIPathString();
475 return m_sUIPath.Mid(m_sUIPath.ReverseFind('\\')+1);
478 CString CTGitPath::GetFileExtension() const
480 if(!IsDirectory())
482 EnsureBackslashPathSet();
483 int dotPos = m_sBackslashPath.ReverseFind('.');
484 int slashPos = m_sBackslashPath.ReverseFind('\\');
485 if (dotPos > slashPos)
486 return m_sBackslashPath.Mid(dotPos);
488 return CString();
490 CString CTGitPath::GetBaseFilename() const
492 int dot;
493 CString filename=GetFilename();
494 dot = filename.ReverseFind(_T('.'));
495 if(dot>0)
496 filename.Truncate(dot);
497 return filename;
500 bool CTGitPath::ArePathStringsEqual(const CString& sP1, const CString& sP2)
502 int length = sP1.GetLength();
503 if(length != sP2.GetLength())
505 // Different lengths
506 return false;
508 // We work from the end of the strings, because path differences
509 // are more likely to occur at the far end of a string
510 LPCTSTR pP1Start = sP1;
511 LPCTSTR pP1 = pP1Start+(length-1);
512 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
513 while(length-- > 0)
515 if(_totlower(*pP1--) != _totlower(*pP2--))
516 return false;
518 return true;
521 bool CTGitPath::ArePathStringsEqualWithCase(const CString& sP1, const CString& sP2)
523 int length = sP1.GetLength();
524 if(length != sP2.GetLength())
526 // Different lengths
527 return false;
529 // We work from the end of the strings, because path differences
530 // are more likely to occur at the far end of a string
531 LPCTSTR pP1Start = sP1;
532 LPCTSTR pP1 = pP1Start+(length-1);
533 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
534 while(length-- > 0)
536 if((*pP1--) != (*pP2--))
537 return false;
539 return true;
542 bool CTGitPath::IsEmpty() const
544 // Check the backward slash path first, since the chance that this
545 // one is set is higher. In case of a 'false' return value it's a little
546 // bit faster.
547 return m_sBackslashPath.IsEmpty() && m_sFwdslashPath.IsEmpty();
550 // Test if both paths refer to the same item
551 // Ignores case and slash direction
552 bool CTGitPath::IsEquivalentTo(const CTGitPath& rhs) const
554 // Try and find a slash direction which avoids having to convert
555 // both filenames
556 if(!m_sBackslashPath.IsEmpty())
558 // *We've* got a \ path - make sure that the RHS also has a \ path
559 rhs.EnsureBackslashPathSet();
560 return ArePathStringsEqualWithCase(m_sBackslashPath, rhs.m_sBackslashPath);
562 else
564 // Assume we've got a fwdslash path and make sure that the RHS has one
565 rhs.EnsureFwdslashPathSet();
566 return ArePathStringsEqualWithCase(m_sFwdslashPath, rhs.m_sFwdslashPath);
570 bool CTGitPath::IsEquivalentToWithoutCase(const CTGitPath& rhs) const
572 // Try and find a slash direction which avoids having to convert
573 // both filenames
574 if(!m_sBackslashPath.IsEmpty())
576 // *We've* got a \ path - make sure that the RHS also has a \ path
577 rhs.EnsureBackslashPathSet();
578 return ArePathStringsEqual(m_sBackslashPath, rhs.m_sBackslashPath);
580 else
582 // Assume we've got a fwdslash path and make sure that the RHS has one
583 rhs.EnsureFwdslashPathSet();
584 return ArePathStringsEqual(m_sFwdslashPath, rhs.m_sFwdslashPath);
588 bool CTGitPath::IsAncestorOf(const CTGitPath& possibleDescendant) const
590 possibleDescendant.EnsureBackslashPathSet();
591 EnsureBackslashPathSet();
593 bool bPathStringsEqual = ArePathStringsEqual(m_sBackslashPath, possibleDescendant.m_sBackslashPath.Left(m_sBackslashPath.GetLength()));
594 if (m_sBackslashPath.GetLength() >= possibleDescendant.GetWinPathString().GetLength())
596 return bPathStringsEqual;
599 return (bPathStringsEqual &&
600 ((possibleDescendant.m_sBackslashPath[m_sBackslashPath.GetLength()] == '\\')||
601 (m_sBackslashPath.GetLength()==3 && m_sBackslashPath[1]==':')));
604 // Get a string representing the file path, optionally with a base
605 // section stripped off the front.
606 CString CTGitPath::GetDisplayString(const CTGitPath* pOptionalBasePath /* = nullptr*/) const
608 EnsureFwdslashPathSet();
609 if (pOptionalBasePath)
611 // Find the length of the base-path without having to do an 'ensure' on it
612 int baseLength = max(pOptionalBasePath->m_sBackslashPath.GetLength(), pOptionalBasePath->m_sFwdslashPath.GetLength());
614 // Now, chop that baseLength of the front of the path
615 return m_sFwdslashPath.Mid(baseLength).TrimLeft('/');
617 return m_sFwdslashPath;
620 int CTGitPath::Compare(const CTGitPath& left, const CTGitPath& right)
622 left.EnsureBackslashPathSet();
623 right.EnsureBackslashPathSet();
624 return left.m_sBackslashPath.CompareNoCase(right.m_sBackslashPath);
627 bool operator<(const CTGitPath& left, const CTGitPath& right)
629 return CTGitPath::Compare(left, right) < 0;
632 bool CTGitPath::PredLeftEquivalentToRight(const CTGitPath& left, const CTGitPath& right)
634 return left.IsEquivalentTo(right);
637 bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right)
639 if (left.IsAdminDir() && right.IsAdminDir())
641 CTGitPath l = left;
642 CTGitPath r = right;
645 l = l.GetContainingDirectory();
646 } while(l.HasAdminDir());
649 r = r.GetContainingDirectory();
650 } while(r.HasAdminDir());
651 return l.GetContainingDirectory().IsEquivalentTo(r.GetContainingDirectory());
653 return left.GetDirectory().IsEquivalentTo(right.GetDirectory());
656 bool CTGitPath::CheckChild(const CTGitPath &parent, const CTGitPath& child)
658 return parent.IsAncestorOf(child);
661 void CTGitPath::AppendRawString(const CString& sAppend)
663 EnsureFwdslashPathSet();
664 CString strCopy = m_sFwdslashPath += sAppend;
665 SetFromUnknown(strCopy);
668 void CTGitPath::AppendPathString(const CString& sAppend)
670 EnsureBackslashPathSet();
671 CString cleanAppend(sAppend);
672 cleanAppend.Replace('/', '\\');
673 cleanAppend.TrimLeft('\\');
674 m_sBackslashPath.TrimRight('\\');
675 CString strCopy = m_sBackslashPath;
676 strCopy += _T('\\');
677 strCopy += cleanAppend;
678 SetFromWin(strCopy);
681 bool CTGitPath::IsWCRoot() const
683 if (m_bIsWCRootKnown)
684 return m_bIsWCRoot;
686 m_bIsWCRootKnown = true;
687 m_bIsWCRoot = false;
689 CString topDirectory;
690 if (!IsDirectory() || !HasAdminDir(&topDirectory))
691 return m_bIsWCRoot;
693 if (IsEquivalentToWithoutCase(topDirectory))
694 m_bIsWCRoot = true;
696 return m_bIsWCRoot;
699 bool CTGitPath::HasAdminDir() const
701 if (m_bHasAdminDirKnown)
702 return m_bHasAdminDir;
704 EnsureBackslashPathSet();
705 m_bHasAdminDir = GitAdminDir::HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
706 m_bHasAdminDirKnown = true;
707 return m_bHasAdminDir;
710 bool CTGitPath::HasSubmodules() const
712 if (HasAdminDir())
714 CString path = m_sProjectRoot;
715 path += _T("\\.gitmodules");
716 if( PathFileExists(path) )
717 return true;
719 return false;
722 int CTGitPath::GetAdminDirMask() const
724 int status = 0;
725 CString topdir;
726 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
727 return status;
729 // ITEMIS_INGIT will be revoked if necessary in TortoiseShell/ContextMenu.cpp
730 status |= ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER;
732 if (IsDirectory())
734 status |= ITEMIS_FOLDERINGIT;
735 if (IsWCRoot())
737 status |= ITEMIS_WCROOT;
739 CString topProjectDir;
740 if (GitAdminDir::HasAdminDir(GetWinPathString(), false, &topProjectDir))
742 if (PathFileExists(topProjectDir + _T("\\.gitmodules")))
744 CAutoConfig config(true);
745 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(topProjectDir + _T("\\.gitmodules")), GIT_CONFIG_LEVEL_APP, FALSE);
746 CString relativePath = GetWinPathString().Mid(topProjectDir.GetLength());
747 relativePath.Replace(_T("\\"), _T("/"));
748 relativePath.Trim(_T("/"));
749 CStringA submodulePath = CUnicodeUtils::GetUTF8(relativePath);
750 if (git_config_foreach_match(config, "submodule\\..*\\.path",
751 [](const git_config_entry *entry, void *data) { return entry->value == *(CStringA *)data ? GIT_EUSER : 0; }, &submodulePath) == GIT_EUSER)
752 status |= ITEMIS_SUBMODULE;
758 CString dotGitPath;
759 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
761 if (PathFileExists(dotGitPath + _T("BISECT_START")))
762 status |= ITEMIS_BISECT;
764 if (PathFileExists(dotGitPath + _T("MERGE_HEAD")))
765 status |= ITEMIS_MERGEACTIVE;
767 if (HasStashDir(dotGitPath))
768 status |= ITEMIS_STASH;
770 if (PathFileExists(dotGitPath + _T("svn")))
771 status |= ITEMIS_GITSVN;
773 if (PathFileExists(topdir + _T("\\.gitmodules")))
774 status |= ITEMIS_SUBMODULECONTAINER;
776 return status;
779 bool CTGitPath::HasStashDir(const CString& dotGitPath) const
781 if (PathFileExists(dotGitPath + L"refs\\stash"))
782 return true;
784 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);
785 if (!hfile)
786 return false;
788 DWORD filesize = ::GetFileSize(hfile, nullptr);
789 if (filesize == 0)
790 return false;
792 DWORD size = 0;
793 auto buff = std::make_unique<char[]>(filesize + 1);
794 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
795 buff.get()[filesize] = '\0';
797 if (size != filesize)
798 return false;
800 for (DWORD i = 0; i < filesize;)
802 if (buff[i] == '#' || buff[i] == '^')
804 while (buff[i] != '\n')
806 ++i;
807 if (i == filesize)
808 break;
810 ++i;
813 if (i >= filesize)
814 break;
816 while (buff[i] != ' ')
818 ++i;
819 if (i == filesize)
820 break;
823 ++i;
824 if (i >= filesize)
825 break;
827 if (i <= filesize - 10 && (buff[i + 10] == '\n' || buff[i + 10] == '\0') && !strncmp("refs/stash", buff.get() + i, 10))
828 return true;
829 while (buff[i] != '\n')
831 ++i;
832 if (i == filesize)
833 break;
836 while (buff[i] == '\n')
838 ++i;
839 if (i == filesize)
840 break;
843 return false;
846 bool CTGitPath::HasStashDir() const
848 CString topdir;
849 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
850 return false;
852 CString dotGitPath;
853 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
855 return HasStashDir(dotGitPath);
858 bool CTGitPath::HasGitSVNDir() const
860 CString topdir;
861 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
862 return false;
864 CString dotGitPath;
865 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
867 return !!PathFileExists(dotGitPath + _T("svn"));
869 bool CTGitPath::IsBisectActive() const
871 CString topdir;
872 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
873 return false;
875 CString dotGitPath;
876 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
878 return !!PathFileExists(dotGitPath + _T("BISECT_START"));
880 bool CTGitPath::IsMergeActive() const
882 CString topdir;
883 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
884 return false;
886 CString dotGitPath;
887 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
889 return !!PathFileExists(dotGitPath + _T("MERGE_HEAD"));
891 bool CTGitPath::HasRebaseApply() const
893 CString topdir;
894 if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir))
895 return false;
897 CString dotGitPath;
898 GitAdminDir::GetAdminDirPath(topdir, dotGitPath);
900 return !!PathFileExists(dotGitPath + _T("rebase-apply"));
903 bool CTGitPath::HasAdminDir(CString *ProjectTopDir) const
905 if (m_bHasAdminDirKnown)
907 if (ProjectTopDir)
908 *ProjectTopDir = m_sProjectRoot;
909 return m_bHasAdminDir;
912 EnsureBackslashPathSet();
913 m_bHasAdminDir = GitAdminDir::HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
914 m_bHasAdminDirKnown = true;
915 if (ProjectTopDir)
916 *ProjectTopDir = m_sProjectRoot;
917 return m_bHasAdminDir;
920 bool CTGitPath::IsAdminDir() const
922 if (m_bIsAdminDirKnown)
923 return m_bIsAdminDir;
925 EnsureBackslashPathSet();
926 m_bIsAdminDir = GitAdminDir::IsAdminDirPath(m_sBackslashPath);
927 m_bIsAdminDirKnown = true;
928 return m_bIsAdminDir;
931 bool CTGitPath::IsValidOnWindows() const
933 if (m_bIsValidOnWindowsKnown)
934 return m_bIsValidOnWindows;
936 m_bIsValidOnWindows = false;
937 EnsureBackslashPathSet();
938 CString sMatch = m_sBackslashPath + _T("\r\n");
939 std::wstring sPattern;
940 // the 'file://' URL is just a normal windows path:
941 if (sMatch.Left(7).CompareNoCase(_T("file:\\\\"))==0)
943 sMatch = sMatch.Mid(7);
944 sMatch.TrimLeft(_T("\\"));
945 sPattern = _T("^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$");
947 else
948 sPattern = _T("^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$");
952 std::tr1::wregex rx(sPattern, std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
953 std::tr1::wsmatch match;
955 std::wstring rmatch = std::wstring((LPCTSTR)sMatch);
956 if (std::tr1::regex_match(rmatch, match, rx))
958 if (std::wstring(match[0]).compare(sMatch)==0)
959 m_bIsValidOnWindows = true;
961 if (m_bIsValidOnWindows)
963 // now check for illegal filenames
964 std::tr1::wregex rx2(_T("\\\\(lpt\\d|com\\d|aux|nul|prn|con)(\\\\|$)"), std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
965 rmatch = m_sBackslashPath;
966 if (std::tr1::regex_search(rmatch, rx2, std::tr1::regex_constants::match_default))
967 m_bIsValidOnWindows = false;
970 catch (std::exception) {}
972 m_bIsValidOnWindowsKnown = true;
973 return m_bIsValidOnWindows;
976 //////////////////////////////////////////////////////////////////////////
978 CTGitPathList::CTGitPathList()
980 m_Action = 0;
983 // A constructor which allows a path list to be easily built which one initial entry in
984 CTGitPathList::CTGitPathList(const CTGitPath& firstEntry)
986 m_Action = 0;
987 AddPath(firstEntry);
989 int CTGitPathList::ParserFromLsFile(BYTE_VECTOR &out,bool /*staged*/)
991 int pos=0;
992 CString one;
993 CTGitPath path;
994 CString part;
995 this->Clear();
997 while (pos >= 0 && pos < (int)out.size())
999 one.Empty();
1000 path.Reset();
1002 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1003 int tabstart=0;
1004 // m_Action is never used and propably never worked (needs to be set after path.SetFromGit)
1005 // also dropped LOGACTIONS_CACHE for 'H'
1006 // path.m_Action=path.ParserAction(out[pos]);
1007 one.Tokenize(_T("\t"),tabstart);
1009 if(tabstart>=0)
1010 path.SetFromGit(one.Right(one.GetLength()-tabstart));
1011 else
1012 return -1;
1014 tabstart=0;
1016 part=one.Tokenize(_T(" "),tabstart); //Tag
1017 if (tabstart < 0)
1018 return -1;
1020 part=one.Tokenize(_T(" "),tabstart); //Mode
1021 if (tabstart < 0)
1022 return -1;
1024 part=one.Tokenize(_T(" "),tabstart); //Hash
1025 if (tabstart < 0)
1026 return -1;
1028 part=one.Tokenize(_T("\t"),tabstart); //Stage
1029 if (tabstart < 0)
1030 return -1;
1032 path.m_Stage=_ttol(part);
1034 this->AddPath(path);
1036 pos=out.findNextString(pos);
1038 return 0;
1040 int CTGitPathList::FillUnRev(unsigned int action, CTGitPathList *list, CString *err)
1042 this->Clear();
1043 CTGitPath path;
1045 int count;
1046 if (!list)
1047 count=1;
1048 else
1049 count=list->GetCount();
1050 for (int i = 0; i < count; ++i)
1052 CString cmd;
1053 int pos = 0;
1055 CString ignored;
1056 if(action & CTGitPath::LOGACTIONS_IGNORE)
1057 ignored= _T(" -i");
1059 if (!list)
1061 cmd=_T("git.exe ls-files --exclude-standard --full-name --others -z");
1062 cmd+=ignored;
1065 else
1066 { cmd.Format(_T("git.exe ls-files --exclude-standard --full-name --others -z%s -- \"%s\""),
1067 (LPCTSTR)ignored,
1068 (*list)[i].GetWinPath());
1071 BYTE_VECTOR out, errb;
1072 out.clear();
1073 if (g_Git.Run(cmd, &out, &errb))
1075 if (err != nullptr)
1076 CGit::StringAppend(err, &errb[0], CP_UTF8, (int)errb.size());
1077 return -1;
1080 pos=0;
1081 CString one;
1082 while (pos >= 0 && pos < (int)out.size())
1084 one.Empty();
1085 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1086 if(!one.IsEmpty())
1088 //SetFromGit will clear all status
1089 path.SetFromGit(one);
1090 path.m_Action=action;
1091 AddPath(path);
1093 pos=out.findNextString(pos);
1097 return 0;
1099 int CTGitPathList::FillBasedOnIndexFlags(unsigned short flag, unsigned short flagextended, CTGitPathList* list /*nullptr*/)
1101 Clear();
1102 CTGitPath path;
1104 CAutoRepository repository(g_Git.GetGitRepository());
1105 if (!repository)
1106 return -1;
1108 CAutoIndex index;
1109 if (git_repository_index(index.GetPointer(), repository))
1110 return -1;
1112 int count;
1113 if (list == nullptr)
1114 count = 1;
1115 else
1116 count = list->GetCount();
1117 for (int j = 0; j < count; ++j)
1119 for (size_t i = 0, ecount = git_index_entrycount(index); i < ecount; ++i)
1121 const git_index_entry *e = git_index_get_byindex(index, i);
1123 if (!e || !((e->flags & flag) || (e->flags_extended & flagextended)) || !e->path)
1124 continue;
1126 CString one = CUnicodeUtils::GetUnicode(e->path);
1128 if (!(!list || (*list)[j].GetWinPathString().IsEmpty() || one == (*list)[j].GetGitPathString() || (PathIsDirectory(g_Git.CombinePath((*list)[j].GetWinPathString())) && CStringUtils::StartsWith(one, (*list)[j].GetGitPathString() + L'/'))))
1129 continue;
1131 //SetFromGit will clear all status
1132 path.SetFromGit(one);
1133 if (e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE)
1134 path.m_Action = CTGitPath::LOGACTIONS_SKIPWORKTREE;
1135 else if (e->flags & GIT_IDXENTRY_VALID)
1136 path.m_Action = CTGitPath::LOGACTIONS_ASSUMEVALID;
1137 AddPath(path);
1140 RemoveDuplicates();
1141 return 0;
1143 int CTGitPathList::ParserFromLog(BYTE_VECTOR &log, bool parseDeletes /*false*/)
1145 this->Clear();
1146 std::map<CString, size_t> duplicateMap;
1147 int pos=0;
1148 CTGitPath path;
1149 m_Action=0;
1150 int logend = (int)log.size();
1151 while (pos >= 0 && pos < logend)
1153 path.Reset();
1154 if(log[pos]=='\n')
1155 ++pos;
1157 if (pos >= logend)
1158 return -1;
1160 if(log[pos]==':')
1162 bool merged=false;
1163 if (pos + 1 >= logend)
1164 return -1;
1165 if(log[pos+1] ==':')
1166 merged=true;
1167 int end=log.find(0,pos);
1168 int actionstart=-1;
1169 int file1=-1,file2=-1;
1170 if( end>0 )
1172 actionstart=log.find(' ',end-6);
1173 pos=actionstart;
1175 if( actionstart>0 )
1177 ++actionstart;
1178 if (actionstart >= logend)
1179 return -1;
1181 file1 = log.find(0,actionstart);
1182 if( file1>=0 )
1184 ++file1;
1185 pos=file1;
1187 if( log[actionstart] == 'C' || log[actionstart] == 'R' )
1189 file2=file1;
1190 file1 = log.find(0,file1);
1191 if(file1>=0 )
1193 ++file1;
1194 pos=file1;
1199 CString pathname1;
1200 CString pathname2;
1202 if( file1>=0 )
1203 CGit::StringAppend(&pathname1, &log[file1], CP_UTF8);
1204 if( file2>=0 )
1205 CGit::StringAppend(&pathname2, &log[file2], CP_UTF8);
1207 if (actionstart < 0)
1208 return -1;
1210 auto existing = duplicateMap.find(pathname1);
1211 if (existing != duplicateMap.end())
1213 CTGitPath& p = m_paths[existing->second];
1214 p.ParserAction(log[actionstart]);
1216 if(merged)
1217 p.m_Action |= CTGitPath::LOGACTIONS_MERGED;
1218 m_Action |= p.m_Action;
1220 else
1222 int ac=path.ParserAction(log[actionstart] );
1223 ac |= merged?CTGitPath::LOGACTIONS_MERGED:0;
1225 path.SetFromGit(pathname1,&pathname2);
1226 path.m_Action=ac;
1227 //action must be set after setfromgit. SetFromGit will clear all status.
1228 this->m_Action|=ac;
1230 AddPath(path);
1231 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1234 else
1236 int tabstart=0;
1237 path.Reset();
1238 CString StatAdd;
1239 CString StatDel;
1240 CString file1;
1241 CString file2;
1243 tabstart=log.find('\t',pos);
1244 if(tabstart >=0)
1246 log[tabstart]=0;
1247 CGit::StringAppend(&StatAdd, &log[pos], CP_UTF8);
1248 pos=tabstart+1;
1251 tabstart=log.find('\t',pos);
1252 if(tabstart >=0)
1254 log[tabstart]=0;
1256 CGit::StringAppend(&StatDel, &log[pos], CP_UTF8);
1257 pos=tabstart+1;
1260 if(log[pos] == 0) //rename
1262 ++pos;
1263 CGit::StringAppend(&file2, &log[pos], CP_UTF8);
1264 int sec=log.find(0,pos);
1265 if(sec>=0)
1267 ++sec;
1268 CGit::StringAppend(&file1, &log[sec], CP_UTF8);
1270 pos=sec;
1272 else
1274 CGit::StringAppend(&file1, &log[pos], CP_UTF8);
1276 path.SetFromGit(file1,&file2);
1278 auto existing = duplicateMap.find(path.GetGitPathString());
1279 if (existing != duplicateMap.end())
1281 CTGitPath& p = m_paths[existing->second];
1282 p.m_StatAdd = StatAdd;
1283 p.m_StatDel = StatDel;
1285 else
1287 //path.SetFromGit(pathname);
1288 if (parseDeletes)
1290 path.m_StatAdd=_T("0");
1291 path.m_StatDel=_T("0");
1292 path.m_Action |= CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_MISSING;
1294 else
1296 path.m_StatAdd=StatAdd;
1297 path.m_StatDel=StatDel;
1299 AddPath(path);
1300 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1303 pos=log.findNextString(pos);
1305 return 0;
1308 void CTGitPathList::AddPath(const CTGitPath& newPath)
1310 m_paths.push_back(newPath);
1311 m_commonBaseDirectory.Reset();
1313 int CTGitPathList::GetCount() const
1315 return (int)m_paths.size();
1317 bool CTGitPathList::IsEmpty() const
1319 return m_paths.empty();
1321 void CTGitPathList::Clear()
1323 m_paths.clear();
1324 m_commonBaseDirectory.Reset();
1327 const CTGitPath& CTGitPathList::operator[](INT_PTR index) const
1329 ATLASSERT(index >= 0 && index < (INT_PTR)m_paths.size());
1330 return m_paths[index];
1333 bool CTGitPathList::AreAllPathsFiles() const
1335 // Look through the vector for any directories - if we find them, return false
1336 return std::find_if(m_paths.cbegin(), m_paths.cend(), std::mem_fun_ref(&CTGitPath::IsDirectory)) == m_paths.end();
1339 #if defined(_MFC_VER)
1341 bool CTGitPathList::LoadFromFile(const CTGitPath& filename)
1343 Clear();
1346 CString strLine;
1347 CStdioFile file(filename.GetWinPath(), CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite);
1349 // for every selected file/folder
1350 CTGitPath path;
1351 while (file.ReadString(strLine))
1353 path.SetFromUnknown(strLine);
1354 AddPath(path);
1356 file.Close();
1358 catch (CFileException* pE)
1360 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException loading target file list\n");
1361 TCHAR error[10000] = {0};
1362 pE->GetErrorMessage(error, 10000);
1363 // CMessageBox::Show(nullptr, error, _T("TortoiseGit"), MB_ICONERROR);
1364 pE->Delete();
1365 return false;
1367 return true;
1370 bool CTGitPathList::WriteToFile(const CString& sFilename, bool bANSI /* = false */) const
1374 if (bANSI)
1376 CStdioFile file(sFilename, CFile::typeText | CFile::modeReadWrite | CFile::modeCreate);
1377 for (const auto& path : m_paths)
1379 CStringA line = CStringA(path.GetGitPathString()) + '\n';
1380 file.Write(line, line.GetLength());
1382 file.Close();
1384 else
1386 CStdioFile file(sFilename, CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
1387 for (const auto& path : m_paths)
1388 file.WriteString(path.GetGitPathString() + _T("\n"));
1389 file.Close();
1392 catch (CFileException* pE)
1394 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in writing temp file\n");
1395 pE->Delete();
1396 return false;
1398 return true;
1401 void CTGitPathList::LoadFromAsteriskSeparatedString(const CString& sPathString)
1403 int pos = 0;
1404 CString temp;
1405 for(;;)
1407 temp = sPathString.Tokenize(_T("*"),pos);
1408 if(temp.IsEmpty())
1409 break;
1410 AddPath(CTGitPath(CPathUtils::GetLongPathname(temp)));
1414 CString CTGitPathList::CreateAsteriskSeparatedString() const
1416 CString sRet;
1417 for (const auto& path : m_paths)
1419 if (!sRet.IsEmpty())
1420 sRet += _T("*");
1421 sRet += path.GetWinPathString();
1423 return sRet;
1425 #endif // _MFC_VER
1427 bool
1428 CTGitPathList::AreAllPathsFilesInOneDirectory() const
1430 // Check if all the paths are files and in the same directory
1431 m_commonBaseDirectory.Reset();
1432 for (const auto& path : m_paths)
1434 if (path.IsDirectory())
1435 return false;
1436 const CTGitPath& baseDirectory = path.GetDirectory();
1437 if(m_commonBaseDirectory.IsEmpty())
1438 m_commonBaseDirectory = baseDirectory;
1439 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1441 // Different path
1442 m_commonBaseDirectory.Reset();
1443 return false;
1446 return true;
1449 CTGitPath CTGitPathList::GetCommonDirectory() const
1451 if (m_commonBaseDirectory.IsEmpty())
1453 for (const auto& path : m_paths)
1455 const CTGitPath& baseDirectory = path.GetDirectory();
1456 if(m_commonBaseDirectory.IsEmpty())
1457 m_commonBaseDirectory = baseDirectory;
1458 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1460 // Different path
1461 m_commonBaseDirectory.Reset();
1462 break;
1466 // since we only checked strings, not paths,
1467 // we have to make sure now that we really return a *path* here
1468 for (const auto& path : m_paths)
1470 if (!m_commonBaseDirectory.IsAncestorOf(path))
1472 m_commonBaseDirectory = m_commonBaseDirectory.GetContainingDirectory();
1473 break;
1476 return m_commonBaseDirectory;
1479 CTGitPath CTGitPathList::GetCommonRoot() const
1481 if (IsEmpty())
1482 return CTGitPath();
1484 if (GetCount() == 1)
1485 return m_paths[0];
1487 // first entry is common root for itself
1488 // (add trailing '\\' to detect partial matches of the last path element)
1489 CString root = m_paths[0].GetWinPathString() + _T('\\');
1490 int rootLength = root.GetLength();
1492 // determine common path string prefix
1493 for (auto it = m_paths.cbegin() + 1; it != m_paths.cend(); ++it)
1495 CString path = it->GetWinPathString() + _T('\\');
1497 int newLength = CStringUtils::GetMatchingLength(root, path);
1498 if (newLength != rootLength)
1500 root.Delete(newLength, rootLength);
1501 rootLength = newLength;
1505 // remove the last (partial) path element
1506 if (rootLength > 0)
1507 root.Delete(root.ReverseFind(_T('\\')), rootLength);
1509 // done
1510 return CTGitPath(root);
1513 void CTGitPathList::SortByPathname(bool bReverse /*= false*/)
1515 std::sort(m_paths.begin(), m_paths.end());
1516 if (bReverse)
1517 std::reverse(m_paths.begin(), m_paths.end());
1520 void CTGitPathList::DeleteAllFiles(bool bTrash, bool bFilesOnly, bool bShowErrorUI)
1522 if (m_paths.empty())
1523 return;
1524 PathVector::const_iterator it;
1525 SortByPathname(true); // nested ones first
1527 CString sPaths;
1528 for (it = m_paths.cbegin(); it != m_paths.cend(); ++it)
1530 if ((it->Exists()) && ((it->IsDirectory() != bFilesOnly) || !bFilesOnly))
1532 if (!it->IsDirectory())
1533 ::SetFileAttributes(it->GetWinPath(), FILE_ATTRIBUTE_NORMAL);
1535 sPaths += it->GetWinPath();
1536 sPaths += '\0';
1539 if (sPaths.IsEmpty())
1540 return;
1541 sPaths += '\0';
1542 sPaths += '\0';
1543 DeleteViaShell((LPCTSTR)sPaths, bTrash, bShowErrorUI);
1544 Clear();
1547 bool CTGitPathList::DeleteViaShell(LPCTSTR path, bool bTrash, bool bShowErrorUI)
1549 SHFILEOPSTRUCT shop = {0};
1550 shop.wFunc = FO_DELETE;
1551 shop.pFrom = path;
1552 shop.fFlags = FOF_NOCONFIRMATION|FOF_NO_CONNECTED_ELEMENTS;
1553 if (!bShowErrorUI)
1554 shop.fFlags |= FOF_NOERRORUI | FOF_SILENT;
1555 if (bTrash)
1556 shop.fFlags |= FOF_ALLOWUNDO;
1557 const bool bRet = (SHFileOperation(&shop) == 0);
1558 return bRet;
1561 void CTGitPathList::RemoveDuplicates()
1563 SortByPathname();
1564 // Remove the duplicates
1565 // (Unique moves them to the end of the vector, then erase chops them off)
1566 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::PredLeftEquivalentToRight), m_paths.end());
1569 void CTGitPathList::RemoveAdminPaths()
1571 PathVector::iterator it;
1572 for(it = m_paths.begin(); it != m_paths.end(); )
1574 if (it->IsAdminDir())
1576 m_paths.erase(it);
1577 it = m_paths.begin();
1579 else
1580 ++it;
1584 void CTGitPathList::RemovePath(const CTGitPath& path)
1586 PathVector::iterator it;
1587 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1589 if (it->IsEquivalentTo(path))
1591 m_paths.erase(it);
1592 return;
1597 void CTGitPathList::RemoveItem(CTGitPath & path)
1599 PathVector::iterator it;
1600 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1602 if (CTGitPath::ArePathStringsEqualWithCase(it->GetGitPathString(), path.GetGitPathString()))
1604 m_paths.erase(it);
1605 return;
1609 void CTGitPathList::RemoveChildren()
1611 SortByPathname();
1612 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::CheckChild), m_paths.end());
1615 bool CTGitPathList::IsEqual(const CTGitPathList& list)
1617 if (list.GetCount() != GetCount())
1618 return false;
1619 for (int i=0; i<list.GetCount(); ++i)
1621 if (!list[i].IsEquivalentTo(m_paths[i]))
1622 return false;
1624 return true;
1627 const CTGitPath* CTGitPathList::LookForGitPath(const CString& path)
1629 int i=0;
1630 for (i = 0; i < this->GetCount(); ++i)
1632 if (CTGitPath::ArePathStringsEqualWithCase((*this)[i].GetGitPathString(), path))
1633 return (CTGitPath*)&(*this)[i];
1635 return nullptr;
1638 CString CTGitPath::GetActionName(int action)
1640 if(action & CTGitPath::LOGACTIONS_UNMERGED)
1641 return MAKEINTRESOURCE(IDS_PATHACTIONS_CONFLICT);
1642 if(action & CTGitPath::LOGACTIONS_ADDED)
1643 return MAKEINTRESOURCE(IDS_PATHACTIONS_ADD);
1644 if (action & CTGitPath::LOGACTIONS_MISSING)
1645 return MAKEINTRESOURCE(IDS_PATHACTIONS_MISSING);
1646 if(action & CTGitPath::LOGACTIONS_DELETED)
1647 return MAKEINTRESOURCE(IDS_PATHACTIONS_DELETE);
1648 if(action & CTGitPath::LOGACTIONS_MERGED )
1649 return MAKEINTRESOURCE(IDS_PATHACTIONS_MERGED);
1651 if(action & CTGitPath::LOGACTIONS_MODIFIED)
1652 return MAKEINTRESOURCE(IDS_PATHACTIONS_MODIFIED);
1653 if(action & CTGitPath::LOGACTIONS_REPLACED)
1654 return MAKEINTRESOURCE(IDS_PATHACTIONS_RENAME);
1655 if(action & CTGitPath::LOGACTIONS_COPY)
1656 return MAKEINTRESOURCE(IDS_PATHACTIONS_COPY);
1658 if (action & CTGitPath::LOGACTIONS_ASSUMEVALID)
1659 return MAKEINTRESOURCE(IDS_PATHACTIONS_ASSUMEUNCHANGED);
1660 if (action & CTGitPath::LOGACTIONS_SKIPWORKTREE)
1661 return MAKEINTRESOURCE(IDS_PATHACTIONS_SKIPWORKTREE);
1663 if (action & CTGitPath::LOGACTIONS_IGNORE)
1664 return MAKEINTRESOURCE(IDS_PATHACTIONS_IGNORED);
1666 return MAKEINTRESOURCE(IDS_PATHACTIONS_UNKNOWN);
1668 CString CTGitPath::GetActionName()
1670 return GetActionName(m_Action);
1673 int CTGitPathList::GetAction()
1675 return m_Action;
1678 CString CTGitPath::GetAbbreviatedRename()
1680 if (GetGitOldPathString().IsEmpty())
1681 return GetFileOrDirectoryName();
1683 // Find common prefix which ends with a slash
1684 int prefix_length = 0;
1685 for (int i = 0, maxLength = min(m_sOldFwdslashPath.GetLength(), m_sFwdslashPath.GetLength()); i < maxLength; ++i)
1687 if (m_sOldFwdslashPath[i] != m_sFwdslashPath[i])
1688 break;
1689 if (m_sOldFwdslashPath[i] == L'/')
1690 prefix_length = i + 1;
1693 LPCTSTR oldName = (LPCTSTR)m_sOldFwdslashPath + m_sOldFwdslashPath.GetLength();
1694 LPCTSTR newName = (LPCTSTR)m_sFwdslashPath + m_sFwdslashPath.GetLength();
1696 int suffix_length = 0;
1697 int prefix_adjust_for_slash = (prefix_length ? 1 : 0);
1698 while ((LPCTSTR)m_sOldFwdslashPath + prefix_length - prefix_adjust_for_slash <= oldName &&
1699 (LPCTSTR)m_sFwdslashPath + prefix_length - prefix_adjust_for_slash <= newName &&
1700 *oldName == *newName)
1702 if (*oldName == L'/')
1703 suffix_length = m_sOldFwdslashPath.GetLength() - (int)(oldName - (LPCTSTR)m_sOldFwdslashPath);
1704 --oldName;
1705 --newName;
1709 * pfx{old_midlen => new_midlen}sfx
1710 * {pfx-old => pfx-new}sfx
1711 * pfx{sfx-old => sfx-new}
1712 * name-old => name-new
1714 int old_midlen = m_sOldFwdslashPath.GetLength() - prefix_length - suffix_length;
1715 int new_midlen = m_sFwdslashPath.GetLength() - prefix_length - suffix_length;
1716 if (old_midlen < 0)
1717 old_midlen = 0;
1718 if (new_midlen < 0)
1719 new_midlen = 0;
1721 CString ret;
1722 if (prefix_length + suffix_length)
1724 ret = m_sOldFwdslashPath.Left(prefix_length);
1725 ret += L'{';
1727 ret += m_sOldFwdslashPath.Mid(prefix_length, old_midlen);
1728 ret += L" => ";
1729 ret += m_sFwdslashPath.Mid(prefix_length, new_midlen);
1730 if (prefix_length + suffix_length)
1732 ret += L'}';
1733 ret += m_sFwdslashPath.Mid(m_sFwdslashPath.GetLength() - suffix_length, suffix_length);
1735 return ret;