Keep the font size of 8 for the explorer property page
[TortoiseGit.git] / src / Git / TGitPath.cpp
blobf33f166d074d1430b8f928a4a42e1c2d419dd4c9
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - 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 <sys/stat.h>
33 extern CGit g_Git;
35 CTGitPath::CTGitPath(void)
36 : m_bDirectoryKnown(false)
37 , m_bIsDirectory(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_bIsWCRootKnown(false)
50 , m_bIsWCRoot(false)
51 , m_fileSize(0)
52 , m_Checked(false)
53 , m_Action(0)
54 , m_ParentNo(0)
55 , m_Stage(0)
59 CTGitPath::~CTGitPath(void)
62 // Create a TGitPath object from an unknown path type (same as using SetFromUnknown)
63 CTGitPath::CTGitPath(const CString& sUnknownPath) : CTGitPath()
65 SetFromUnknown(sUnknownPath);
68 CTGitPath::CTGitPath(const CString& sUnknownPath, bool bIsDirectory) : CTGitPath(sUnknownPath)
70 m_bDirectoryKnown = true;
71 m_bIsDirectory = bIsDirectory;
74 unsigned int CTGitPath::ParserAction(BYTE action)
76 //action=action.TrimLeft();
77 //TCHAR c=action.GetAt(0);
78 if(action == 'M')
79 m_Action|= LOGACTIONS_MODIFIED;
80 if(action == 'R')
81 m_Action|= LOGACTIONS_REPLACED;
82 if(action == 'A')
83 m_Action|= LOGACTIONS_ADDED;
84 if(action == 'D')
85 m_Action|= LOGACTIONS_DELETED;
86 if(action == 'U')
87 m_Action|= LOGACTIONS_UNMERGED;
88 if(action == 'K')
89 m_Action|= LOGACTIONS_DELETED;
90 if(action == 'C' )
91 m_Action|= LOGACTIONS_COPY;
92 if(action == 'T')
93 m_Action|= LOGACTIONS_MODIFIED;
95 return m_Action;
98 unsigned int CTGitPath::ParserAction(git_delta_t action)
100 if (action == GIT_DELTA_MODIFIED)
101 m_Action |= LOGACTIONS_MODIFIED;
102 if (action == GIT_DELTA_RENAMED)
103 m_Action |= LOGACTIONS_REPLACED;
104 if (action == GIT_DELTA_ADDED)
105 m_Action |= LOGACTIONS_ADDED;
106 if (action == GIT_DELTA_DELETED)
107 m_Action |= LOGACTIONS_DELETED;
108 if (action == GIT_DELTA_UNMODIFIED)
109 m_Action |= LOGACTIONS_UNMERGED;
110 if (action == GIT_DELTA_COPIED)
111 m_Action |= LOGACTIONS_COPY;
112 if (action == GIT_DELTA_TYPECHANGE)
113 m_Action |= LOGACTIONS_MODIFIED;
115 return m_Action;
118 void CTGitPath::SetFromGit(const char* pPath)
120 Reset();
121 if (!pPath)
122 return;
123 int len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, nullptr, 0);
124 if (len)
126 len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, m_sFwdslashPath.GetBuffer(len+1), len+1);
127 m_sFwdslashPath.ReleaseBuffer(len-1);
129 SanitizeRootPath(m_sFwdslashPath, true);
132 void CTGitPath::SetFromGit(const char* pPath, bool bIsDirectory)
134 SetFromGit(pPath);
135 m_bDirectoryKnown = true;
136 m_bIsDirectory = bIsDirectory;
139 void CTGitPath::SetFromGit(const TCHAR* pPath, bool bIsDirectory)
141 Reset();
142 if (pPath)
144 m_sFwdslashPath = pPath;
145 SanitizeRootPath(m_sFwdslashPath, true);
147 m_bDirectoryKnown = true;
148 m_bIsDirectory = bIsDirectory;
151 void CTGitPath::SetFromGit(const CString& sPath, CString* oldpath, int* bIsDirectory)
153 Reset();
154 m_sFwdslashPath = sPath;
155 SanitizeRootPath(m_sFwdslashPath, true);
156 if (bIsDirectory)
158 m_bDirectoryKnown = true;
159 m_bIsDirectory = *bIsDirectory != FALSE;
161 if(oldpath)
162 m_sOldFwdslashPath = *oldpath;
165 void CTGitPath::SetFromWin(LPCTSTR pPath)
167 Reset();
168 m_sBackslashPath = pPath;
169 m_sBackslashPath.Replace(L"\\\\?\\", L"");
170 SanitizeRootPath(m_sBackslashPath, false);
171 ATLASSERT(m_sBackslashPath.Find('/')<0);
173 void CTGitPath::SetFromWin(const CString& sPath)
175 Reset();
176 m_sBackslashPath = sPath;
177 m_sBackslashPath.Replace(L"\\\\?\\", L"");
178 SanitizeRootPath(m_sBackslashPath, false);
180 void CTGitPath::SetFromWin(LPCTSTR pPath, bool bIsDirectory)
182 Reset();
183 m_sBackslashPath = pPath;
184 m_bIsDirectory = bIsDirectory;
185 m_bDirectoryKnown = true;
186 SanitizeRootPath(m_sBackslashPath, false);
188 void CTGitPath::SetFromWin(const CString& sPath, bool bIsDirectory)
190 Reset();
191 m_sBackslashPath = sPath;
192 m_bIsDirectory = bIsDirectory;
193 m_bDirectoryKnown = true;
194 SanitizeRootPath(m_sBackslashPath, false);
196 void CTGitPath::SetFromUnknown(const CString& sPath)
198 Reset();
199 // Just set whichever path we think is most likely to be used
200 // GitAdminDir admin;
201 // CString p;
202 // if(admin.HasAdminDir(sPath,&p))
203 // SetFwdslashPath(sPath.Right(sPath.GetLength()-p.GetLength()));
204 // else
205 SetFwdslashPath(sPath);
208 void CTGitPath::UpdateCase()
210 m_sBackslashPath = CPathUtils::GetLongPathname(GetWinPathString());
211 CPathUtils::TrimTrailingPathDelimiter(m_sBackslashPath);
212 SanitizeRootPath(m_sBackslashPath, false);
213 SetFwdslashPath(m_sBackslashPath);
216 LPCTSTR CTGitPath::GetWinPath() const
218 if(IsEmpty())
219 return L"";
220 if(m_sBackslashPath.IsEmpty())
221 SetBackslashPath(m_sFwdslashPath);
222 return m_sBackslashPath;
224 // This is a temporary function, to be used during the migration to
225 // the path class. Ultimately, functions consuming paths should take a CTGitPath&, not a CString
226 const CString& CTGitPath::GetWinPathString() const
228 if(m_sBackslashPath.IsEmpty())
229 SetBackslashPath(m_sFwdslashPath);
230 return m_sBackslashPath;
233 const CString& CTGitPath::GetGitPathString() const
235 if(m_sFwdslashPath.IsEmpty())
236 SetFwdslashPath(m_sBackslashPath);
237 return m_sFwdslashPath;
240 const CString &CTGitPath::GetGitOldPathString() const
242 return m_sOldFwdslashPath;
245 const CString& CTGitPath::GetUIPathString() const
247 if (m_sUIPath.IsEmpty())
248 m_sUIPath = GetWinPathString();
249 return m_sUIPath;
252 void CTGitPath::SetFwdslashPath(const CString& sPath) const
254 CString path = sPath;
255 path.Replace('\\', '/');
257 // We don't leave a trailing /
258 path.TrimRight('/');
259 path.Replace(L"//?/", L"");
261 SanitizeRootPath(path, true);
263 path.Replace(L"file:////", L"file://");
264 m_sFwdslashPath = path;
267 void CTGitPath::SetBackslashPath(const CString& sPath) const
269 CString path = sPath;
270 path.Replace('/', '\\');
271 path.TrimRight('\\');
272 SanitizeRootPath(path, false);
273 m_sBackslashPath = path;
276 void CTGitPath::SanitizeRootPath(CString& sPath, bool bIsForwardPath) const
278 // Make sure to add the trailing slash to root paths such as 'C:'
279 if (sPath.GetLength() == 2 && sPath[1] == ':')
280 sPath += (bIsForwardPath) ? L'/' : L'\\';
283 bool CTGitPath::IsDirectory() const
285 if(!m_bDirectoryKnown)
286 UpdateAttributes();
287 return m_bIsDirectory;
290 bool CTGitPath::Exists() const
292 if (!m_bExistsKnown)
293 UpdateAttributes();
294 return m_bExists;
297 bool CTGitPath::Delete(bool bTrash, bool bShowErrorUI) const
299 EnsureBackslashPathSet();
300 ::SetFileAttributes(m_sBackslashPath, FILE_ATTRIBUTE_NORMAL);
301 bool bRet = false;
302 if (Exists())
304 if ((bTrash)||(IsDirectory()))
306 auto buf = std::make_unique<TCHAR[]>(m_sBackslashPath.GetLength() + 2);
307 wcscpy_s(buf.get(), m_sBackslashPath.GetLength() + 2, m_sBackslashPath);
308 buf[m_sBackslashPath.GetLength()] = L'\0';
309 buf[m_sBackslashPath.GetLength() + 1] = L'\0';
310 bRet = CTGitPathList::DeleteViaShell(buf.get(), bTrash, bShowErrorUI);
312 else
313 bRet = !!::DeleteFile(m_sBackslashPath);
315 m_bExists = false;
316 m_bExistsKnown = true;
317 return bRet;
320 __int64 CTGitPath::GetLastWriteTime(bool force /* = false */) const
322 if (!m_bLastWriteTimeKnown || force)
323 UpdateAttributes();
324 return m_lastWriteTime;
327 __int64 CTGitPath::GetFileSize() const
329 if(!m_bDirectoryKnown)
330 UpdateAttributes();
331 return m_fileSize;
334 bool CTGitPath::IsReadOnly() const
336 if(!m_bLastWriteTimeKnown)
337 UpdateAttributes();
338 return m_bIsReadOnly;
341 void CTGitPath::UpdateAttributes() const
343 EnsureBackslashPathSet();
344 WIN32_FILE_ATTRIBUTE_DATA attribs;
345 if (m_sBackslashPath.IsEmpty())
346 m_sLongBackslashPath = L".";
347 else if (m_sBackslashPath.GetLength() >= 248)
349 if (!PathIsRelative(m_sBackslashPath))
350 m_sLongBackslashPath = L"\\\\?\\" + m_sBackslashPath;
351 else
352 m_sLongBackslashPath = L"\\\\?\\" + g_Git.CombinePath(m_sBackslashPath);
354 if (GetFileAttributesEx(m_sBackslashPath.IsEmpty() || m_sBackslashPath.GetLength() >= 248 ? m_sLongBackslashPath : m_sBackslashPath, GetFileExInfoStandard, &attribs))
356 m_bIsDirectory = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
357 // don't cast directly to an __int64:
358 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284%28v=vs.85%29.aspx
359 // "Do not cast a pointer to a FILETIME structure to either a ULARGE_INTEGER* or __int64* value
360 // because it can cause alignment faults on 64-bit Windows."
361 m_lastWriteTime = static_cast<__int64>(attribs.ftLastWriteTime.dwHighDateTime) << 32 | attribs.ftLastWriteTime.dwLowDateTime;
362 if (m_bIsDirectory)
363 m_fileSize = 0;
364 else
365 m_fileSize = ((INT64)( (DWORD)(attribs.nFileSizeLow) ) | ( (INT64)( (DWORD)(attribs.nFileSizeHigh) )<<32 ));
366 m_bIsReadOnly = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
367 m_bExists = true;
369 else
371 m_bIsDirectory = false;
372 m_lastWriteTime = 0;
373 m_fileSize = 0;
374 DWORD err = GetLastError();
375 if ((err == ERROR_FILE_NOT_FOUND)||(err == ERROR_PATH_NOT_FOUND)||(err == ERROR_INVALID_NAME))
376 m_bExists = false;
377 else
379 m_bExists = true;
380 return;
383 m_bDirectoryKnown = true;
384 m_bLastWriteTimeKnown = true;
385 m_bExistsKnown = true;
388 CTGitPath CTGitPath::GetSubPath(const CTGitPath &root)
390 CTGitPath path;
392 if (CStringUtils::StartsWith(GetWinPathString(), root.GetWinPathString()))
394 CString str=GetWinPathString();
395 path.SetFromWin(str.Right(str.GetLength()-root.GetWinPathString().GetLength()-1));
397 return path;
400 void CTGitPath::EnsureBackslashPathSet() const
402 if(m_sBackslashPath.IsEmpty())
404 SetBackslashPath(m_sFwdslashPath);
405 ATLASSERT(IsEmpty() || !m_sBackslashPath.IsEmpty());
408 void CTGitPath::EnsureFwdslashPathSet() const
410 if(m_sFwdslashPath.IsEmpty())
412 SetFwdslashPath(m_sBackslashPath);
413 ATLASSERT(IsEmpty() || !m_sFwdslashPath.IsEmpty());
418 // Reset all the caches
419 void CTGitPath::Reset()
421 m_bDirectoryKnown = false;
422 m_bLastWriteTimeKnown = false;
423 m_bHasAdminDirKnown = false;
424 m_bIsValidOnWindowsKnown = false;
425 m_bIsAdminDirKnown = false;
426 m_bExistsKnown = false;
428 m_sBackslashPath.Empty();
429 m_sLongBackslashPath.Empty();
430 m_sFwdslashPath.Empty();
431 m_sUIPath.Empty();
432 m_sProjectRoot.Empty();
433 m_sOldFwdslashPath.Empty();
435 this->m_Action=0;
436 this->m_StatAdd.Empty();
437 this->m_StatDel.Empty();
438 m_ParentNo=0;
439 ATLASSERT(IsEmpty());
442 CTGitPath CTGitPath::GetDirectory() const
444 if ((IsDirectory())||(!Exists()))
445 return *this;
446 return GetContainingDirectory();
449 CTGitPath CTGitPath::GetContainingDirectory() const
451 EnsureBackslashPathSet();
453 CString sDirName = m_sBackslashPath.Left(m_sBackslashPath.ReverseFind('\\'));
454 if(sDirName.GetLength() == 2 && sDirName[1] == ':')
456 // This is a root directory, which needs a trailing slash
457 sDirName += '\\';
458 if(sDirName == m_sBackslashPath)
460 // We were clearly provided with a root path to start with - we should return nothing now
461 sDirName.Empty();
464 if(sDirName.GetLength() == 1 && sDirName[0] == '\\')
466 // We have an UNC path and we already are the root
467 sDirName.Empty();
469 CTGitPath retVal;
470 retVal.SetFromWin(sDirName);
471 return retVal;
474 CString CTGitPath::GetRootPathString() const
476 EnsureBackslashPathSet();
477 CString workingPath = m_sBackslashPath;
478 ATLVERIFY(::PathStripToRoot(CStrBuf(workingPath, MAX_PATH))); // MAX_PATH ok here.
479 return workingPath;
483 CString CTGitPath::GetFilename() const
485 //ATLASSERT(!IsDirectory());
486 return GetFileOrDirectoryName();
489 CString CTGitPath::GetFileOrDirectoryName() const
491 EnsureBackslashPathSet();
492 return m_sBackslashPath.Mid(m_sBackslashPath.ReverseFind('\\')+1);
495 CString CTGitPath::GetUIFileOrDirectoryName() const
497 GetUIPathString();
498 return m_sUIPath.Mid(m_sUIPath.ReverseFind('\\')+1);
501 CString CTGitPath::GetFileExtension() const
503 if(!IsDirectory())
505 EnsureBackslashPathSet();
506 int dotPos = m_sBackslashPath.ReverseFind('.');
507 int slashPos = m_sBackslashPath.ReverseFind('\\');
508 if (dotPos > slashPos)
509 return m_sBackslashPath.Mid(dotPos);
511 return CString();
513 CString CTGitPath::GetBaseFilename() const
515 int dot;
516 CString filename=GetFilename();
517 dot = filename.ReverseFind(L'.');
518 if(dot>0)
519 filename.Truncate(dot);
520 return filename;
523 bool CTGitPath::IsEmpty() const
525 // Check the backward slash path first, since the chance that this
526 // one is set is higher. In case of a 'false' return value it's a little
527 // bit faster.
528 return m_sBackslashPath.IsEmpty() && m_sFwdslashPath.IsEmpty();
531 // Test if both paths refer to the same item
532 // Ignores case and slash direction
533 bool CTGitPath::IsEquivalentTo(const CTGitPath& rhs) const
535 // Try and find a slash direction which avoids having to convert
536 // both filenames
537 if(!m_sBackslashPath.IsEmpty())
539 // *We've* got a \ path - make sure that the RHS also has a \ path
540 rhs.EnsureBackslashPathSet();
541 return CPathUtils::ArePathStringsEqualWithCase(m_sBackslashPath, rhs.m_sBackslashPath);
543 else
545 // Assume we've got a fwdslash path and make sure that the RHS has one
546 rhs.EnsureFwdslashPathSet();
547 return CPathUtils::ArePathStringsEqualWithCase(m_sFwdslashPath, rhs.m_sFwdslashPath);
551 bool CTGitPath::IsEquivalentToWithoutCase(const CTGitPath& rhs) const
553 // Try and find a slash direction which avoids having to convert
554 // both filenames
555 if(!m_sBackslashPath.IsEmpty())
557 // *We've* got a \ path - make sure that the RHS also has a \ path
558 rhs.EnsureBackslashPathSet();
559 return CPathUtils::ArePathStringsEqual(m_sBackslashPath, rhs.m_sBackslashPath);
561 else
563 // Assume we've got a fwdslash path and make sure that the RHS has one
564 rhs.EnsureFwdslashPathSet();
565 return CPathUtils::ArePathStringsEqual(m_sFwdslashPath, rhs.m_sFwdslashPath);
569 bool CTGitPath::IsAncestorOf(const CTGitPath& possibleDescendant) const
571 possibleDescendant.EnsureBackslashPathSet();
572 EnsureBackslashPathSet();
574 bool bPathStringsEqual = CPathUtils::ArePathStringsEqual(m_sBackslashPath, possibleDescendant.m_sBackslashPath.Left(m_sBackslashPath.GetLength()));
575 if (m_sBackslashPath.GetLength() >= possibleDescendant.GetWinPathString().GetLength())
577 return bPathStringsEqual;
580 return (bPathStringsEqual &&
581 ((possibleDescendant.m_sBackslashPath[m_sBackslashPath.GetLength()] == '\\')||
582 (m_sBackslashPath.GetLength()==3 && m_sBackslashPath[1]==':')));
585 // Get a string representing the file path, optionally with a base
586 // section stripped off the front.
587 CString CTGitPath::GetDisplayString(const CTGitPath* pOptionalBasePath /* = nullptr*/) const
589 EnsureFwdslashPathSet();
590 if (pOptionalBasePath)
592 // Find the length of the base-path without having to do an 'ensure' on it
593 int baseLength = max(pOptionalBasePath->m_sBackslashPath.GetLength(), pOptionalBasePath->m_sFwdslashPath.GetLength());
595 // Now, chop that baseLength of the front of the path
596 return m_sFwdslashPath.Mid(baseLength).TrimLeft('/');
598 return m_sFwdslashPath;
601 int CTGitPath::Compare(const CTGitPath& left, const CTGitPath& right)
603 left.EnsureBackslashPathSet();
604 right.EnsureBackslashPathSet();
605 return CStringUtils::FastCompareNoCase(left.m_sBackslashPath, right.m_sBackslashPath);
608 bool operator<(const CTGitPath& left, const CTGitPath& right)
610 return CTGitPath::Compare(left, right) < 0;
613 bool CTGitPath::PredLeftEquivalentToRight(const CTGitPath& left, const CTGitPath& right)
615 return left.IsEquivalentTo(right);
618 bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right)
620 if (left.IsAdminDir() && right.IsAdminDir())
622 CTGitPath l = left;
623 CTGitPath r = right;
626 l = l.GetContainingDirectory();
627 } while(l.HasAdminDir());
630 r = r.GetContainingDirectory();
631 } while(r.HasAdminDir());
632 return l.GetContainingDirectory().IsEquivalentTo(r.GetContainingDirectory());
634 return left.GetDirectory().IsEquivalentTo(right.GetDirectory());
637 bool CTGitPath::CheckChild(const CTGitPath &parent, const CTGitPath& child)
639 return parent.IsAncestorOf(child);
642 void CTGitPath::AppendRawString(const CString& sAppend)
644 EnsureFwdslashPathSet();
645 CString strCopy = m_sFwdslashPath += sAppend;
646 SetFromUnknown(strCopy);
649 void CTGitPath::AppendPathString(const CString& sAppend)
651 EnsureBackslashPathSet();
652 CString cleanAppend(sAppend);
653 cleanAppend.Replace(L'/', L'\\');
654 cleanAppend.TrimLeft(L'\\');
655 m_sBackslashPath.TrimRight(L'\\');
656 CString strCopy = m_sBackslashPath;
657 strCopy += L'\\';
658 strCopy += cleanAppend;
659 SetFromWin(strCopy);
662 bool CTGitPath::IsWCRoot() const
664 if (m_bIsWCRootKnown)
665 return m_bIsWCRoot;
667 m_bIsWCRootKnown = true;
668 m_bIsWCRoot = false;
670 CString topDirectory;
671 if (!IsDirectory() || !HasAdminDir(&topDirectory))
672 return m_bIsWCRoot;
674 if (IsEquivalentToWithoutCase(topDirectory))
675 m_bIsWCRoot = true;
677 return m_bIsWCRoot;
680 bool CTGitPath::HasSubmodules() const
682 if (HasAdminDir())
684 CString path = m_sProjectRoot;
685 path += L"\\.gitmodules";
686 if( PathFileExists(path) )
687 return true;
689 return false;
692 int CTGitPath::GetAdminDirMask() const
694 int status = 0;
695 if (!HasAdminDir())
696 return status;
698 // ITEMIS_INGIT will be revoked if necessary in TortoiseShell/ContextMenu.cpp
699 status |= ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER;
701 if (IsDirectory())
703 status |= ITEMIS_FOLDERINGIT;
704 if (IsWCRoot())
706 status |= ITEMIS_WCROOT;
708 if (IsRegisteredSubmoduleOfParentProject())
709 status |= ITEMIS_SUBMODULE;
713 CString dotGitPath;
714 bool isWorktree;
715 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath, &isWorktree);
716 if (HasStashDir(dotGitPath))
717 status |= ITEMIS_STASH;
719 if (PathFileExists(dotGitPath + L"svn"))
720 status |= ITEMIS_GITSVN;
722 if (isWorktree)
724 dotGitPath.Empty();
725 GitAdminDir::GetWorktreeAdminDirPath(m_sProjectRoot, dotGitPath);
728 if (PathFileExists(dotGitPath + L"BISECT_START"))
729 status |= ITEMIS_BISECT;
731 if (PathFileExists(dotGitPath + L"MERGE_HEAD"))
732 status |= ITEMIS_MERGEACTIVE;
734 if (PathFileExists(m_sProjectRoot + L"\\.gitmodules"))
735 status |= ITEMIS_SUBMODULECONTAINER;
737 return status;
740 bool CTGitPath::IsRegisteredSubmoduleOfParentProject(CString* parentProjectRoot /* nullptr */) const
742 CString topProjectDir;
743 if (!GitAdminDir::HasAdminDir(GetWinPathString(), false, &topProjectDir))
744 return false;
746 if (parentProjectRoot)
747 *parentProjectRoot = topProjectDir;
749 if (!PathFileExists(topProjectDir + L"\\.gitmodules"))
750 return false;
752 CAutoConfig config(true);
753 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(topProjectDir + L"\\.gitmodules"), GIT_CONFIG_LEVEL_APP, nullptr, FALSE);
754 CString relativePath = GetWinPathString().Mid(topProjectDir.GetLength());
755 relativePath.Replace(L'\\', L'/');
756 relativePath.Trim(L'/');
757 CStringA submodulePath = CUnicodeUtils::GetUTF8(relativePath);
758 if (git_config_foreach_match(config, "submodule\\..*\\.path", [](const git_config_entry *entry, void *data) { return entry->value == *(CStringA *)data ? GIT_EUSER : 0; }, &submodulePath) == GIT_EUSER)
759 return true;
760 return false;
763 bool CTGitPath::HasStashDir(const CString& dotGitPath) const
765 if (PathFileExists(dotGitPath + L"refs\\stash"))
766 return true;
768 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);
769 if (!hfile)
770 return false;
772 DWORD filesize = ::GetFileSize(hfile, nullptr);
773 if (filesize == 0 || filesize == INVALID_FILE_SIZE)
774 return false;
776 DWORD size = 0;
777 auto buff = std::make_unique<char[]>(filesize + 1);
778 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
779 buff[filesize] = '\0';
781 if (size != filesize)
782 return false;
784 for (DWORD i = 0; i < filesize;)
786 if (buff[i] == '#' || buff[i] == '^')
788 while (buff[i] != '\n')
790 ++i;
791 if (i == filesize)
792 break;
794 ++i;
797 if (i >= filesize)
798 break;
800 while (buff[i] != ' ')
802 ++i;
803 if (i == filesize)
804 break;
807 ++i;
808 if (i >= filesize)
809 break;
811 if (i <= filesize - 10 && (buff[i + 10] == '\n' || buff[i + 10] == '\0') && !strncmp("refs/stash", buff.get() + i, 10))
812 return true;
813 while (buff[i] != '\n')
815 ++i;
816 if (i == filesize)
817 break;
820 while (buff[i] == '\n')
822 ++i;
823 if (i == filesize)
824 break;
827 return false;
830 bool CTGitPath::HasStashDir() const
832 if (!HasAdminDir())
833 return false;
835 CString dotGitPath;
836 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
838 return HasStashDir(dotGitPath);
841 bool CTGitPath::HasGitSVNDir() const
843 if (!HasAdminDir())
844 return false;
846 CString dotGitPath;
847 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
849 return !!PathFileExists(dotGitPath + L"svn");
851 bool CTGitPath::IsBisectActive() const
853 if (!HasAdminDir())
854 return false;
856 CString dotGitPath;
857 GitAdminDir::GetWorktreeAdminDirPath(m_sProjectRoot, dotGitPath);
859 return !!PathFileExists(dotGitPath + L"BISECT_START");
861 bool CTGitPath::IsMergeActive() const
863 if (!HasAdminDir())
864 return false;
866 CString dotGitPath;
867 GitAdminDir::GetWorktreeAdminDirPath(m_sProjectRoot, dotGitPath);
869 return !!PathFileExists(dotGitPath + L"MERGE_HEAD");
871 bool CTGitPath::HasRebaseApply() const
873 if (!HasAdminDir())
874 return false;
876 CString dotGitPath;
877 GitAdminDir::GetWorktreeAdminDirPath(m_sProjectRoot, dotGitPath);
879 return !!PathFileExists(dotGitPath + L"rebase-apply");
882 bool CTGitPath::HasAdminDir(CString* ProjectTopDir /* = nullptr */, bool force /* = false */) const
884 if (m_bHasAdminDirKnown && !force)
886 if (ProjectTopDir)
887 *ProjectTopDir = m_sProjectRoot;
888 return m_bHasAdminDir;
891 EnsureBackslashPathSet();
892 bool isAdminDir = false;
893 m_bHasAdminDir = GitAdminDir::HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot, &isAdminDir);
894 m_bHasAdminDirKnown = true;
895 if ((m_bHasAdminDir || isAdminDir) && !m_bIsAdminDirKnown)
897 m_bIsAdminDir = isAdminDir;
898 m_bIsAdminDirKnown = true;
900 if (ProjectTopDir)
901 *ProjectTopDir = m_sProjectRoot;
902 return m_bHasAdminDir;
905 bool CTGitPath::IsAdminDir() const
907 if (m_bIsAdminDirKnown)
908 return m_bIsAdminDir;
910 EnsureBackslashPathSet();
911 m_bIsAdminDir = GitAdminDir::IsAdminDirPath(m_sBackslashPath);
912 m_bIsAdminDirKnown = true;
913 if (m_bIsAdminDir && !m_bHasAdminDirKnown)
915 m_bHasAdminDir = false;
916 m_bHasAdminDirKnown = true;
918 return m_bIsAdminDir;
921 bool CTGitPath::IsValidOnWindows() const
923 if (m_bIsValidOnWindowsKnown)
924 return m_bIsValidOnWindows;
926 m_bIsValidOnWindows = false;
927 EnsureBackslashPathSet();
928 CString sMatch = m_sBackslashPath + L"\r\n";
929 std::wstring sPattern;
930 // the 'file://' URL is just a normal windows path:
931 if (CStringUtils::StartsWithI(sMatch, L"file:\\\\"))
933 sMatch = sMatch.Mid((int)wcslen(L"file:\\\\"));
934 sMatch.TrimLeft(L'\\');
935 sPattern = L"^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$";
937 else
938 sPattern = L"^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$";
942 std::wregex rx(sPattern, std::regex_constants::icase | std::regex_constants::ECMAScript);
943 std::wsmatch match;
945 std::wstring rmatch = std::wstring((LPCTSTR)sMatch);
946 if (std::regex_match(rmatch, match, rx))
948 if (std::wstring(match[0]).compare(sMatch)==0)
949 m_bIsValidOnWindows = true;
951 if (m_bIsValidOnWindows)
953 // now check for illegal filenames
954 std::wregex rx2(L"\\\\(lpt\\d|com\\d|aux|nul|prn|con)(\\\\|$)", std::regex_constants::icase | std::regex_constants::ECMAScript);
955 rmatch = m_sBackslashPath;
956 if (std::regex_search(rmatch, rx2, std::regex_constants::match_default))
957 m_bIsValidOnWindows = false;
960 catch (std::exception&) {}
962 m_bIsValidOnWindowsKnown = true;
963 return m_bIsValidOnWindows;
966 //////////////////////////////////////////////////////////////////////////
968 CTGitPathList::CTGitPathList()
970 m_Action = 0;
973 // A constructor which allows a path list to be easily built which one initial entry in
974 CTGitPathList::CTGitPathList(const CTGitPath& firstEntry)
976 m_Action = 0;
977 AddPath(firstEntry);
979 int CTGitPathList::ParserFromLsFile(BYTE_VECTOR &out,bool /*staged*/)
981 size_t pos = 0;
982 CString one;
983 CTGitPath path;
984 CString part;
985 this->Clear();
987 while (pos < out.size())
989 one.Empty();
990 path.Reset();
992 CGit::StringAppend(&one, &out[pos], CP_UTF8);
993 int tabstart=0;
994 // m_Action is never used and propably never worked (needs to be set after path.SetFromGit)
995 // also dropped LOGACTIONS_CACHE for 'H'
996 // path.m_Action=path.ParserAction(out[pos]);
997 one.Tokenize(L"\t", tabstart);
999 if (tabstart < 0)
1000 return -1;
1002 CString pathstring = one.Right(one.GetLength() - tabstart);
1004 tabstart=0;
1006 part = one.Tokenize(L" ", tabstart); //Tag
1007 if (tabstart < 0)
1008 return -1;
1010 part = one.Tokenize(L" ", tabstart); //Mode
1011 if (tabstart < 0)
1012 return -1;
1013 int mode = wcstol(part, nullptr, 8);
1014 path.SetFromGit(pathstring, (mode & S_IFDIR) == S_IFDIR);
1016 part = one.Tokenize(L" ", tabstart); //Hash
1017 if (tabstart < 0)
1018 return -1;
1020 part = one.Tokenize(L"\t", tabstart); //Stage
1021 if (tabstart < 0)
1022 return -1;
1024 path.m_Stage = _wtol(part);
1026 this->AddPath(path);
1028 pos=out.findNextString(pos);
1030 return 0;
1033 int CTGitPathList::FillUnRev(unsigned int action, const CTGitPathList* list, CString* err)
1035 this->Clear();
1036 CTGitPath path;
1038 int count;
1039 if (!list)
1040 count=1;
1041 else
1042 count=list->GetCount();
1043 ATLASSERT(count > 0);
1044 for (int i = 0; i < count; ++i)
1046 CString cmd;
1047 CString ignored;
1048 if(action & CTGitPath::LOGACTIONS_IGNORE)
1049 ignored = L" -i";
1051 if (!list)
1053 cmd = L"git.exe ls-files --exclude-standard --full-name --others -z";
1054 cmd+=ignored;
1057 else
1059 ATLASSERT(!(*list)[i].GetWinPathString().IsEmpty());
1060 cmd.Format(L"git.exe ls-files --exclude-standard --full-name --others -z%s -- \"%s\"",
1061 (LPCTSTR)ignored,
1062 (*list)[i].GetWinPath());
1065 BYTE_VECTOR out, errb;
1066 out.clear();
1067 if (g_Git.Run(cmd, &out, &errb))
1069 if (err != nullptr)
1070 CGit::StringAppend(err, errb.data(), CP_UTF8, (int)errb.size());
1071 return -1;
1074 size_t pos = 0;
1075 CString one;
1076 while (pos < out.size())
1078 one.Empty();
1079 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1080 if(!one.IsEmpty())
1082 //SetFromGit will clear all status
1083 if (CStringUtils::EndsWith(one, L'/'))
1085 one.Truncate(one.GetLength() - 1);
1086 path.SetFromGit(one, true);
1088 else
1089 path.SetFromGit(one);
1090 path.m_Action=action;
1091 AddPath(path);
1093 pos=out.findNextString(pos);
1097 return 0;
1100 int CTGitPathList::FillBasedOnIndexFlags(unsigned short flag, unsigned short flagextended, const CTGitPathList* list /*nullptr*/)
1102 Clear();
1103 CTGitPath path;
1105 CAutoRepository repository(g_Git.GetGitRepository());
1106 if (!repository)
1107 return -1;
1109 CAutoIndex index;
1110 if (git_repository_index(index.GetPointer(), repository))
1111 return -1;
1113 int count;
1114 if (list == nullptr)
1115 count = 1;
1116 else
1117 count = list->GetCount();
1118 for (int j = 0; j < count; ++j)
1120 for (size_t i = 0, ecount = git_index_entrycount(index); i < ecount; ++i)
1122 const git_index_entry *e = git_index_get_byindex(index, i);
1124 if (!e || !((e->flags & flag) || (e->flags_extended & flagextended)) || !e->path)
1125 continue;
1127 CString one = CUnicodeUtils::GetUnicode(e->path);
1129 if (!(!list || (*list)[j].GetWinPathString().IsEmpty() || one == (*list)[j].GetGitPathString() || (PathIsDirectory(g_Git.CombinePath((*list)[j].GetWinPathString())) && CStringUtils::StartsWith(one, (*list)[j].GetGitPathString() + L'/'))))
1130 continue;
1132 //SetFromGit will clear all status
1133 path.SetFromGit(one, (e->mode & S_IFDIR) == S_IFDIR);
1134 if (e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE)
1135 path.m_Action = CTGitPath::LOGACTIONS_SKIPWORKTREE;
1136 else if (e->flags & GIT_IDXENTRY_VALID)
1137 path.m_Action = CTGitPath::LOGACTIONS_ASSUMEVALID;
1138 AddPath(path);
1141 RemoveDuplicates();
1142 return 0;
1144 int CTGitPathList::ParserFromLog(BYTE_VECTOR &log, bool parseDeletes /*false*/)
1146 this->Clear();
1147 std::map<CString, size_t> duplicateMap;
1148 size_t pos = 0;
1149 CTGitPath path;
1150 m_Action=0;
1151 size_t logend = log.size();
1152 while (pos < logend)
1154 path.Reset();
1155 if(log[pos]=='\n')
1156 ++pos;
1158 if (pos >= logend)
1159 return -1;
1161 if(log[pos]==':')
1163 bool merged=false;
1164 if (pos + 1 >= logend)
1165 return -1;
1166 if (log[pos + 1] == ':')
1168 merged = true;
1169 ++pos;
1172 int modenew = 0;
1173 int modeold = 0;
1174 size_t end = log.find(0, pos);
1175 size_t actionstart = BYTE_VECTOR::npos;
1176 size_t file1 = BYTE_VECTOR::npos, file2 = BYTE_VECTOR::npos;
1177 if (end != BYTE_VECTOR::npos && end > 7)
1179 modeold = strtol((const char*)&log[pos + 1], nullptr, 8);
1180 modenew = strtol((const char*)&log[pos + 7], nullptr, 8);
1181 actionstart=log.find(' ',end-6);
1182 pos=actionstart;
1184 if (actionstart != BYTE_VECTOR::npos && actionstart > 0)
1186 ++actionstart;
1187 if (actionstart >= logend)
1188 return -1;
1190 file1 = log.find(0,actionstart);
1191 if (file1 != BYTE_VECTOR::npos)
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 != BYTE_VECTOR::npos)
1202 ++file1;
1203 pos=file1;
1208 CString pathname1;
1209 CString pathname2;
1211 if (file1 != BYTE_VECTOR::npos)
1212 CGit::StringAppend(&pathname1, &log[file1], CP_UTF8);
1213 if (file2 != BYTE_VECTOR::npos)
1214 CGit::StringAppend(&pathname2, &log[file2], CP_UTF8);
1216 if (actionstart == BYTE_VECTOR::npos)
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 // reset submodule/folder status if a staged entry is not a folder
1226 if (p.IsDirectory() && ((modeold && !(modeold & S_IFDIR)) || (modenew && !(modenew & S_IFDIR))))
1227 p.UnsetDirectoryStatus();
1229 if(merged)
1230 p.m_Action |= CTGitPath::LOGACTIONS_MERGED;
1231 m_Action |= p.m_Action;
1233 else
1235 int ac=path.ParserAction(log[actionstart] );
1236 ac |= merged?CTGitPath::LOGACTIONS_MERGED:0;
1238 int isSubmodule = FALSE;
1239 if (ac & (CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_UNMERGED))
1240 isSubmodule = (modeold & S_IFDIR) == S_IFDIR;
1241 else
1242 isSubmodule = (modenew & S_IFDIR) == S_IFDIR;
1244 path.SetFromGit(pathname1, &pathname2, &isSubmodule);
1245 path.m_Action=ac;
1246 //action must be set after setfromgit. SetFromGit will clear all status.
1247 this->m_Action|=ac;
1249 AddPath(path);
1250 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1253 else
1255 path.Reset();
1256 CString StatAdd;
1257 CString StatDel;
1258 CString file1;
1259 CString file2;
1260 int isSubmodule = FALSE;
1261 size_t tabstart = log.find('\t', pos);
1262 if (tabstart != BYTE_VECTOR::npos)
1264 int modenew = strtol((const char*)&log[pos + 2], nullptr, 8);
1265 isSubmodule = (modenew & S_IFDIR) == S_IFDIR;
1266 log[tabstart]=0;
1267 CGit::StringAppend(&StatAdd, &log[pos], CP_UTF8);
1268 pos=tabstart+1;
1271 tabstart=log.find('\t',pos);
1272 if (tabstart != BYTE_VECTOR::npos)
1274 log[tabstart]=0;
1276 CGit::StringAppend(&StatDel, &log[pos], CP_UTF8);
1277 pos=tabstart+1;
1280 if(log[pos] == 0) //rename
1282 ++pos;
1283 CGit::StringAppend(&file2, &log[pos], CP_UTF8);
1284 size_t sec = log.find(0, pos);
1285 if (sec != BYTE_VECTOR::npos)
1287 ++sec;
1288 CGit::StringAppend(&file1, &log[sec], CP_UTF8);
1290 pos=sec;
1292 else
1294 CGit::StringAppend(&file1, &log[pos], CP_UTF8);
1296 path.SetFromGit(file1, &file2, &isSubmodule);
1298 auto existing = duplicateMap.find(path.GetGitPathString());
1299 if (existing != duplicateMap.end())
1301 CTGitPath& p = m_paths[existing->second];
1302 p.m_StatAdd = StatAdd;
1303 p.m_StatDel = StatDel;
1305 else
1307 //path.SetFromGit(pathname);
1308 if (parseDeletes)
1310 path.m_StatAdd = L"0";
1311 path.m_StatDel = L"0";
1312 path.m_Action |= CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_MISSING;
1314 else
1316 path.m_StatAdd=StatAdd;
1317 path.m_StatDel=StatDel;
1319 AddPath(path);
1320 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1323 pos=log.findNextString(pos);
1325 return 0;
1328 void CTGitPathList::AddPath(const CTGitPath& newPath)
1330 m_paths.push_back(newPath);
1331 m_commonBaseDirectory.Reset();
1333 int CTGitPathList::GetCount() const
1335 return (int)m_paths.size();
1337 bool CTGitPathList::IsEmpty() const
1339 return m_paths.empty();
1341 void CTGitPathList::Clear()
1343 m_Action = 0;
1344 m_paths.clear();
1345 m_commonBaseDirectory.Reset();
1348 const CTGitPath& CTGitPathList::operator[](INT_PTR index) const
1350 ATLASSERT(index >= 0 && index < (INT_PTR)m_paths.size());
1351 return m_paths[index];
1354 bool CTGitPathList::AreAllPathsFiles() const
1356 // Look through the vector for any directories - if we find them, return false
1357 return std::find_if(m_paths.cbegin(), m_paths.cend(), std::mem_fn(&CTGitPath::IsDirectory)) == m_paths.cend();
1360 #if defined(_MFC_VER)
1362 bool CTGitPathList::LoadFromFile(const CTGitPath& filename)
1364 Clear();
1367 CString strLine;
1368 CStdioFile file(filename.GetWinPath(), CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite);
1370 // for every selected file/folder
1371 CTGitPath path;
1372 while (file.ReadString(strLine))
1374 path.SetFromUnknown(strLine);
1375 AddPath(path);
1377 file.Close();
1379 catch (CFileException* pE)
1381 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException loading target file list\n");
1382 TCHAR error[10000] = {0};
1383 pE->GetErrorMessage(error, 10000);
1384 // CMessageBox::Show(nullptr, error, L"TortoiseGit", MB_ICONERROR);
1385 pE->Delete();
1386 return false;
1388 return true;
1391 bool CTGitPathList::WriteToFile(const CString& sFilename, bool bANSI /* = false */) const
1395 if (bANSI)
1397 CStdioFile file(sFilename, CFile::typeText | CFile::modeReadWrite | CFile::modeCreate);
1398 for (const auto& path : m_paths)
1400 CStringA line = CStringA(path.GetGitPathString()) + '\n';
1401 file.Write(line, line.GetLength());
1403 file.Close();
1405 else
1407 CStdioFile file(sFilename, CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
1408 for (const auto& path : m_paths)
1409 file.WriteString(path.GetGitPathString() + L'\n');
1410 file.Close();
1413 catch (CFileException* pE)
1415 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in writing temp file\n");
1416 pE->Delete();
1417 return false;
1419 return true;
1422 void CTGitPathList::LoadFromAsteriskSeparatedString(const CString& sPathString)
1424 int pos = 0;
1425 CString temp;
1426 for(;;)
1428 temp = sPathString.Tokenize(L"*", pos);
1429 if(temp.IsEmpty())
1430 break;
1431 AddPath(CTGitPath(CPathUtils::GetLongPathname(temp)));
1435 CString CTGitPathList::CreateAsteriskSeparatedString() const
1437 CString sRet;
1438 for (const auto& path : m_paths)
1440 if (!sRet.IsEmpty())
1441 sRet += L'*';
1442 sRet += path.GetWinPathString();
1444 return sRet;
1446 #endif // _MFC_VER
1448 bool
1449 CTGitPathList::AreAllPathsFilesInOneDirectory() const
1451 // Check if all the paths are files and in the same directory
1452 m_commonBaseDirectory.Reset();
1453 for (const auto& path : m_paths)
1455 if (path.IsDirectory())
1456 return false;
1457 const CTGitPath& baseDirectory = path.GetDirectory();
1458 if(m_commonBaseDirectory.IsEmpty())
1459 m_commonBaseDirectory = baseDirectory;
1460 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1462 // Different path
1463 m_commonBaseDirectory.Reset();
1464 return false;
1467 return true;
1470 CTGitPath CTGitPathList::GetCommonDirectory() const
1472 if (m_commonBaseDirectory.IsEmpty())
1474 for (const auto& path : m_paths)
1476 const CTGitPath& baseDirectory = path.GetDirectory();
1477 if(m_commonBaseDirectory.IsEmpty())
1478 m_commonBaseDirectory = baseDirectory;
1479 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1481 // Different path
1482 m_commonBaseDirectory.Reset();
1483 break;
1487 // since we only checked strings, not paths,
1488 // we have to make sure now that we really return a *path* here
1489 for (const auto& path : m_paths)
1491 if (!m_commonBaseDirectory.IsAncestorOf(path))
1493 m_commonBaseDirectory = m_commonBaseDirectory.GetContainingDirectory();
1494 break;
1497 return m_commonBaseDirectory;
1500 CTGitPath CTGitPathList::GetCommonRoot() const
1502 if (IsEmpty())
1503 return CTGitPath();
1505 if (GetCount() == 1)
1506 return m_paths[0];
1508 // first entry is common root for itself
1509 // (add trailing '\\' to detect partial matches of the last path element)
1510 CString root = m_paths[0].GetWinPathString() + L'\\';
1511 int rootLength = root.GetLength();
1513 // determine common path string prefix
1514 for (auto it = m_paths.cbegin() + 1; it != m_paths.cend(); ++it)
1516 CString path = it->GetWinPathString() + L'\\';
1518 int newLength = CStringUtils::GetMatchingLength(root, path);
1519 if (newLength != rootLength)
1521 root.Delete(newLength, rootLength);
1522 rootLength = newLength;
1526 // remove the last (partial) path element
1527 if (rootLength > 0)
1528 root.Delete(root.ReverseFind(L'\\'), rootLength);
1530 // done
1531 return CTGitPath(root);
1534 void CTGitPathList::SortByPathname(bool bReverse /*= false*/)
1536 std::sort(m_paths.begin(), m_paths.end());
1537 if (bReverse)
1538 std::reverse(m_paths.begin(), m_paths.end());
1541 void CTGitPathList::DeleteAllFiles(bool bTrash, bool bFilesOnly, bool bShowErrorUI)
1543 if (m_paths.empty())
1544 return;
1545 PathVector::const_iterator it;
1546 SortByPathname(true); // nested ones first
1548 CString sPaths;
1549 for (it = m_paths.cbegin(); it != m_paths.cend(); ++it)
1551 if ((it->Exists()) && ((it->IsDirectory() != bFilesOnly) || !bFilesOnly))
1553 if (!it->IsDirectory())
1554 ::SetFileAttributes(it->GetWinPath(), FILE_ATTRIBUTE_NORMAL);
1556 sPaths += it->GetWinPath();
1557 sPaths += '\0';
1560 if (sPaths.IsEmpty())
1561 return;
1562 sPaths += '\0';
1563 sPaths += '\0';
1564 DeleteViaShell((LPCTSTR)sPaths, bTrash, bShowErrorUI);
1565 Clear();
1568 bool CTGitPathList::DeleteViaShell(LPCTSTR path, bool bTrash, bool bShowErrorUI)
1570 SHFILEOPSTRUCT shop = {0};
1571 shop.wFunc = FO_DELETE;
1572 shop.pFrom = path;
1573 shop.fFlags = FOF_NOCONFIRMATION|FOF_NO_CONNECTED_ELEMENTS;
1574 if (!bShowErrorUI)
1575 shop.fFlags |= FOF_NOERRORUI | FOF_SILENT;
1576 if (bTrash)
1577 shop.fFlags |= FOF_ALLOWUNDO;
1578 const bool bRet = (SHFileOperation(&shop) == 0);
1579 return bRet;
1582 void CTGitPathList::RemoveDuplicates()
1584 SortByPathname();
1585 // Remove the duplicates
1586 // (Unique moves them to the end of the vector, then erase chops them off)
1587 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::PredLeftEquivalentToRight), m_paths.end());
1590 void CTGitPathList::RemoveAdminPaths()
1592 PathVector::iterator it;
1593 for(it = m_paths.begin(); it != m_paths.end(); )
1595 if (it->IsAdminDir())
1597 m_paths.erase(it);
1598 it = m_paths.begin();
1600 else
1601 ++it;
1605 void CTGitPathList::RemovePath(const CTGitPath& path)
1607 PathVector::iterator it;
1608 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1610 if (it->IsEquivalentTo(path))
1612 m_paths.erase(it);
1613 return;
1618 void CTGitPathList::RemoveItem(CTGitPath & path)
1620 PathVector::iterator it;
1621 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1623 if (CPathUtils::ArePathStringsEqualWithCase(it->GetGitPathString(), path.GetGitPathString()))
1625 m_paths.erase(it);
1626 return;
1630 void CTGitPathList::RemoveChildren()
1632 SortByPathname();
1633 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::CheckChild), m_paths.end());
1636 bool CTGitPathList::IsEqual(const CTGitPathList& list)
1638 if (list.GetCount() != GetCount())
1639 return false;
1640 for (int i=0; i<list.GetCount(); ++i)
1642 if (!list[i].IsEquivalentTo(m_paths[i]))
1643 return false;
1645 return true;
1648 const CTGitPath* CTGitPathList::LookForGitPath(const CString& path)
1650 int i=0;
1651 for (i = 0; i < this->GetCount(); ++i)
1653 if (CPathUtils::ArePathStringsEqualWithCase((*this)[i].GetGitPathString(), path))
1654 return (CTGitPath*)&(*this)[i];
1656 return nullptr;
1659 CString CTGitPath::GetActionName(unsigned int action)
1661 if(action & CTGitPath::LOGACTIONS_UNMERGED)
1662 return MAKEINTRESOURCE(IDS_PATHACTIONS_CONFLICT);
1663 if(action & CTGitPath::LOGACTIONS_ADDED)
1664 return MAKEINTRESOURCE(IDS_PATHACTIONS_ADD);
1665 if (action & CTGitPath::LOGACTIONS_MISSING)
1666 return MAKEINTRESOURCE(IDS_PATHACTIONS_MISSING);
1667 if(action & CTGitPath::LOGACTIONS_DELETED)
1668 return MAKEINTRESOURCE(IDS_PATHACTIONS_DELETE);
1669 if(action & CTGitPath::LOGACTIONS_MERGED )
1670 return MAKEINTRESOURCE(IDS_PATHACTIONS_MERGED);
1672 if(action & CTGitPath::LOGACTIONS_MODIFIED)
1673 return MAKEINTRESOURCE(IDS_PATHACTIONS_MODIFIED);
1674 if(action & CTGitPath::LOGACTIONS_REPLACED)
1675 return MAKEINTRESOURCE(IDS_PATHACTIONS_RENAME);
1676 if(action & CTGitPath::LOGACTIONS_COPY)
1677 return MAKEINTRESOURCE(IDS_PATHACTIONS_COPY);
1679 if (action & CTGitPath::LOGACTIONS_ASSUMEVALID)
1680 return MAKEINTRESOURCE(IDS_PATHACTIONS_ASSUMEUNCHANGED);
1681 if (action & CTGitPath::LOGACTIONS_SKIPWORKTREE)
1682 return MAKEINTRESOURCE(IDS_PATHACTIONS_SKIPWORKTREE);
1684 if (action & CTGitPath::LOGACTIONS_IGNORE)
1685 return MAKEINTRESOURCE(IDS_PATHACTIONS_IGNORED);
1687 return MAKEINTRESOURCE(IDS_PATHACTIONS_UNKNOWN);
1690 CString CTGitPath::GetActionName() const
1692 return GetActionName(m_Action);
1695 unsigned int CTGitPathList::GetAction()
1697 return m_Action;
1700 CString CTGitPath::GetAbbreviatedRename() const
1702 if (GetGitOldPathString().IsEmpty())
1703 return GetFileOrDirectoryName();
1705 // Find common prefix which ends with a slash
1706 int prefix_length = 0;
1707 for (int i = 0, maxLength = min(m_sOldFwdslashPath.GetLength(), m_sFwdslashPath.GetLength()); i < maxLength; ++i)
1709 if (m_sOldFwdslashPath[i] != m_sFwdslashPath[i])
1710 break;
1711 if (m_sOldFwdslashPath[i] == L'/')
1712 prefix_length = i + 1;
1715 LPCTSTR oldName = (LPCTSTR)m_sOldFwdslashPath + m_sOldFwdslashPath.GetLength();
1716 LPCTSTR newName = (LPCTSTR)m_sFwdslashPath + m_sFwdslashPath.GetLength();
1718 int suffix_length = 0;
1719 int prefix_adjust_for_slash = (prefix_length ? 1 : 0);
1720 while ((LPCTSTR)m_sOldFwdslashPath + prefix_length - prefix_adjust_for_slash <= oldName &&
1721 (LPCTSTR)m_sFwdslashPath + prefix_length - prefix_adjust_for_slash <= newName &&
1722 *oldName == *newName)
1724 if (*oldName == L'/')
1725 suffix_length = m_sOldFwdslashPath.GetLength() - (int)(oldName - (LPCTSTR)m_sOldFwdslashPath);
1726 --oldName;
1727 --newName;
1731 * pfx{old_midlen => new_midlen}sfx
1732 * {pfx-old => pfx-new}sfx
1733 * pfx{sfx-old => sfx-new}
1734 * name-old => name-new
1736 int old_midlen = m_sOldFwdslashPath.GetLength() - prefix_length - suffix_length;
1737 int new_midlen = m_sFwdslashPath.GetLength() - prefix_length - suffix_length;
1738 if (old_midlen < 0)
1739 old_midlen = 0;
1740 if (new_midlen < 0)
1741 new_midlen = 0;
1743 CString ret;
1744 if (prefix_length + suffix_length)
1746 ret = m_sOldFwdslashPath.Left(prefix_length);
1747 ret += L'{';
1749 ret += m_sOldFwdslashPath.Mid(prefix_length, old_midlen);
1750 ret += L" => ";
1751 ret += m_sFwdslashPath.Mid(prefix_length, new_midlen);
1752 if (prefix_length + suffix_length)
1754 ret += L'}';
1755 ret += m_sFwdslashPath.Mid(m_sFwdslashPath.GetLength() - suffix_length, suffix_length);
1757 return ret;