Update some images for the documentation
[TortoiseGit.git] / src / Git / TGitPath.cpp
blob8b0559a8702aa5cf55fa57d28d2c074c2c7c7573
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - 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 int CTGitPath::ParserAction(BYTE action)
70 //action=action.TrimLeft();
71 //TCHAR c=action.GetAt(0);
72 if(action == 'M')
73 m_Action|= LOGACTIONS_MODIFIED;
74 if(action == 'R')
75 m_Action|= LOGACTIONS_REPLACED;
76 if(action == 'A')
77 m_Action|= LOGACTIONS_ADDED;
78 if(action == 'D')
79 m_Action|= LOGACTIONS_DELETED;
80 if(action == 'U')
81 m_Action|= LOGACTIONS_UNMERGED;
82 if(action == 'K')
83 m_Action|= LOGACTIONS_DELETED;
84 if(action == 'C' )
85 m_Action|= LOGACTIONS_COPY;
86 if(action == 'T')
87 m_Action|= LOGACTIONS_MODIFIED;
89 return m_Action;
92 int CTGitPath::ParserAction(git_delta_t action)
94 if (action == GIT_DELTA_MODIFIED)
95 m_Action |= LOGACTIONS_MODIFIED;
96 if (action == GIT_DELTA_RENAMED)
97 m_Action |= LOGACTIONS_REPLACED;
98 if (action == GIT_DELTA_ADDED)
99 m_Action |= LOGACTIONS_ADDED;
100 if (action == GIT_DELTA_DELETED)
101 m_Action |= LOGACTIONS_DELETED;
102 if (action == GIT_DELTA_UNMODIFIED)
103 m_Action |= LOGACTIONS_UNMERGED;
104 if (action == GIT_DELTA_COPIED)
105 m_Action |= LOGACTIONS_COPY;
106 if (action == GIT_DELTA_TYPECHANGE)
107 m_Action |= LOGACTIONS_MODIFIED;
109 return m_Action;
112 void CTGitPath::SetFromGit(const char* pPath)
114 Reset();
115 if (!pPath)
116 return;
117 int len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, nullptr, 0);
118 if (len)
120 len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, m_sFwdslashPath.GetBuffer(len+1), len+1);
121 m_sFwdslashPath.ReleaseBuffer(len-1);
123 SanitizeRootPath(m_sFwdslashPath, true);
126 void CTGitPath::SetFromGit(const char* pPath, bool bIsDirectory)
128 SetFromGit(pPath);
129 m_bDirectoryKnown = true;
130 m_bIsDirectory = bIsDirectory;
133 void CTGitPath::SetFromGit(const TCHAR* pPath, bool bIsDirectory)
135 Reset();
136 if (pPath)
138 m_sFwdslashPath = pPath;
139 SanitizeRootPath(m_sFwdslashPath, true);
141 m_bDirectoryKnown = true;
142 m_bIsDirectory = bIsDirectory;
145 void CTGitPath::SetFromGit(const CString& sPath, CString* oldpath, int* bIsDirectory)
147 Reset();
148 m_sFwdslashPath = sPath;
149 SanitizeRootPath(m_sFwdslashPath, true);
150 if (bIsDirectory)
152 m_bDirectoryKnown = true;
153 m_bIsDirectory = *bIsDirectory != FALSE;
155 if(oldpath)
156 m_sOldFwdslashPath = *oldpath;
159 void CTGitPath::SetFromWin(LPCTSTR pPath)
161 Reset();
162 m_sBackslashPath = pPath;
163 m_sBackslashPath.Replace(L"\\\\?\\", L"");
164 SanitizeRootPath(m_sBackslashPath, false);
165 ATLASSERT(m_sBackslashPath.Find('/')<0);
167 void CTGitPath::SetFromWin(const CString& sPath)
169 Reset();
170 m_sBackslashPath = sPath;
171 m_sBackslashPath.Replace(L"\\\\?\\", L"");
172 SanitizeRootPath(m_sBackslashPath, false);
174 void CTGitPath::SetFromWin(LPCTSTR pPath, bool bIsDirectory)
176 Reset();
177 m_sBackslashPath = pPath;
178 m_bIsDirectory = bIsDirectory;
179 m_bDirectoryKnown = true;
180 SanitizeRootPath(m_sBackslashPath, false);
182 void CTGitPath::SetFromWin(const CString& sPath, bool bIsDirectory)
184 Reset();
185 m_sBackslashPath = sPath;
186 m_bIsDirectory = bIsDirectory;
187 m_bDirectoryKnown = true;
188 SanitizeRootPath(m_sBackslashPath, false);
190 void CTGitPath::SetFromUnknown(const CString& sPath)
192 Reset();
193 // Just set whichever path we think is most likely to be used
194 // GitAdminDir admin;
195 // CString p;
196 // if(admin.HasAdminDir(sPath,&p))
197 // SetFwdslashPath(sPath.Right(sPath.GetLength()-p.GetLength()));
198 // else
199 SetFwdslashPath(sPath);
202 LPCTSTR CTGitPath::GetWinPath() const
204 if(IsEmpty())
205 return L"";
206 if(m_sBackslashPath.IsEmpty())
207 SetBackslashPath(m_sFwdslashPath);
208 return m_sBackslashPath;
210 // This is a temporary function, to be used during the migration to
211 // the path class. Ultimately, functions consuming paths should take a CTGitPath&, not a CString
212 const CString& CTGitPath::GetWinPathString() const
214 if(m_sBackslashPath.IsEmpty())
215 SetBackslashPath(m_sFwdslashPath);
216 return m_sBackslashPath;
219 const CString& CTGitPath::GetGitPathString() const
221 if(m_sFwdslashPath.IsEmpty())
222 SetFwdslashPath(m_sBackslashPath);
223 return m_sFwdslashPath;
226 const CString &CTGitPath::GetGitOldPathString() const
228 return m_sOldFwdslashPath;
231 const CString& CTGitPath::GetUIPathString() const
233 if (m_sUIPath.IsEmpty())
234 m_sUIPath = GetWinPathString();
235 return m_sUIPath;
238 void CTGitPath::SetFwdslashPath(const CString& sPath) const
240 CString path = sPath;
241 path.Replace('\\', '/');
243 // We don't leave a trailing /
244 path.TrimRight('/');
245 path.Replace(L"//?/", L"");
247 SanitizeRootPath(path, true);
249 path.Replace(L"file:////", L"file://");
250 m_sFwdslashPath = path;
253 void CTGitPath::SetBackslashPath(const CString& sPath) const
255 CString path = sPath;
256 path.Replace('/', '\\');
257 path.TrimRight('\\');
258 SanitizeRootPath(path, false);
259 m_sBackslashPath = path;
262 void CTGitPath::SanitizeRootPath(CString& sPath, bool bIsForwardPath) const
264 // Make sure to add the trailing slash to root paths such as 'C:'
265 if (sPath.GetLength() == 2 && sPath[1] == ':')
266 sPath += (bIsForwardPath) ? L'/' : L'\\';
269 bool CTGitPath::IsDirectory() const
271 if(!m_bDirectoryKnown)
272 UpdateAttributes();
273 return m_bIsDirectory;
276 bool CTGitPath::Exists() const
278 if (!m_bExistsKnown)
279 UpdateAttributes();
280 return m_bExists;
283 bool CTGitPath::Delete(bool bTrash, bool bShowErrorUI) const
285 EnsureBackslashPathSet();
286 ::SetFileAttributes(m_sBackslashPath, FILE_ATTRIBUTE_NORMAL);
287 bool bRet = false;
288 if (Exists())
290 if ((bTrash)||(IsDirectory()))
292 auto buf = std::make_unique<TCHAR[]>(m_sBackslashPath.GetLength() + 2);
293 wcscpy_s(buf.get(), m_sBackslashPath.GetLength() + 2, m_sBackslashPath);
294 buf[m_sBackslashPath.GetLength()] = L'\0';
295 buf[m_sBackslashPath.GetLength() + 1] = L'\0';
296 bRet = CTGitPathList::DeleteViaShell(buf.get(), bTrash, bShowErrorUI);
298 else
299 bRet = !!::DeleteFile(m_sBackslashPath);
301 m_bExists = false;
302 m_bExistsKnown = true;
303 return bRet;
306 __int64 CTGitPath::GetLastWriteTime() const
308 if(!m_bLastWriteTimeKnown)
309 UpdateAttributes();
310 return m_lastWriteTime;
313 __int64 CTGitPath::GetFileSize() const
315 if(!m_bDirectoryKnown)
316 UpdateAttributes();
317 return m_fileSize;
320 bool CTGitPath::IsReadOnly() const
322 if(!m_bLastWriteTimeKnown)
323 UpdateAttributes();
324 return m_bIsReadOnly;
327 void CTGitPath::UpdateAttributes() const
329 EnsureBackslashPathSet();
330 WIN32_FILE_ATTRIBUTE_DATA attribs;
331 if (m_sBackslashPath.GetLength() >= 248)
333 if (!PathIsRelative(m_sBackslashPath))
334 m_sLongBackslashPath = L"\\\\?\\" + m_sBackslashPath;
335 else
336 m_sLongBackslashPath = L"\\\\?\\" + g_Git.CombinePath(m_sBackslashPath);
338 if(GetFileAttributesEx(m_sBackslashPath.GetLength() >= 248 ? m_sLongBackslashPath : m_sBackslashPath, GetFileExInfoStandard, &attribs))
340 m_bIsDirectory = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
341 // don't cast directly to an __int64:
342 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284%28v=vs.85%29.aspx
343 // "Do not cast a pointer to a FILETIME structure to either a ULARGE_INTEGER* or __int64* value
344 // because it can cause alignment faults on 64-bit Windows."
345 m_lastWriteTime = static_cast<__int64>(attribs.ftLastWriteTime.dwHighDateTime) << 32 | attribs.ftLastWriteTime.dwLowDateTime;
346 if (m_bIsDirectory)
347 m_fileSize = 0;
348 else
349 m_fileSize = ((INT64)( (DWORD)(attribs.nFileSizeLow) ) | ( (INT64)( (DWORD)(attribs.nFileSizeHigh) )<<32 ));
350 m_bIsReadOnly = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
351 m_bExists = true;
353 else
355 m_bIsDirectory = false;
356 m_lastWriteTime = 0;
357 m_fileSize = 0;
358 DWORD err = GetLastError();
359 if ((err == ERROR_FILE_NOT_FOUND)||(err == ERROR_PATH_NOT_FOUND)||(err == ERROR_INVALID_NAME))
360 m_bExists = false;
361 else
363 m_bExists = true;
364 return;
367 m_bDirectoryKnown = true;
368 m_bLastWriteTimeKnown = true;
369 m_bExistsKnown = true;
372 CTGitPath CTGitPath::GetSubPath(const CTGitPath &root)
374 CTGitPath path;
376 if (CStringUtils::StartsWith(GetWinPathString(), root.GetWinPathString()))
378 CString str=GetWinPathString();
379 path.SetFromWin(str.Right(str.GetLength()-root.GetWinPathString().GetLength()-1));
381 return path;
384 void CTGitPath::EnsureBackslashPathSet() const
386 if(m_sBackslashPath.IsEmpty())
388 SetBackslashPath(m_sFwdslashPath);
389 ATLASSERT(IsEmpty() || !m_sBackslashPath.IsEmpty());
392 void CTGitPath::EnsureFwdslashPathSet() const
394 if(m_sFwdslashPath.IsEmpty())
396 SetFwdslashPath(m_sBackslashPath);
397 ATLASSERT(IsEmpty() || !m_sFwdslashPath.IsEmpty());
402 // Reset all the caches
403 void CTGitPath::Reset()
405 m_bDirectoryKnown = false;
406 m_bLastWriteTimeKnown = false;
407 m_bHasAdminDirKnown = false;
408 m_bIsValidOnWindowsKnown = false;
409 m_bIsAdminDirKnown = false;
410 m_bExistsKnown = false;
412 m_sBackslashPath.Empty();
413 m_sFwdslashPath.Empty();
414 this->m_Action=0;
415 this->m_StatAdd.Empty();
416 this->m_StatDel.Empty();
417 m_ParentNo=0;
418 ATLASSERT(IsEmpty());
421 CTGitPath CTGitPath::GetDirectory() const
423 if ((IsDirectory())||(!Exists()))
424 return *this;
425 return GetContainingDirectory();
428 CTGitPath CTGitPath::GetContainingDirectory() const
430 EnsureBackslashPathSet();
432 CString sDirName = m_sBackslashPath.Left(m_sBackslashPath.ReverseFind('\\'));
433 if(sDirName.GetLength() == 2 && sDirName[1] == ':')
435 // This is a root directory, which needs a trailing slash
436 sDirName += '\\';
437 if(sDirName == m_sBackslashPath)
439 // We were clearly provided with a root path to start with - we should return nothing now
440 sDirName.Empty();
443 if(sDirName.GetLength() == 1 && sDirName[0] == '\\')
445 // We have an UNC path and we already are the root
446 sDirName.Empty();
448 CTGitPath retVal;
449 retVal.SetFromWin(sDirName);
450 return retVal;
453 CString CTGitPath::GetRootPathString() const
455 EnsureBackslashPathSet();
456 CString workingPath = m_sBackslashPath;
457 ATLVERIFY(::PathStripToRoot(CStrBuf(workingPath, MAX_PATH))); // MAX_PATH ok here.
458 return workingPath;
462 CString CTGitPath::GetFilename() const
464 //ATLASSERT(!IsDirectory());
465 return GetFileOrDirectoryName();
468 CString CTGitPath::GetFileOrDirectoryName() const
470 EnsureBackslashPathSet();
471 return m_sBackslashPath.Mid(m_sBackslashPath.ReverseFind('\\')+1);
474 CString CTGitPath::GetUIFileOrDirectoryName() const
476 GetUIPathString();
477 return m_sUIPath.Mid(m_sUIPath.ReverseFind('\\')+1);
480 CString CTGitPath::GetFileExtension() const
482 if(!IsDirectory())
484 EnsureBackslashPathSet();
485 int dotPos = m_sBackslashPath.ReverseFind('.');
486 int slashPos = m_sBackslashPath.ReverseFind('\\');
487 if (dotPos > slashPos)
488 return m_sBackslashPath.Mid(dotPos);
490 return CString();
492 CString CTGitPath::GetBaseFilename() const
494 int dot;
495 CString filename=GetFilename();
496 dot = filename.ReverseFind(L'.');
497 if(dot>0)
498 filename.Truncate(dot);
499 return filename;
502 bool CTGitPath::ArePathStringsEqual(const CString& sP1, const CString& sP2)
504 int length = sP1.GetLength();
505 if(length != sP2.GetLength())
507 // Different lengths
508 return false;
510 // We work from the end of the strings, because path differences
511 // are more likely to occur at the far end of a string
512 LPCTSTR pP1Start = sP1;
513 LPCTSTR pP1 = pP1Start+(length-1);
514 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
515 while(length-- > 0)
517 if(_totlower(*pP1--) != _totlower(*pP2--))
518 return false;
520 return true;
523 bool CTGitPath::ArePathStringsEqualWithCase(const CString& sP1, const CString& sP2)
525 int length = sP1.GetLength();
526 if(length != sP2.GetLength())
528 // Different lengths
529 return false;
531 // We work from the end of the strings, because path differences
532 // are more likely to occur at the far end of a string
533 LPCTSTR pP1Start = sP1;
534 LPCTSTR pP1 = pP1Start+(length-1);
535 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
536 while(length-- > 0)
538 if((*pP1--) != (*pP2--))
539 return false;
541 return true;
544 bool CTGitPath::IsEmpty() const
546 // Check the backward slash path first, since the chance that this
547 // one is set is higher. In case of a 'false' return value it's a little
548 // bit faster.
549 return m_sBackslashPath.IsEmpty() && m_sFwdslashPath.IsEmpty();
552 // Test if both paths refer to the same item
553 // Ignores case and slash direction
554 bool CTGitPath::IsEquivalentTo(const CTGitPath& rhs) const
556 // Try and find a slash direction which avoids having to convert
557 // both filenames
558 if(!m_sBackslashPath.IsEmpty())
560 // *We've* got a \ path - make sure that the RHS also has a \ path
561 rhs.EnsureBackslashPathSet();
562 return ArePathStringsEqualWithCase(m_sBackslashPath, rhs.m_sBackslashPath);
564 else
566 // Assume we've got a fwdslash path and make sure that the RHS has one
567 rhs.EnsureFwdslashPathSet();
568 return ArePathStringsEqualWithCase(m_sFwdslashPath, rhs.m_sFwdslashPath);
572 bool CTGitPath::IsEquivalentToWithoutCase(const CTGitPath& rhs) const
574 // Try and find a slash direction which avoids having to convert
575 // both filenames
576 if(!m_sBackslashPath.IsEmpty())
578 // *We've* got a \ path - make sure that the RHS also has a \ path
579 rhs.EnsureBackslashPathSet();
580 return ArePathStringsEqual(m_sBackslashPath, rhs.m_sBackslashPath);
582 else
584 // Assume we've got a fwdslash path and make sure that the RHS has one
585 rhs.EnsureFwdslashPathSet();
586 return ArePathStringsEqual(m_sFwdslashPath, rhs.m_sFwdslashPath);
590 bool CTGitPath::IsAncestorOf(const CTGitPath& possibleDescendant) const
592 possibleDescendant.EnsureBackslashPathSet();
593 EnsureBackslashPathSet();
595 bool bPathStringsEqual = ArePathStringsEqual(m_sBackslashPath, possibleDescendant.m_sBackslashPath.Left(m_sBackslashPath.GetLength()));
596 if (m_sBackslashPath.GetLength() >= possibleDescendant.GetWinPathString().GetLength())
598 return bPathStringsEqual;
601 return (bPathStringsEqual &&
602 ((possibleDescendant.m_sBackslashPath[m_sBackslashPath.GetLength()] == '\\')||
603 (m_sBackslashPath.GetLength()==3 && m_sBackslashPath[1]==':')));
606 // Get a string representing the file path, optionally with a base
607 // section stripped off the front.
608 CString CTGitPath::GetDisplayString(const CTGitPath* pOptionalBasePath /* = nullptr*/) const
610 EnsureFwdslashPathSet();
611 if (pOptionalBasePath)
613 // Find the length of the base-path without having to do an 'ensure' on it
614 int baseLength = max(pOptionalBasePath->m_sBackslashPath.GetLength(), pOptionalBasePath->m_sFwdslashPath.GetLength());
616 // Now, chop that baseLength of the front of the path
617 return m_sFwdslashPath.Mid(baseLength).TrimLeft('/');
619 return m_sFwdslashPath;
622 int CTGitPath::Compare(const CTGitPath& left, const CTGitPath& right)
624 left.EnsureBackslashPathSet();
625 right.EnsureBackslashPathSet();
626 return CStringUtils::FastCompareNoCase(left.m_sBackslashPath, right.m_sBackslashPath);
629 bool operator<(const CTGitPath& left, const CTGitPath& right)
631 return CTGitPath::Compare(left, right) < 0;
634 bool CTGitPath::PredLeftEquivalentToRight(const CTGitPath& left, const CTGitPath& right)
636 return left.IsEquivalentTo(right);
639 bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right)
641 if (left.IsAdminDir() && right.IsAdminDir())
643 CTGitPath l = left;
644 CTGitPath r = right;
647 l = l.GetContainingDirectory();
648 } while(l.HasAdminDir());
651 r = r.GetContainingDirectory();
652 } while(r.HasAdminDir());
653 return l.GetContainingDirectory().IsEquivalentTo(r.GetContainingDirectory());
655 return left.GetDirectory().IsEquivalentTo(right.GetDirectory());
658 bool CTGitPath::CheckChild(const CTGitPath &parent, const CTGitPath& child)
660 return parent.IsAncestorOf(child);
663 void CTGitPath::AppendRawString(const CString& sAppend)
665 EnsureFwdslashPathSet();
666 CString strCopy = m_sFwdslashPath += sAppend;
667 SetFromUnknown(strCopy);
670 void CTGitPath::AppendPathString(const CString& sAppend)
672 EnsureBackslashPathSet();
673 CString cleanAppend(sAppend);
674 cleanAppend.Replace(L'/', L'\\');
675 cleanAppend.TrimLeft(L'\\');
676 m_sBackslashPath.TrimRight(L'\\');
677 CString strCopy = m_sBackslashPath;
678 strCopy += L'\\';
679 strCopy += cleanAppend;
680 SetFromWin(strCopy);
683 bool CTGitPath::IsWCRoot() const
685 if (m_bIsWCRootKnown)
686 return m_bIsWCRoot;
688 m_bIsWCRootKnown = true;
689 m_bIsWCRoot = false;
691 CString topDirectory;
692 if (!IsDirectory() || !HasAdminDir(&topDirectory))
693 return m_bIsWCRoot;
695 if (IsEquivalentToWithoutCase(topDirectory))
696 m_bIsWCRoot = true;
698 return m_bIsWCRoot;
701 bool CTGitPath::HasSubmodules() const
703 if (HasAdminDir())
705 CString path = m_sProjectRoot;
706 path += L"\\.gitmodules";
707 if( PathFileExists(path) )
708 return true;
710 return false;
713 int CTGitPath::GetAdminDirMask() const
715 int status = 0;
716 if (!HasAdminDir())
717 return status;
719 // ITEMIS_INGIT will be revoked if necessary in TortoiseShell/ContextMenu.cpp
720 status |= ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER;
722 if (IsDirectory())
724 status |= ITEMIS_FOLDERINGIT;
725 if (IsWCRoot())
727 status |= ITEMIS_WCROOT;
729 CString topProjectDir;
730 if (GitAdminDir::HasAdminDir(GetWinPathString(), false, &topProjectDir))
732 if (PathFileExists(topProjectDir + L"\\.gitmodules"))
734 CAutoConfig config(true);
735 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(topProjectDir + L"\\.gitmodules"), GIT_CONFIG_LEVEL_APP, FALSE);
736 CString relativePath = GetWinPathString().Mid(topProjectDir.GetLength());
737 relativePath.Replace(L'\\', L'/');
738 relativePath.Trim(L'/');
739 CStringA submodulePath = CUnicodeUtils::GetUTF8(relativePath);
740 if (git_config_foreach_match(config, "submodule\\..*\\.path",
741 [](const git_config_entry *entry, void *data) { return entry->value == *(CStringA *)data ? GIT_EUSER : 0; }, &submodulePath) == GIT_EUSER)
742 status |= ITEMIS_SUBMODULE;
748 CString dotGitPath;
749 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
751 if (PathFileExists(dotGitPath + L"BISECT_START"))
752 status |= ITEMIS_BISECT;
754 if (PathFileExists(dotGitPath + L"MERGE_HEAD"))
755 status |= ITEMIS_MERGEACTIVE;
757 if (HasStashDir(dotGitPath))
758 status |= ITEMIS_STASH;
760 if (PathFileExists(dotGitPath + L"svn"))
761 status |= ITEMIS_GITSVN;
763 if (PathFileExists(m_sProjectRoot + L"\\.gitmodules"))
764 status |= ITEMIS_SUBMODULECONTAINER;
766 return status;
769 bool CTGitPath::HasStashDir(const CString& dotGitPath) const
771 if (PathFileExists(dotGitPath + L"refs\\stash"))
772 return true;
774 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);
775 if (!hfile)
776 return false;
778 DWORD filesize = ::GetFileSize(hfile, nullptr);
779 if (filesize == 0)
780 return false;
782 DWORD size = 0;
783 auto buff = std::make_unique<char[]>(filesize + 1);
784 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
785 buff.get()[filesize] = '\0';
787 if (size != filesize)
788 return false;
790 for (DWORD i = 0; i < filesize;)
792 if (buff[i] == '#' || buff[i] == '^')
794 while (buff[i] != '\n')
796 ++i;
797 if (i == filesize)
798 break;
800 ++i;
803 if (i >= filesize)
804 break;
806 while (buff[i] != ' ')
808 ++i;
809 if (i == filesize)
810 break;
813 ++i;
814 if (i >= filesize)
815 break;
817 if (i <= filesize - 10 && (buff[i + 10] == '\n' || buff[i + 10] == '\0') && !strncmp("refs/stash", buff.get() + i, 10))
818 return true;
819 while (buff[i] != '\n')
821 ++i;
822 if (i == filesize)
823 break;
826 while (buff[i] == '\n')
828 ++i;
829 if (i == filesize)
830 break;
833 return false;
836 bool CTGitPath::HasStashDir() const
838 if (!HasAdminDir())
839 return false;
841 CString dotGitPath;
842 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
844 return HasStashDir(dotGitPath);
847 bool CTGitPath::HasGitSVNDir() const
849 if (!HasAdminDir())
850 return false;
852 CString dotGitPath;
853 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
855 return !!PathFileExists(dotGitPath + L"svn");
857 bool CTGitPath::IsBisectActive() const
859 if (!HasAdminDir())
860 return false;
862 CString dotGitPath;
863 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
865 return !!PathFileExists(dotGitPath + L"BISECT_START");
867 bool CTGitPath::IsMergeActive() const
869 if (!HasAdminDir())
870 return false;
872 CString dotGitPath;
873 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
875 return !!PathFileExists(dotGitPath + L"MERGE_HEAD");
877 bool CTGitPath::HasRebaseApply() const
879 if (!HasAdminDir())
880 return false;
882 CString dotGitPath;
883 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
885 return !!PathFileExists(dotGitPath + L"rebase-apply");
888 bool CTGitPath::HasAdminDir(CString *ProjectTopDir) const
890 if (m_bHasAdminDirKnown)
892 if (ProjectTopDir)
893 *ProjectTopDir = m_sProjectRoot;
894 return m_bHasAdminDir;
897 EnsureBackslashPathSet();
898 bool isAdminDir = false;
899 m_bHasAdminDir = GitAdminDir::HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
900 m_bHasAdminDirKnown = true;
901 if ((m_bHasAdminDir || isAdminDir) && !m_bIsAdminDirKnown)
903 m_bIsAdminDir = isAdminDir;
904 m_bIsAdminDirKnown = true;
906 if (ProjectTopDir)
907 *ProjectTopDir = m_sProjectRoot;
908 return m_bHasAdminDir;
911 bool CTGitPath::IsAdminDir() const
913 if (m_bIsAdminDirKnown)
914 return m_bIsAdminDir;
916 EnsureBackslashPathSet();
917 m_bIsAdminDir = GitAdminDir::IsAdminDirPath(m_sBackslashPath);
918 m_bIsAdminDirKnown = true;
919 if (m_bIsAdminDir && !m_bIsAdminDirKnown)
921 m_bHasAdminDir = false;
922 m_bIsAdminDirKnown = true;
924 return m_bIsAdminDir;
927 bool CTGitPath::IsValidOnWindows() const
929 if (m_bIsValidOnWindowsKnown)
930 return m_bIsValidOnWindows;
932 m_bIsValidOnWindows = false;
933 EnsureBackslashPathSet();
934 CString sMatch = m_sBackslashPath + L"\r\n";
935 std::wstring sPattern;
936 // the 'file://' URL is just a normal windows path:
937 if (CStringUtils::StartsWithI(sMatch, L"file:\\\\"))
939 sMatch = sMatch.Mid(7);
940 sMatch.TrimLeft(L'\\');
941 sPattern = L"^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$";
943 else
944 sPattern = L"^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$";
948 std::tr1::wregex rx(sPattern, std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
949 std::tr1::wsmatch match;
951 std::wstring rmatch = std::wstring((LPCTSTR)sMatch);
952 if (std::tr1::regex_match(rmatch, match, rx))
954 if (std::wstring(match[0]).compare(sMatch)==0)
955 m_bIsValidOnWindows = true;
957 if (m_bIsValidOnWindows)
959 // now check for illegal filenames
960 std::tr1::wregex rx2(L"\\\\(lpt\\d|com\\d|aux|nul|prn|con)(\\\\|$)", std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
961 rmatch = m_sBackslashPath;
962 if (std::tr1::regex_search(rmatch, rx2, std::tr1::regex_constants::match_default))
963 m_bIsValidOnWindows = false;
966 catch (std::exception&) {}
968 m_bIsValidOnWindowsKnown = true;
969 return m_bIsValidOnWindows;
972 //////////////////////////////////////////////////////////////////////////
974 CTGitPathList::CTGitPathList()
976 m_Action = 0;
979 // A constructor which allows a path list to be easily built which one initial entry in
980 CTGitPathList::CTGitPathList(const CTGitPath& firstEntry)
982 m_Action = 0;
983 AddPath(firstEntry);
985 int CTGitPathList::ParserFromLsFile(BYTE_VECTOR &out,bool /*staged*/)
987 size_t pos = 0;
988 CString one;
989 CTGitPath path;
990 CString part;
991 this->Clear();
993 while (pos < out.size())
995 one.Empty();
996 path.Reset();
998 CGit::StringAppend(&one, &out[pos], CP_UTF8);
999 int tabstart=0;
1000 // m_Action is never used and propably never worked (needs to be set after path.SetFromGit)
1001 // also dropped LOGACTIONS_CACHE for 'H'
1002 // path.m_Action=path.ParserAction(out[pos]);
1003 one.Tokenize(L"\t", tabstart);
1005 if (tabstart < 0)
1006 return -1;
1008 CString pathstring = one.Right(one.GetLength() - tabstart);
1010 tabstart=0;
1012 part = one.Tokenize(L" ", tabstart); //Tag
1013 if (tabstart < 0)
1014 return -1;
1016 part = one.Tokenize(L" ", tabstart); //Mode
1017 if (tabstart < 0)
1018 return -1;
1019 int mode = wcstol(part, nullptr, 8);
1020 path.SetFromGit(pathstring, (mode & S_IFDIR) == S_IFDIR);
1022 part = one.Tokenize(L" ", tabstart); //Hash
1023 if (tabstart < 0)
1024 return -1;
1026 part = one.Tokenize(L"\t", tabstart); //Stage
1027 if (tabstart < 0)
1028 return -1;
1030 path.m_Stage = _wtol(part);
1032 this->AddPath(path);
1034 pos=out.findNextString(pos);
1036 return 0;
1039 int CTGitPathList::FillUnRev(unsigned int action, const CTGitPathList* list, CString* err)
1041 this->Clear();
1042 CTGitPath path;
1044 int count;
1045 if (!list)
1046 count=1;
1047 else
1048 count=list->GetCount();
1049 for (int i = 0; i < count; ++i)
1051 CString cmd;
1052 CString ignored;
1053 if(action & CTGitPath::LOGACTIONS_IGNORE)
1054 ignored = L" -i";
1056 if (!list)
1058 cmd = L"git.exe ls-files --exclude-standard --full-name --others -z";
1059 cmd+=ignored;
1062 else
1064 cmd.Format(L"git.exe ls-files --exclude-standard --full-name --others -z%s -- \"%s\"",
1065 (LPCTSTR)ignored,
1066 (*list)[i].GetWinPath());
1069 BYTE_VECTOR out, errb;
1070 out.clear();
1071 if (g_Git.Run(cmd, &out, &errb))
1073 if (err != nullptr)
1074 CGit::StringAppend(err, &errb[0], CP_UTF8, (int)errb.size());
1075 return -1;
1078 size_t pos = 0;
1079 CString one;
1080 while (pos < out.size())
1082 one.Empty();
1083 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1084 if(!one.IsEmpty())
1086 //SetFromGit will clear all status
1087 if (CStringUtils::EndsWith(one, L'/'))
1089 one.Truncate(one.GetLength() - 1);
1090 path.SetFromGit(one, true);
1092 else
1093 path.SetFromGit(one);
1094 path.m_Action=action;
1095 AddPath(path);
1097 pos=out.findNextString(pos);
1101 return 0;
1104 int CTGitPathList::FillBasedOnIndexFlags(unsigned short flag, unsigned short flagextended, const CTGitPathList* list /*nullptr*/)
1106 Clear();
1107 CTGitPath path;
1109 CAutoRepository repository(g_Git.GetGitRepository());
1110 if (!repository)
1111 return -1;
1113 CAutoIndex index;
1114 if (git_repository_index(index.GetPointer(), repository))
1115 return -1;
1117 int count;
1118 if (list == nullptr)
1119 count = 1;
1120 else
1121 count = list->GetCount();
1122 for (int j = 0; j < count; ++j)
1124 for (size_t i = 0, ecount = git_index_entrycount(index); i < ecount; ++i)
1126 const git_index_entry *e = git_index_get_byindex(index, i);
1128 if (!e || !((e->flags & flag) || (e->flags_extended & flagextended)) || !e->path)
1129 continue;
1131 CString one = CUnicodeUtils::GetUnicode(e->path);
1133 if (!(!list || (*list)[j].GetWinPathString().IsEmpty() || one == (*list)[j].GetGitPathString() || (PathIsDirectory(g_Git.CombinePath((*list)[j].GetWinPathString())) && CStringUtils::StartsWith(one, (*list)[j].GetGitPathString() + L'/'))))
1134 continue;
1136 //SetFromGit will clear all status
1137 path.SetFromGit(one, (e->mode & S_IFDIR) == S_IFDIR);
1138 if (e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE)
1139 path.m_Action = CTGitPath::LOGACTIONS_SKIPWORKTREE;
1140 else if (e->flags & GIT_IDXENTRY_VALID)
1141 path.m_Action = CTGitPath::LOGACTIONS_ASSUMEVALID;
1142 AddPath(path);
1145 RemoveDuplicates();
1146 return 0;
1148 int CTGitPathList::ParserFromLog(BYTE_VECTOR &log, bool parseDeletes /*false*/)
1150 this->Clear();
1151 std::map<CString, size_t> duplicateMap;
1152 size_t pos = 0;
1153 CTGitPath path;
1154 m_Action=0;
1155 size_t logend = log.size();
1156 while (pos < logend)
1158 path.Reset();
1159 if(log[pos]=='\n')
1160 ++pos;
1162 if (pos >= logend)
1163 return -1;
1165 if(log[pos]==':')
1167 bool merged=false;
1168 if (pos + 1 >= logend)
1169 return -1;
1170 if (log[pos + 1] == ':')
1172 merged = true;
1173 ++pos;
1176 int modenew = 0;
1177 int modeold = 0;
1178 size_t end = log.find(0, pos);
1179 size_t actionstart = BYTE_VECTOR::npos;
1180 size_t file1 = BYTE_VECTOR::npos, file2 = BYTE_VECTOR::npos;
1181 if (end != BYTE_VECTOR::npos && end > 7)
1183 modeold = strtol((const char*)&log[pos + 1], nullptr, 8);
1184 modenew = strtol((const char*)&log[pos + 7], nullptr, 8);
1185 actionstart=log.find(' ',end-6);
1186 pos=actionstart;
1188 if (actionstart != BYTE_VECTOR::npos && actionstart > 0)
1190 ++actionstart;
1191 if (actionstart >= logend)
1192 return -1;
1194 file1 = log.find(0,actionstart);
1195 if (file1 != BYTE_VECTOR::npos)
1197 ++file1;
1198 pos=file1;
1200 if( log[actionstart] == 'C' || log[actionstart] == 'R' )
1202 file2=file1;
1203 file1 = log.find(0,file1);
1204 if (file1 != BYTE_VECTOR::npos)
1206 ++file1;
1207 pos=file1;
1212 CString pathname1;
1213 CString pathname2;
1215 if (file1 != BYTE_VECTOR::npos)
1216 CGit::StringAppend(&pathname1, &log[file1], CP_UTF8);
1217 if (file2 != BYTE_VECTOR::npos)
1218 CGit::StringAppend(&pathname2, &log[file2], CP_UTF8);
1220 if (actionstart == BYTE_VECTOR::npos)
1221 return -1;
1223 auto existing = duplicateMap.find(pathname1);
1224 if (existing != duplicateMap.end())
1226 CTGitPath& p = m_paths[existing->second];
1227 p.ParserAction(log[actionstart]);
1229 // reset submodule/folder status if a staged entry is not a folder
1230 if (p.IsDirectory() && ((modeold && !(modeold & S_IFDIR)) || (modenew && !(modenew & S_IFDIR))))
1231 p.UnsetDirectoryStatus();
1233 if(merged)
1234 p.m_Action |= CTGitPath::LOGACTIONS_MERGED;
1235 m_Action |= p.m_Action;
1237 else
1239 int ac=path.ParserAction(log[actionstart] );
1240 ac |= merged?CTGitPath::LOGACTIONS_MERGED:0;
1242 int isSubmodule = FALSE;
1243 if (ac & (CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_UNMERGED))
1244 isSubmodule = (modeold & S_IFDIR) == S_IFDIR;
1245 else
1246 isSubmodule = (modenew & S_IFDIR) == S_IFDIR;
1248 path.SetFromGit(pathname1, &pathname2, &isSubmodule);
1249 path.m_Action=ac;
1250 //action must be set after setfromgit. SetFromGit will clear all status.
1251 this->m_Action|=ac;
1253 AddPath(path);
1254 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1257 else
1259 path.Reset();
1260 CString StatAdd;
1261 CString StatDel;
1262 CString file1;
1263 CString file2;
1264 int isSubmodule = FALSE;
1265 size_t tabstart = log.find('\t', pos);
1266 if (tabstart != BYTE_VECTOR::npos)
1268 int modenew = strtol((const char*)&log[pos + 2], nullptr, 8);
1269 isSubmodule = (modenew & S_IFDIR) == S_IFDIR;
1270 log[tabstart]=0;
1271 CGit::StringAppend(&StatAdd, &log[pos], CP_UTF8);
1272 pos=tabstart+1;
1275 tabstart=log.find('\t',pos);
1276 if (tabstart != BYTE_VECTOR::npos)
1278 log[tabstart]=0;
1280 CGit::StringAppend(&StatDel, &log[pos], CP_UTF8);
1281 pos=tabstart+1;
1284 if(log[pos] == 0) //rename
1286 ++pos;
1287 CGit::StringAppend(&file2, &log[pos], CP_UTF8);
1288 size_t sec = log.find(0, pos);
1289 if (sec != BYTE_VECTOR::npos)
1291 ++sec;
1292 CGit::StringAppend(&file1, &log[sec], CP_UTF8);
1294 pos=sec;
1296 else
1298 CGit::StringAppend(&file1, &log[pos], CP_UTF8);
1300 path.SetFromGit(file1, &file2, &isSubmodule);
1302 auto existing = duplicateMap.find(path.GetGitPathString());
1303 if (existing != duplicateMap.end())
1305 CTGitPath& p = m_paths[existing->second];
1306 p.m_StatAdd = StatAdd;
1307 p.m_StatDel = StatDel;
1309 else
1311 //path.SetFromGit(pathname);
1312 if (parseDeletes)
1314 path.m_StatAdd = L"0";
1315 path.m_StatDel = L"0";
1316 path.m_Action |= CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_MISSING;
1318 else
1320 path.m_StatAdd=StatAdd;
1321 path.m_StatDel=StatDel;
1323 AddPath(path);
1324 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1327 pos=log.findNextString(pos);
1329 return 0;
1332 void CTGitPathList::AddPath(const CTGitPath& newPath)
1334 m_paths.push_back(newPath);
1335 m_commonBaseDirectory.Reset();
1337 int CTGitPathList::GetCount() const
1339 return (int)m_paths.size();
1341 bool CTGitPathList::IsEmpty() const
1343 return m_paths.empty();
1345 void CTGitPathList::Clear()
1347 m_paths.clear();
1348 m_commonBaseDirectory.Reset();
1351 const CTGitPath& CTGitPathList::operator[](INT_PTR index) const
1353 ATLASSERT(index >= 0 && index < (INT_PTR)m_paths.size());
1354 return m_paths[index];
1357 bool CTGitPathList::AreAllPathsFiles() const
1359 // Look through the vector for any directories - if we find them, return false
1360 return std::find_if(m_paths.cbegin(), m_paths.cend(), std::mem_fun_ref(&CTGitPath::IsDirectory)) == m_paths.end();
1363 #if defined(_MFC_VER)
1365 bool CTGitPathList::LoadFromFile(const CTGitPath& filename)
1367 Clear();
1370 CString strLine;
1371 CStdioFile file(filename.GetWinPath(), CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite);
1373 // for every selected file/folder
1374 CTGitPath path;
1375 while (file.ReadString(strLine))
1377 path.SetFromUnknown(strLine);
1378 AddPath(path);
1380 file.Close();
1382 catch (CFileException* pE)
1384 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException loading target file list\n");
1385 TCHAR error[10000] = {0};
1386 pE->GetErrorMessage(error, 10000);
1387 // CMessageBox::Show(nullptr, error, L"TortoiseGit", MB_ICONERROR);
1388 pE->Delete();
1389 return false;
1391 return true;
1394 bool CTGitPathList::WriteToFile(const CString& sFilename, bool bANSI /* = false */) const
1398 if (bANSI)
1400 CStdioFile file(sFilename, CFile::typeText | CFile::modeReadWrite | CFile::modeCreate);
1401 for (const auto& path : m_paths)
1403 CStringA line = CStringA(path.GetGitPathString()) + '\n';
1404 file.Write(line, line.GetLength());
1406 file.Close();
1408 else
1410 CStdioFile file(sFilename, CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
1411 for (const auto& path : m_paths)
1412 file.WriteString(path.GetGitPathString() + L'\n');
1413 file.Close();
1416 catch (CFileException* pE)
1418 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in writing temp file\n");
1419 pE->Delete();
1420 return false;
1422 return true;
1425 void CTGitPathList::LoadFromAsteriskSeparatedString(const CString& sPathString)
1427 int pos = 0;
1428 CString temp;
1429 for(;;)
1431 temp = sPathString.Tokenize(L"*", pos);
1432 if(temp.IsEmpty())
1433 break;
1434 AddPath(CTGitPath(CPathUtils::GetLongPathname(temp)));
1438 CString CTGitPathList::CreateAsteriskSeparatedString() const
1440 CString sRet;
1441 for (const auto& path : m_paths)
1443 if (!sRet.IsEmpty())
1444 sRet += L'*';
1445 sRet += path.GetWinPathString();
1447 return sRet;
1449 #endif // _MFC_VER
1451 bool
1452 CTGitPathList::AreAllPathsFilesInOneDirectory() const
1454 // Check if all the paths are files and in the same directory
1455 m_commonBaseDirectory.Reset();
1456 for (const auto& path : m_paths)
1458 if (path.IsDirectory())
1459 return false;
1460 const CTGitPath& baseDirectory = path.GetDirectory();
1461 if(m_commonBaseDirectory.IsEmpty())
1462 m_commonBaseDirectory = baseDirectory;
1463 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1465 // Different path
1466 m_commonBaseDirectory.Reset();
1467 return false;
1470 return true;
1473 CTGitPath CTGitPathList::GetCommonDirectory() const
1475 if (m_commonBaseDirectory.IsEmpty())
1477 for (const auto& path : m_paths)
1479 const CTGitPath& baseDirectory = path.GetDirectory();
1480 if(m_commonBaseDirectory.IsEmpty())
1481 m_commonBaseDirectory = baseDirectory;
1482 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1484 // Different path
1485 m_commonBaseDirectory.Reset();
1486 break;
1490 // since we only checked strings, not paths,
1491 // we have to make sure now that we really return a *path* here
1492 for (const auto& path : m_paths)
1494 if (!m_commonBaseDirectory.IsAncestorOf(path))
1496 m_commonBaseDirectory = m_commonBaseDirectory.GetContainingDirectory();
1497 break;
1500 return m_commonBaseDirectory;
1503 CTGitPath CTGitPathList::GetCommonRoot() const
1505 if (IsEmpty())
1506 return CTGitPath();
1508 if (GetCount() == 1)
1509 return m_paths[0];
1511 // first entry is common root for itself
1512 // (add trailing '\\' to detect partial matches of the last path element)
1513 CString root = m_paths[0].GetWinPathString() + L'\\';
1514 int rootLength = root.GetLength();
1516 // determine common path string prefix
1517 for (auto it = m_paths.cbegin() + 1; it != m_paths.cend(); ++it)
1519 CString path = it->GetWinPathString() + L'\\';
1521 int newLength = CStringUtils::GetMatchingLength(root, path);
1522 if (newLength != rootLength)
1524 root.Delete(newLength, rootLength);
1525 rootLength = newLength;
1529 // remove the last (partial) path element
1530 if (rootLength > 0)
1531 root.Delete(root.ReverseFind(L'\\'), rootLength);
1533 // done
1534 return CTGitPath(root);
1537 void CTGitPathList::SortByPathname(bool bReverse /*= false*/)
1539 std::sort(m_paths.begin(), m_paths.end());
1540 if (bReverse)
1541 std::reverse(m_paths.begin(), m_paths.end());
1544 void CTGitPathList::DeleteAllFiles(bool bTrash, bool bFilesOnly, bool bShowErrorUI)
1546 if (m_paths.empty())
1547 return;
1548 PathVector::const_iterator it;
1549 SortByPathname(true); // nested ones first
1551 CString sPaths;
1552 for (it = m_paths.cbegin(); it != m_paths.cend(); ++it)
1554 if ((it->Exists()) && ((it->IsDirectory() != bFilesOnly) || !bFilesOnly))
1556 if (!it->IsDirectory())
1557 ::SetFileAttributes(it->GetWinPath(), FILE_ATTRIBUTE_NORMAL);
1559 sPaths += it->GetWinPath();
1560 sPaths += '\0';
1563 if (sPaths.IsEmpty())
1564 return;
1565 sPaths += '\0';
1566 sPaths += '\0';
1567 DeleteViaShell((LPCTSTR)sPaths, bTrash, bShowErrorUI);
1568 Clear();
1571 bool CTGitPathList::DeleteViaShell(LPCTSTR path, bool bTrash, bool bShowErrorUI)
1573 SHFILEOPSTRUCT shop = {0};
1574 shop.wFunc = FO_DELETE;
1575 shop.pFrom = path;
1576 shop.fFlags = FOF_NOCONFIRMATION|FOF_NO_CONNECTED_ELEMENTS;
1577 if (!bShowErrorUI)
1578 shop.fFlags |= FOF_NOERRORUI | FOF_SILENT;
1579 if (bTrash)
1580 shop.fFlags |= FOF_ALLOWUNDO;
1581 const bool bRet = (SHFileOperation(&shop) == 0);
1582 return bRet;
1585 void CTGitPathList::RemoveDuplicates()
1587 SortByPathname();
1588 // Remove the duplicates
1589 // (Unique moves them to the end of the vector, then erase chops them off)
1590 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::PredLeftEquivalentToRight), m_paths.end());
1593 void CTGitPathList::RemoveAdminPaths()
1595 PathVector::iterator it;
1596 for(it = m_paths.begin(); it != m_paths.end(); )
1598 if (it->IsAdminDir())
1600 m_paths.erase(it);
1601 it = m_paths.begin();
1603 else
1604 ++it;
1608 void CTGitPathList::RemovePath(const CTGitPath& path)
1610 PathVector::iterator it;
1611 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1613 if (it->IsEquivalentTo(path))
1615 m_paths.erase(it);
1616 return;
1621 void CTGitPathList::RemoveItem(CTGitPath & path)
1623 PathVector::iterator it;
1624 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1626 if (CTGitPath::ArePathStringsEqualWithCase(it->GetGitPathString(), path.GetGitPathString()))
1628 m_paths.erase(it);
1629 return;
1633 void CTGitPathList::RemoveChildren()
1635 SortByPathname();
1636 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::CheckChild), m_paths.end());
1639 bool CTGitPathList::IsEqual(const CTGitPathList& list)
1641 if (list.GetCount() != GetCount())
1642 return false;
1643 for (int i=0; i<list.GetCount(); ++i)
1645 if (!list[i].IsEquivalentTo(m_paths[i]))
1646 return false;
1648 return true;
1651 const CTGitPath* CTGitPathList::LookForGitPath(const CString& path)
1653 int i=0;
1654 for (i = 0; i < this->GetCount(); ++i)
1656 if (CTGitPath::ArePathStringsEqualWithCase((*this)[i].GetGitPathString(), path))
1657 return (CTGitPath*)&(*this)[i];
1659 return nullptr;
1662 CString CTGitPath::GetActionName(int action)
1664 if(action & CTGitPath::LOGACTIONS_UNMERGED)
1665 return MAKEINTRESOURCE(IDS_PATHACTIONS_CONFLICT);
1666 if(action & CTGitPath::LOGACTIONS_ADDED)
1667 return MAKEINTRESOURCE(IDS_PATHACTIONS_ADD);
1668 if (action & CTGitPath::LOGACTIONS_MISSING)
1669 return MAKEINTRESOURCE(IDS_PATHACTIONS_MISSING);
1670 if(action & CTGitPath::LOGACTIONS_DELETED)
1671 return MAKEINTRESOURCE(IDS_PATHACTIONS_DELETE);
1672 if(action & CTGitPath::LOGACTIONS_MERGED )
1673 return MAKEINTRESOURCE(IDS_PATHACTIONS_MERGED);
1675 if(action & CTGitPath::LOGACTIONS_MODIFIED)
1676 return MAKEINTRESOURCE(IDS_PATHACTIONS_MODIFIED);
1677 if(action & CTGitPath::LOGACTIONS_REPLACED)
1678 return MAKEINTRESOURCE(IDS_PATHACTIONS_RENAME);
1679 if(action & CTGitPath::LOGACTIONS_COPY)
1680 return MAKEINTRESOURCE(IDS_PATHACTIONS_COPY);
1682 if (action & CTGitPath::LOGACTIONS_ASSUMEVALID)
1683 return MAKEINTRESOURCE(IDS_PATHACTIONS_ASSUMEUNCHANGED);
1684 if (action & CTGitPath::LOGACTIONS_SKIPWORKTREE)
1685 return MAKEINTRESOURCE(IDS_PATHACTIONS_SKIPWORKTREE);
1687 if (action & CTGitPath::LOGACTIONS_IGNORE)
1688 return MAKEINTRESOURCE(IDS_PATHACTIONS_IGNORED);
1690 return MAKEINTRESOURCE(IDS_PATHACTIONS_UNKNOWN);
1693 CString CTGitPath::GetActionName() const
1695 return GetActionName(m_Action);
1698 int CTGitPathList::GetAction()
1700 return m_Action;
1703 CString CTGitPath::GetAbbreviatedRename()
1705 if (GetGitOldPathString().IsEmpty())
1706 return GetFileOrDirectoryName();
1708 // Find common prefix which ends with a slash
1709 int prefix_length = 0;
1710 for (int i = 0, maxLength = min(m_sOldFwdslashPath.GetLength(), m_sFwdslashPath.GetLength()); i < maxLength; ++i)
1712 if (m_sOldFwdslashPath[i] != m_sFwdslashPath[i])
1713 break;
1714 if (m_sOldFwdslashPath[i] == L'/')
1715 prefix_length = i + 1;
1718 LPCTSTR oldName = (LPCTSTR)m_sOldFwdslashPath + m_sOldFwdslashPath.GetLength();
1719 LPCTSTR newName = (LPCTSTR)m_sFwdslashPath + m_sFwdslashPath.GetLength();
1721 int suffix_length = 0;
1722 int prefix_adjust_for_slash = (prefix_length ? 1 : 0);
1723 while ((LPCTSTR)m_sOldFwdslashPath + prefix_length - prefix_adjust_for_slash <= oldName &&
1724 (LPCTSTR)m_sFwdslashPath + prefix_length - prefix_adjust_for_slash <= newName &&
1725 *oldName == *newName)
1727 if (*oldName == L'/')
1728 suffix_length = m_sOldFwdslashPath.GetLength() - (int)(oldName - (LPCTSTR)m_sOldFwdslashPath);
1729 --oldName;
1730 --newName;
1734 * pfx{old_midlen => new_midlen}sfx
1735 * {pfx-old => pfx-new}sfx
1736 * pfx{sfx-old => sfx-new}
1737 * name-old => name-new
1739 int old_midlen = m_sOldFwdslashPath.GetLength() - prefix_length - suffix_length;
1740 int new_midlen = m_sFwdslashPath.GetLength() - prefix_length - suffix_length;
1741 if (old_midlen < 0)
1742 old_midlen = 0;
1743 if (new_midlen < 0)
1744 new_midlen = 0;
1746 CString ret;
1747 if (prefix_length + suffix_length)
1749 ret = m_sOldFwdslashPath.Left(prefix_length);
1750 ret += L'{';
1752 ret += m_sOldFwdslashPath.Mid(prefix_length, old_midlen);
1753 ret += L" => ";
1754 ret += m_sFwdslashPath.Mid(prefix_length, new_midlen);
1755 if (prefix_length + suffix_length)
1757 ret += L'}';
1758 ret += m_sFwdslashPath.Mid(m_sFwdslashPath.GetLength() - suffix_length, suffix_length);
1760 return ret;