Fixed issue #2911: Doing Add on repository root fails with libgit2 returned invalid...
[TortoiseGit.git] / src / Git / TGitPath.cpp
blob220d637b08d8f61628e15e3a8f0416a43ddf0435
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.IsEmpty())
332 m_sLongBackslashPath = L".";
333 else if (m_sBackslashPath.GetLength() >= 248)
335 if (!PathIsRelative(m_sBackslashPath))
336 m_sLongBackslashPath = L"\\\\?\\" + m_sBackslashPath;
337 else
338 m_sLongBackslashPath = L"\\\\?\\" + g_Git.CombinePath(m_sBackslashPath);
340 if (GetFileAttributesEx(m_sBackslashPath.IsEmpty() || m_sBackslashPath.GetLength() >= 248 ? m_sLongBackslashPath : m_sBackslashPath, GetFileExInfoStandard, &attribs))
342 m_bIsDirectory = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
343 // don't cast directly to an __int64:
344 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284%28v=vs.85%29.aspx
345 // "Do not cast a pointer to a FILETIME structure to either a ULARGE_INTEGER* or __int64* value
346 // because it can cause alignment faults on 64-bit Windows."
347 m_lastWriteTime = static_cast<__int64>(attribs.ftLastWriteTime.dwHighDateTime) << 32 | attribs.ftLastWriteTime.dwLowDateTime;
348 if (m_bIsDirectory)
349 m_fileSize = 0;
350 else
351 m_fileSize = ((INT64)( (DWORD)(attribs.nFileSizeLow) ) | ( (INT64)( (DWORD)(attribs.nFileSizeHigh) )<<32 ));
352 m_bIsReadOnly = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
353 m_bExists = true;
355 else
357 m_bIsDirectory = false;
358 m_lastWriteTime = 0;
359 m_fileSize = 0;
360 DWORD err = GetLastError();
361 if ((err == ERROR_FILE_NOT_FOUND)||(err == ERROR_PATH_NOT_FOUND)||(err == ERROR_INVALID_NAME))
362 m_bExists = false;
363 else
365 m_bExists = true;
366 return;
369 m_bDirectoryKnown = true;
370 m_bLastWriteTimeKnown = true;
371 m_bExistsKnown = true;
374 CTGitPath CTGitPath::GetSubPath(const CTGitPath &root)
376 CTGitPath path;
378 if (CStringUtils::StartsWith(GetWinPathString(), root.GetWinPathString()))
380 CString str=GetWinPathString();
381 path.SetFromWin(str.Right(str.GetLength()-root.GetWinPathString().GetLength()-1));
383 return path;
386 void CTGitPath::EnsureBackslashPathSet() const
388 if(m_sBackslashPath.IsEmpty())
390 SetBackslashPath(m_sFwdslashPath);
391 ATLASSERT(IsEmpty() || !m_sBackslashPath.IsEmpty());
394 void CTGitPath::EnsureFwdslashPathSet() const
396 if(m_sFwdslashPath.IsEmpty())
398 SetFwdslashPath(m_sBackslashPath);
399 ATLASSERT(IsEmpty() || !m_sFwdslashPath.IsEmpty());
404 // Reset all the caches
405 void CTGitPath::Reset()
407 m_bDirectoryKnown = false;
408 m_bLastWriteTimeKnown = false;
409 m_bHasAdminDirKnown = false;
410 m_bIsValidOnWindowsKnown = false;
411 m_bIsAdminDirKnown = false;
412 m_bExistsKnown = false;
414 m_sBackslashPath.Empty();
415 m_sFwdslashPath.Empty();
416 this->m_Action=0;
417 this->m_StatAdd.Empty();
418 this->m_StatDel.Empty();
419 m_ParentNo=0;
420 ATLASSERT(IsEmpty());
423 CTGitPath CTGitPath::GetDirectory() const
425 if ((IsDirectory())||(!Exists()))
426 return *this;
427 return GetContainingDirectory();
430 CTGitPath CTGitPath::GetContainingDirectory() const
432 EnsureBackslashPathSet();
434 CString sDirName = m_sBackslashPath.Left(m_sBackslashPath.ReverseFind('\\'));
435 if(sDirName.GetLength() == 2 && sDirName[1] == ':')
437 // This is a root directory, which needs a trailing slash
438 sDirName += '\\';
439 if(sDirName == m_sBackslashPath)
441 // We were clearly provided with a root path to start with - we should return nothing now
442 sDirName.Empty();
445 if(sDirName.GetLength() == 1 && sDirName[0] == '\\')
447 // We have an UNC path and we already are the root
448 sDirName.Empty();
450 CTGitPath retVal;
451 retVal.SetFromWin(sDirName);
452 return retVal;
455 CString CTGitPath::GetRootPathString() const
457 EnsureBackslashPathSet();
458 CString workingPath = m_sBackslashPath;
459 ATLVERIFY(::PathStripToRoot(CStrBuf(workingPath, MAX_PATH))); // MAX_PATH ok here.
460 return workingPath;
464 CString CTGitPath::GetFilename() const
466 //ATLASSERT(!IsDirectory());
467 return GetFileOrDirectoryName();
470 CString CTGitPath::GetFileOrDirectoryName() const
472 EnsureBackslashPathSet();
473 return m_sBackslashPath.Mid(m_sBackslashPath.ReverseFind('\\')+1);
476 CString CTGitPath::GetUIFileOrDirectoryName() const
478 GetUIPathString();
479 return m_sUIPath.Mid(m_sUIPath.ReverseFind('\\')+1);
482 CString CTGitPath::GetFileExtension() const
484 if(!IsDirectory())
486 EnsureBackslashPathSet();
487 int dotPos = m_sBackslashPath.ReverseFind('.');
488 int slashPos = m_sBackslashPath.ReverseFind('\\');
489 if (dotPos > slashPos)
490 return m_sBackslashPath.Mid(dotPos);
492 return CString();
494 CString CTGitPath::GetBaseFilename() const
496 int dot;
497 CString filename=GetFilename();
498 dot = filename.ReverseFind(L'.');
499 if(dot>0)
500 filename.Truncate(dot);
501 return filename;
504 bool CTGitPath::ArePathStringsEqual(const CString& sP1, const CString& sP2)
506 int length = sP1.GetLength();
507 if(length != sP2.GetLength())
509 // Different lengths
510 return false;
512 // We work from the end of the strings, because path differences
513 // are more likely to occur at the far end of a string
514 LPCTSTR pP1Start = sP1;
515 LPCTSTR pP1 = pP1Start+(length-1);
516 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
517 while(length-- > 0)
519 if(_totlower(*pP1--) != _totlower(*pP2--))
520 return false;
522 return true;
525 bool CTGitPath::ArePathStringsEqualWithCase(const CString& sP1, const CString& sP2)
527 int length = sP1.GetLength();
528 if(length != sP2.GetLength())
530 // Different lengths
531 return false;
533 // We work from the end of the strings, because path differences
534 // are more likely to occur at the far end of a string
535 LPCTSTR pP1Start = sP1;
536 LPCTSTR pP1 = pP1Start+(length-1);
537 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
538 while(length-- > 0)
540 if((*pP1--) != (*pP2--))
541 return false;
543 return true;
546 bool CTGitPath::IsEmpty() const
548 // Check the backward slash path first, since the chance that this
549 // one is set is higher. In case of a 'false' return value it's a little
550 // bit faster.
551 return m_sBackslashPath.IsEmpty() && m_sFwdslashPath.IsEmpty();
554 // Test if both paths refer to the same item
555 // Ignores case and slash direction
556 bool CTGitPath::IsEquivalentTo(const CTGitPath& rhs) const
558 // Try and find a slash direction which avoids having to convert
559 // both filenames
560 if(!m_sBackslashPath.IsEmpty())
562 // *We've* got a \ path - make sure that the RHS also has a \ path
563 rhs.EnsureBackslashPathSet();
564 return ArePathStringsEqualWithCase(m_sBackslashPath, rhs.m_sBackslashPath);
566 else
568 // Assume we've got a fwdslash path and make sure that the RHS has one
569 rhs.EnsureFwdslashPathSet();
570 return ArePathStringsEqualWithCase(m_sFwdslashPath, rhs.m_sFwdslashPath);
574 bool CTGitPath::IsEquivalentToWithoutCase(const CTGitPath& rhs) const
576 // Try and find a slash direction which avoids having to convert
577 // both filenames
578 if(!m_sBackslashPath.IsEmpty())
580 // *We've* got a \ path - make sure that the RHS also has a \ path
581 rhs.EnsureBackslashPathSet();
582 return ArePathStringsEqual(m_sBackslashPath, rhs.m_sBackslashPath);
584 else
586 // Assume we've got a fwdslash path and make sure that the RHS has one
587 rhs.EnsureFwdslashPathSet();
588 return ArePathStringsEqual(m_sFwdslashPath, rhs.m_sFwdslashPath);
592 bool CTGitPath::IsAncestorOf(const CTGitPath& possibleDescendant) const
594 possibleDescendant.EnsureBackslashPathSet();
595 EnsureBackslashPathSet();
597 bool bPathStringsEqual = ArePathStringsEqual(m_sBackslashPath, possibleDescendant.m_sBackslashPath.Left(m_sBackslashPath.GetLength()));
598 if (m_sBackslashPath.GetLength() >= possibleDescendant.GetWinPathString().GetLength())
600 return bPathStringsEqual;
603 return (bPathStringsEqual &&
604 ((possibleDescendant.m_sBackslashPath[m_sBackslashPath.GetLength()] == '\\')||
605 (m_sBackslashPath.GetLength()==3 && m_sBackslashPath[1]==':')));
608 // Get a string representing the file path, optionally with a base
609 // section stripped off the front.
610 CString CTGitPath::GetDisplayString(const CTGitPath* pOptionalBasePath /* = nullptr*/) const
612 EnsureFwdslashPathSet();
613 if (pOptionalBasePath)
615 // Find the length of the base-path without having to do an 'ensure' on it
616 int baseLength = max(pOptionalBasePath->m_sBackslashPath.GetLength(), pOptionalBasePath->m_sFwdslashPath.GetLength());
618 // Now, chop that baseLength of the front of the path
619 return m_sFwdslashPath.Mid(baseLength).TrimLeft('/');
621 return m_sFwdslashPath;
624 int CTGitPath::Compare(const CTGitPath& left, const CTGitPath& right)
626 left.EnsureBackslashPathSet();
627 right.EnsureBackslashPathSet();
628 return CStringUtils::FastCompareNoCase(left.m_sBackslashPath, right.m_sBackslashPath);
631 bool operator<(const CTGitPath& left, const CTGitPath& right)
633 return CTGitPath::Compare(left, right) < 0;
636 bool CTGitPath::PredLeftEquivalentToRight(const CTGitPath& left, const CTGitPath& right)
638 return left.IsEquivalentTo(right);
641 bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right)
643 if (left.IsAdminDir() && right.IsAdminDir())
645 CTGitPath l = left;
646 CTGitPath r = right;
649 l = l.GetContainingDirectory();
650 } while(l.HasAdminDir());
653 r = r.GetContainingDirectory();
654 } while(r.HasAdminDir());
655 return l.GetContainingDirectory().IsEquivalentTo(r.GetContainingDirectory());
657 return left.GetDirectory().IsEquivalentTo(right.GetDirectory());
660 bool CTGitPath::CheckChild(const CTGitPath &parent, const CTGitPath& child)
662 return parent.IsAncestorOf(child);
665 void CTGitPath::AppendRawString(const CString& sAppend)
667 EnsureFwdslashPathSet();
668 CString strCopy = m_sFwdslashPath += sAppend;
669 SetFromUnknown(strCopy);
672 void CTGitPath::AppendPathString(const CString& sAppend)
674 EnsureBackslashPathSet();
675 CString cleanAppend(sAppend);
676 cleanAppend.Replace(L'/', L'\\');
677 cleanAppend.TrimLeft(L'\\');
678 m_sBackslashPath.TrimRight(L'\\');
679 CString strCopy = m_sBackslashPath;
680 strCopy += L'\\';
681 strCopy += cleanAppend;
682 SetFromWin(strCopy);
685 bool CTGitPath::IsWCRoot() const
687 if (m_bIsWCRootKnown)
688 return m_bIsWCRoot;
690 m_bIsWCRootKnown = true;
691 m_bIsWCRoot = false;
693 CString topDirectory;
694 if (!IsDirectory() || !HasAdminDir(&topDirectory))
695 return m_bIsWCRoot;
697 if (IsEquivalentToWithoutCase(topDirectory))
698 m_bIsWCRoot = true;
700 return m_bIsWCRoot;
703 bool CTGitPath::HasSubmodules() const
705 if (HasAdminDir())
707 CString path = m_sProjectRoot;
708 path += L"\\.gitmodules";
709 if( PathFileExists(path) )
710 return true;
712 return false;
715 int CTGitPath::GetAdminDirMask() const
717 int status = 0;
718 if (!HasAdminDir())
719 return status;
721 // ITEMIS_INGIT will be revoked if necessary in TortoiseShell/ContextMenu.cpp
722 status |= ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER;
724 if (IsDirectory())
726 status |= ITEMIS_FOLDERINGIT;
727 if (IsWCRoot())
729 status |= ITEMIS_WCROOT;
731 CString topProjectDir;
732 if (GitAdminDir::HasAdminDir(GetWinPathString(), false, &topProjectDir))
734 if (PathFileExists(topProjectDir + L"\\.gitmodules"))
736 CAutoConfig config(true);
737 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(topProjectDir + L"\\.gitmodules"), GIT_CONFIG_LEVEL_APP, FALSE);
738 CString relativePath = GetWinPathString().Mid(topProjectDir.GetLength());
739 relativePath.Replace(L'\\', L'/');
740 relativePath.Trim(L'/');
741 CStringA submodulePath = CUnicodeUtils::GetUTF8(relativePath);
742 if (git_config_foreach_match(config, "submodule\\..*\\.path",
743 [](const git_config_entry *entry, void *data) { return entry->value == *(CStringA *)data ? GIT_EUSER : 0; }, &submodulePath) == GIT_EUSER)
744 status |= ITEMIS_SUBMODULE;
750 CString dotGitPath;
751 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
753 if (PathFileExists(dotGitPath + L"BISECT_START"))
754 status |= ITEMIS_BISECT;
756 if (PathFileExists(dotGitPath + L"MERGE_HEAD"))
757 status |= ITEMIS_MERGEACTIVE;
759 if (HasStashDir(dotGitPath))
760 status |= ITEMIS_STASH;
762 if (PathFileExists(dotGitPath + L"svn"))
763 status |= ITEMIS_GITSVN;
765 if (PathFileExists(m_sProjectRoot + L"\\.gitmodules"))
766 status |= ITEMIS_SUBMODULECONTAINER;
768 return status;
771 bool CTGitPath::HasStashDir(const CString& dotGitPath) const
773 if (PathFileExists(dotGitPath + L"refs\\stash"))
774 return true;
776 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);
777 if (!hfile)
778 return false;
780 DWORD filesize = ::GetFileSize(hfile, nullptr);
781 if (filesize == 0)
782 return false;
784 DWORD size = 0;
785 auto buff = std::make_unique<char[]>(filesize + 1);
786 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
787 buff.get()[filesize] = '\0';
789 if (size != filesize)
790 return false;
792 for (DWORD i = 0; i < filesize;)
794 if (buff[i] == '#' || buff[i] == '^')
796 while (buff[i] != '\n')
798 ++i;
799 if (i == filesize)
800 break;
802 ++i;
805 if (i >= filesize)
806 break;
808 while (buff[i] != ' ')
810 ++i;
811 if (i == filesize)
812 break;
815 ++i;
816 if (i >= filesize)
817 break;
819 if (i <= filesize - 10 && (buff[i + 10] == '\n' || buff[i + 10] == '\0') && !strncmp("refs/stash", buff.get() + i, 10))
820 return true;
821 while (buff[i] != '\n')
823 ++i;
824 if (i == filesize)
825 break;
828 while (buff[i] == '\n')
830 ++i;
831 if (i == filesize)
832 break;
835 return false;
838 bool CTGitPath::HasStashDir() const
840 if (!HasAdminDir())
841 return false;
843 CString dotGitPath;
844 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
846 return HasStashDir(dotGitPath);
849 bool CTGitPath::HasGitSVNDir() const
851 if (!HasAdminDir())
852 return false;
854 CString dotGitPath;
855 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
857 return !!PathFileExists(dotGitPath + L"svn");
859 bool CTGitPath::IsBisectActive() const
861 if (!HasAdminDir())
862 return false;
864 CString dotGitPath;
865 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
867 return !!PathFileExists(dotGitPath + L"BISECT_START");
869 bool CTGitPath::IsMergeActive() const
871 if (!HasAdminDir())
872 return false;
874 CString dotGitPath;
875 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
877 return !!PathFileExists(dotGitPath + L"MERGE_HEAD");
879 bool CTGitPath::HasRebaseApply() const
881 if (!HasAdminDir())
882 return false;
884 CString dotGitPath;
885 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
887 return !!PathFileExists(dotGitPath + L"rebase-apply");
890 bool CTGitPath::HasAdminDir(CString *ProjectTopDir) const
892 if (m_bHasAdminDirKnown)
894 if (ProjectTopDir)
895 *ProjectTopDir = m_sProjectRoot;
896 return m_bHasAdminDir;
899 EnsureBackslashPathSet();
900 bool isAdminDir = false;
901 m_bHasAdminDir = GitAdminDir::HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
902 m_bHasAdminDirKnown = true;
903 if ((m_bHasAdminDir || isAdminDir) && !m_bIsAdminDirKnown)
905 m_bIsAdminDir = isAdminDir;
906 m_bIsAdminDirKnown = true;
908 if (ProjectTopDir)
909 *ProjectTopDir = m_sProjectRoot;
910 return m_bHasAdminDir;
913 bool CTGitPath::IsAdminDir() const
915 if (m_bIsAdminDirKnown)
916 return m_bIsAdminDir;
918 EnsureBackslashPathSet();
919 m_bIsAdminDir = GitAdminDir::IsAdminDirPath(m_sBackslashPath);
920 m_bIsAdminDirKnown = true;
921 if (m_bIsAdminDir && !m_bIsAdminDirKnown)
923 m_bHasAdminDir = false;
924 m_bIsAdminDirKnown = true;
926 return m_bIsAdminDir;
929 bool CTGitPath::IsValidOnWindows() const
931 if (m_bIsValidOnWindowsKnown)
932 return m_bIsValidOnWindows;
934 m_bIsValidOnWindows = false;
935 EnsureBackslashPathSet();
936 CString sMatch = m_sBackslashPath + L"\r\n";
937 std::wstring sPattern;
938 // the 'file://' URL is just a normal windows path:
939 if (CStringUtils::StartsWithI(sMatch, L"file:\\\\"))
941 sMatch = sMatch.Mid(7);
942 sMatch.TrimLeft(L'\\');
943 sPattern = L"^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$";
945 else
946 sPattern = L"^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$";
950 std::tr1::wregex rx(sPattern, std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
951 std::tr1::wsmatch match;
953 std::wstring rmatch = std::wstring((LPCTSTR)sMatch);
954 if (std::tr1::regex_match(rmatch, match, rx))
956 if (std::wstring(match[0]).compare(sMatch)==0)
957 m_bIsValidOnWindows = true;
959 if (m_bIsValidOnWindows)
961 // now check for illegal filenames
962 std::tr1::wregex rx2(L"\\\\(lpt\\d|com\\d|aux|nul|prn|con)(\\\\|$)", std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
963 rmatch = m_sBackslashPath;
964 if (std::tr1::regex_search(rmatch, rx2, std::tr1::regex_constants::match_default))
965 m_bIsValidOnWindows = false;
968 catch (std::exception&) {}
970 m_bIsValidOnWindowsKnown = true;
971 return m_bIsValidOnWindows;
974 //////////////////////////////////////////////////////////////////////////
976 CTGitPathList::CTGitPathList()
978 m_Action = 0;
981 // A constructor which allows a path list to be easily built which one initial entry in
982 CTGitPathList::CTGitPathList(const CTGitPath& firstEntry)
984 m_Action = 0;
985 AddPath(firstEntry);
987 int CTGitPathList::ParserFromLsFile(BYTE_VECTOR &out,bool /*staged*/)
989 size_t pos = 0;
990 CString one;
991 CTGitPath path;
992 CString part;
993 this->Clear();
995 while (pos < out.size())
997 one.Empty();
998 path.Reset();
1000 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1001 int tabstart=0;
1002 // m_Action is never used and propably never worked (needs to be set after path.SetFromGit)
1003 // also dropped LOGACTIONS_CACHE for 'H'
1004 // path.m_Action=path.ParserAction(out[pos]);
1005 one.Tokenize(L"\t", tabstart);
1007 if (tabstart < 0)
1008 return -1;
1010 CString pathstring = one.Right(one.GetLength() - tabstart);
1012 tabstart=0;
1014 part = one.Tokenize(L" ", tabstart); //Tag
1015 if (tabstart < 0)
1016 return -1;
1018 part = one.Tokenize(L" ", tabstart); //Mode
1019 if (tabstart < 0)
1020 return -1;
1021 int mode = wcstol(part, nullptr, 8);
1022 path.SetFromGit(pathstring, (mode & S_IFDIR) == S_IFDIR);
1024 part = one.Tokenize(L" ", tabstart); //Hash
1025 if (tabstart < 0)
1026 return -1;
1028 part = one.Tokenize(L"\t", tabstart); //Stage
1029 if (tabstart < 0)
1030 return -1;
1032 path.m_Stage = _wtol(part);
1034 this->AddPath(path);
1036 pos=out.findNextString(pos);
1038 return 0;
1041 int CTGitPathList::FillUnRev(unsigned int action, const CTGitPathList* list, CString* err)
1043 this->Clear();
1044 CTGitPath path;
1046 int count;
1047 if (!list)
1048 count=1;
1049 else
1050 count=list->GetCount();
1051 for (int i = 0; i < count; ++i)
1053 CString cmd;
1054 CString ignored;
1055 if(action & CTGitPath::LOGACTIONS_IGNORE)
1056 ignored = L" -i";
1058 if (!list)
1060 cmd = L"git.exe ls-files --exclude-standard --full-name --others -z";
1061 cmd+=ignored;
1064 else
1066 cmd.Format(L"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 size_t pos = 0;
1081 CString one;
1082 while (pos < out.size())
1084 one.Empty();
1085 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1086 if(!one.IsEmpty())
1088 //SetFromGit will clear all status
1089 if (CStringUtils::EndsWith(one, L'/'))
1091 one.Truncate(one.GetLength() - 1);
1092 path.SetFromGit(one, true);
1094 else
1095 path.SetFromGit(one);
1096 path.m_Action=action;
1097 AddPath(path);
1099 pos=out.findNextString(pos);
1103 return 0;
1106 int CTGitPathList::FillBasedOnIndexFlags(unsigned short flag, unsigned short flagextended, const CTGitPathList* list /*nullptr*/)
1108 Clear();
1109 CTGitPath path;
1111 CAutoRepository repository(g_Git.GetGitRepository());
1112 if (!repository)
1113 return -1;
1115 CAutoIndex index;
1116 if (git_repository_index(index.GetPointer(), repository))
1117 return -1;
1119 int count;
1120 if (list == nullptr)
1121 count = 1;
1122 else
1123 count = list->GetCount();
1124 for (int j = 0; j < count; ++j)
1126 for (size_t i = 0, ecount = git_index_entrycount(index); i < ecount; ++i)
1128 const git_index_entry *e = git_index_get_byindex(index, i);
1130 if (!e || !((e->flags & flag) || (e->flags_extended & flagextended)) || !e->path)
1131 continue;
1133 CString one = CUnicodeUtils::GetUnicode(e->path);
1135 if (!(!list || (*list)[j].GetWinPathString().IsEmpty() || one == (*list)[j].GetGitPathString() || (PathIsDirectory(g_Git.CombinePath((*list)[j].GetWinPathString())) && CStringUtils::StartsWith(one, (*list)[j].GetGitPathString() + L'/'))))
1136 continue;
1138 //SetFromGit will clear all status
1139 path.SetFromGit(one, (e->mode & S_IFDIR) == S_IFDIR);
1140 if (e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE)
1141 path.m_Action = CTGitPath::LOGACTIONS_SKIPWORKTREE;
1142 else if (e->flags & GIT_IDXENTRY_VALID)
1143 path.m_Action = CTGitPath::LOGACTIONS_ASSUMEVALID;
1144 AddPath(path);
1147 RemoveDuplicates();
1148 return 0;
1150 int CTGitPathList::ParserFromLog(BYTE_VECTOR &log, bool parseDeletes /*false*/)
1152 this->Clear();
1153 std::map<CString, size_t> duplicateMap;
1154 size_t pos = 0;
1155 CTGitPath path;
1156 m_Action=0;
1157 size_t logend = log.size();
1158 while (pos < logend)
1160 path.Reset();
1161 if(log[pos]=='\n')
1162 ++pos;
1164 if (pos >= logend)
1165 return -1;
1167 if(log[pos]==':')
1169 bool merged=false;
1170 if (pos + 1 >= logend)
1171 return -1;
1172 if (log[pos + 1] == ':')
1174 merged = true;
1175 ++pos;
1178 int modenew = 0;
1179 int modeold = 0;
1180 size_t end = log.find(0, pos);
1181 size_t actionstart = BYTE_VECTOR::npos;
1182 size_t file1 = BYTE_VECTOR::npos, file2 = BYTE_VECTOR::npos;
1183 if (end != BYTE_VECTOR::npos && end > 7)
1185 modeold = strtol((const char*)&log[pos + 1], nullptr, 8);
1186 modenew = strtol((const char*)&log[pos + 7], nullptr, 8);
1187 actionstart=log.find(' ',end-6);
1188 pos=actionstart;
1190 if (actionstart != BYTE_VECTOR::npos && actionstart > 0)
1192 ++actionstart;
1193 if (actionstart >= logend)
1194 return -1;
1196 file1 = log.find(0,actionstart);
1197 if (file1 != BYTE_VECTOR::npos)
1199 ++file1;
1200 pos=file1;
1202 if( log[actionstart] == 'C' || log[actionstart] == 'R' )
1204 file2=file1;
1205 file1 = log.find(0,file1);
1206 if (file1 != BYTE_VECTOR::npos)
1208 ++file1;
1209 pos=file1;
1214 CString pathname1;
1215 CString pathname2;
1217 if (file1 != BYTE_VECTOR::npos)
1218 CGit::StringAppend(&pathname1, &log[file1], CP_UTF8);
1219 if (file2 != BYTE_VECTOR::npos)
1220 CGit::StringAppend(&pathname2, &log[file2], CP_UTF8);
1222 if (actionstart == BYTE_VECTOR::npos)
1223 return -1;
1225 auto existing = duplicateMap.find(pathname1);
1226 if (existing != duplicateMap.end())
1228 CTGitPath& p = m_paths[existing->second];
1229 p.ParserAction(log[actionstart]);
1231 // reset submodule/folder status if a staged entry is not a folder
1232 if (p.IsDirectory() && ((modeold && !(modeold & S_IFDIR)) || (modenew && !(modenew & S_IFDIR))))
1233 p.UnsetDirectoryStatus();
1235 if(merged)
1236 p.m_Action |= CTGitPath::LOGACTIONS_MERGED;
1237 m_Action |= p.m_Action;
1239 else
1241 int ac=path.ParserAction(log[actionstart] );
1242 ac |= merged?CTGitPath::LOGACTIONS_MERGED:0;
1244 int isSubmodule = FALSE;
1245 if (ac & (CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_UNMERGED))
1246 isSubmodule = (modeold & S_IFDIR) == S_IFDIR;
1247 else
1248 isSubmodule = (modenew & S_IFDIR) == S_IFDIR;
1250 path.SetFromGit(pathname1, &pathname2, &isSubmodule);
1251 path.m_Action=ac;
1252 //action must be set after setfromgit. SetFromGit will clear all status.
1253 this->m_Action|=ac;
1255 AddPath(path);
1256 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1259 else
1261 path.Reset();
1262 CString StatAdd;
1263 CString StatDel;
1264 CString file1;
1265 CString file2;
1266 int isSubmodule = FALSE;
1267 size_t tabstart = log.find('\t', pos);
1268 if (tabstart != BYTE_VECTOR::npos)
1270 int modenew = strtol((const char*)&log[pos + 2], nullptr, 8);
1271 isSubmodule = (modenew & S_IFDIR) == S_IFDIR;
1272 log[tabstart]=0;
1273 CGit::StringAppend(&StatAdd, &log[pos], CP_UTF8);
1274 pos=tabstart+1;
1277 tabstart=log.find('\t',pos);
1278 if (tabstart != BYTE_VECTOR::npos)
1280 log[tabstart]=0;
1282 CGit::StringAppend(&StatDel, &log[pos], CP_UTF8);
1283 pos=tabstart+1;
1286 if(log[pos] == 0) //rename
1288 ++pos;
1289 CGit::StringAppend(&file2, &log[pos], CP_UTF8);
1290 size_t sec = log.find(0, pos);
1291 if (sec != BYTE_VECTOR::npos)
1293 ++sec;
1294 CGit::StringAppend(&file1, &log[sec], CP_UTF8);
1296 pos=sec;
1298 else
1300 CGit::StringAppend(&file1, &log[pos], CP_UTF8);
1302 path.SetFromGit(file1, &file2, &isSubmodule);
1304 auto existing = duplicateMap.find(path.GetGitPathString());
1305 if (existing != duplicateMap.end())
1307 CTGitPath& p = m_paths[existing->second];
1308 p.m_StatAdd = StatAdd;
1309 p.m_StatDel = StatDel;
1311 else
1313 //path.SetFromGit(pathname);
1314 if (parseDeletes)
1316 path.m_StatAdd = L"0";
1317 path.m_StatDel = L"0";
1318 path.m_Action |= CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_MISSING;
1320 else
1322 path.m_StatAdd=StatAdd;
1323 path.m_StatDel=StatDel;
1325 AddPath(path);
1326 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1329 pos=log.findNextString(pos);
1331 return 0;
1334 void CTGitPathList::AddPath(const CTGitPath& newPath)
1336 m_paths.push_back(newPath);
1337 m_commonBaseDirectory.Reset();
1339 int CTGitPathList::GetCount() const
1341 return (int)m_paths.size();
1343 bool CTGitPathList::IsEmpty() const
1345 return m_paths.empty();
1347 void CTGitPathList::Clear()
1349 m_paths.clear();
1350 m_commonBaseDirectory.Reset();
1353 const CTGitPath& CTGitPathList::operator[](INT_PTR index) const
1355 ATLASSERT(index >= 0 && index < (INT_PTR)m_paths.size());
1356 return m_paths[index];
1359 bool CTGitPathList::AreAllPathsFiles() const
1361 // Look through the vector for any directories - if we find them, return false
1362 return std::find_if(m_paths.cbegin(), m_paths.cend(), std::mem_fun_ref(&CTGitPath::IsDirectory)) == m_paths.end();
1365 #if defined(_MFC_VER)
1367 bool CTGitPathList::LoadFromFile(const CTGitPath& filename)
1369 Clear();
1372 CString strLine;
1373 CStdioFile file(filename.GetWinPath(), CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite);
1375 // for every selected file/folder
1376 CTGitPath path;
1377 while (file.ReadString(strLine))
1379 path.SetFromUnknown(strLine);
1380 AddPath(path);
1382 file.Close();
1384 catch (CFileException* pE)
1386 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException loading target file list\n");
1387 TCHAR error[10000] = {0};
1388 pE->GetErrorMessage(error, 10000);
1389 // CMessageBox::Show(nullptr, error, L"TortoiseGit", MB_ICONERROR);
1390 pE->Delete();
1391 return false;
1393 return true;
1396 bool CTGitPathList::WriteToFile(const CString& sFilename, bool bANSI /* = false */) const
1400 if (bANSI)
1402 CStdioFile file(sFilename, CFile::typeText | CFile::modeReadWrite | CFile::modeCreate);
1403 for (const auto& path : m_paths)
1405 CStringA line = CStringA(path.GetGitPathString()) + '\n';
1406 file.Write(line, line.GetLength());
1408 file.Close();
1410 else
1412 CStdioFile file(sFilename, CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
1413 for (const auto& path : m_paths)
1414 file.WriteString(path.GetGitPathString() + L'\n');
1415 file.Close();
1418 catch (CFileException* pE)
1420 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in writing temp file\n");
1421 pE->Delete();
1422 return false;
1424 return true;
1427 void CTGitPathList::LoadFromAsteriskSeparatedString(const CString& sPathString)
1429 int pos = 0;
1430 CString temp;
1431 for(;;)
1433 temp = sPathString.Tokenize(L"*", pos);
1434 if(temp.IsEmpty())
1435 break;
1436 AddPath(CTGitPath(CPathUtils::GetLongPathname(temp)));
1440 CString CTGitPathList::CreateAsteriskSeparatedString() const
1442 CString sRet;
1443 for (const auto& path : m_paths)
1445 if (!sRet.IsEmpty())
1446 sRet += L'*';
1447 sRet += path.GetWinPathString();
1449 return sRet;
1451 #endif // _MFC_VER
1453 bool
1454 CTGitPathList::AreAllPathsFilesInOneDirectory() const
1456 // Check if all the paths are files and in the same directory
1457 m_commonBaseDirectory.Reset();
1458 for (const auto& path : m_paths)
1460 if (path.IsDirectory())
1461 return false;
1462 const CTGitPath& baseDirectory = path.GetDirectory();
1463 if(m_commonBaseDirectory.IsEmpty())
1464 m_commonBaseDirectory = baseDirectory;
1465 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1467 // Different path
1468 m_commonBaseDirectory.Reset();
1469 return false;
1472 return true;
1475 CTGitPath CTGitPathList::GetCommonDirectory() const
1477 if (m_commonBaseDirectory.IsEmpty())
1479 for (const auto& path : m_paths)
1481 const CTGitPath& baseDirectory = path.GetDirectory();
1482 if(m_commonBaseDirectory.IsEmpty())
1483 m_commonBaseDirectory = baseDirectory;
1484 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1486 // Different path
1487 m_commonBaseDirectory.Reset();
1488 break;
1492 // since we only checked strings, not paths,
1493 // we have to make sure now that we really return a *path* here
1494 for (const auto& path : m_paths)
1496 if (!m_commonBaseDirectory.IsAncestorOf(path))
1498 m_commonBaseDirectory = m_commonBaseDirectory.GetContainingDirectory();
1499 break;
1502 return m_commonBaseDirectory;
1505 CTGitPath CTGitPathList::GetCommonRoot() const
1507 if (IsEmpty())
1508 return CTGitPath();
1510 if (GetCount() == 1)
1511 return m_paths[0];
1513 // first entry is common root for itself
1514 // (add trailing '\\' to detect partial matches of the last path element)
1515 CString root = m_paths[0].GetWinPathString() + L'\\';
1516 int rootLength = root.GetLength();
1518 // determine common path string prefix
1519 for (auto it = m_paths.cbegin() + 1; it != m_paths.cend(); ++it)
1521 CString path = it->GetWinPathString() + L'\\';
1523 int newLength = CStringUtils::GetMatchingLength(root, path);
1524 if (newLength != rootLength)
1526 root.Delete(newLength, rootLength);
1527 rootLength = newLength;
1531 // remove the last (partial) path element
1532 if (rootLength > 0)
1533 root.Delete(root.ReverseFind(L'\\'), rootLength);
1535 // done
1536 return CTGitPath(root);
1539 void CTGitPathList::SortByPathname(bool bReverse /*= false*/)
1541 std::sort(m_paths.begin(), m_paths.end());
1542 if (bReverse)
1543 std::reverse(m_paths.begin(), m_paths.end());
1546 void CTGitPathList::DeleteAllFiles(bool bTrash, bool bFilesOnly, bool bShowErrorUI)
1548 if (m_paths.empty())
1549 return;
1550 PathVector::const_iterator it;
1551 SortByPathname(true); // nested ones first
1553 CString sPaths;
1554 for (it = m_paths.cbegin(); it != m_paths.cend(); ++it)
1556 if ((it->Exists()) && ((it->IsDirectory() != bFilesOnly) || !bFilesOnly))
1558 if (!it->IsDirectory())
1559 ::SetFileAttributes(it->GetWinPath(), FILE_ATTRIBUTE_NORMAL);
1561 sPaths += it->GetWinPath();
1562 sPaths += '\0';
1565 if (sPaths.IsEmpty())
1566 return;
1567 sPaths += '\0';
1568 sPaths += '\0';
1569 DeleteViaShell((LPCTSTR)sPaths, bTrash, bShowErrorUI);
1570 Clear();
1573 bool CTGitPathList::DeleteViaShell(LPCTSTR path, bool bTrash, bool bShowErrorUI)
1575 SHFILEOPSTRUCT shop = {0};
1576 shop.wFunc = FO_DELETE;
1577 shop.pFrom = path;
1578 shop.fFlags = FOF_NOCONFIRMATION|FOF_NO_CONNECTED_ELEMENTS;
1579 if (!bShowErrorUI)
1580 shop.fFlags |= FOF_NOERRORUI | FOF_SILENT;
1581 if (bTrash)
1582 shop.fFlags |= FOF_ALLOWUNDO;
1583 const bool bRet = (SHFileOperation(&shop) == 0);
1584 return bRet;
1587 void CTGitPathList::RemoveDuplicates()
1589 SortByPathname();
1590 // Remove the duplicates
1591 // (Unique moves them to the end of the vector, then erase chops them off)
1592 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::PredLeftEquivalentToRight), m_paths.end());
1595 void CTGitPathList::RemoveAdminPaths()
1597 PathVector::iterator it;
1598 for(it = m_paths.begin(); it != m_paths.end(); )
1600 if (it->IsAdminDir())
1602 m_paths.erase(it);
1603 it = m_paths.begin();
1605 else
1606 ++it;
1610 void CTGitPathList::RemovePath(const CTGitPath& path)
1612 PathVector::iterator it;
1613 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1615 if (it->IsEquivalentTo(path))
1617 m_paths.erase(it);
1618 return;
1623 void CTGitPathList::RemoveItem(CTGitPath & path)
1625 PathVector::iterator it;
1626 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1628 if (CTGitPath::ArePathStringsEqualWithCase(it->GetGitPathString(), path.GetGitPathString()))
1630 m_paths.erase(it);
1631 return;
1635 void CTGitPathList::RemoveChildren()
1637 SortByPathname();
1638 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::CheckChild), m_paths.end());
1641 bool CTGitPathList::IsEqual(const CTGitPathList& list)
1643 if (list.GetCount() != GetCount())
1644 return false;
1645 for (int i=0; i<list.GetCount(); ++i)
1647 if (!list[i].IsEquivalentTo(m_paths[i]))
1648 return false;
1650 return true;
1653 const CTGitPath* CTGitPathList::LookForGitPath(const CString& path)
1655 int i=0;
1656 for (i = 0; i < this->GetCount(); ++i)
1658 if (CTGitPath::ArePathStringsEqualWithCase((*this)[i].GetGitPathString(), path))
1659 return (CTGitPath*)&(*this)[i];
1661 return nullptr;
1664 CString CTGitPath::GetActionName(int action)
1666 if(action & CTGitPath::LOGACTIONS_UNMERGED)
1667 return MAKEINTRESOURCE(IDS_PATHACTIONS_CONFLICT);
1668 if(action & CTGitPath::LOGACTIONS_ADDED)
1669 return MAKEINTRESOURCE(IDS_PATHACTIONS_ADD);
1670 if (action & CTGitPath::LOGACTIONS_MISSING)
1671 return MAKEINTRESOURCE(IDS_PATHACTIONS_MISSING);
1672 if(action & CTGitPath::LOGACTIONS_DELETED)
1673 return MAKEINTRESOURCE(IDS_PATHACTIONS_DELETE);
1674 if(action & CTGitPath::LOGACTIONS_MERGED )
1675 return MAKEINTRESOURCE(IDS_PATHACTIONS_MERGED);
1677 if(action & CTGitPath::LOGACTIONS_MODIFIED)
1678 return MAKEINTRESOURCE(IDS_PATHACTIONS_MODIFIED);
1679 if(action & CTGitPath::LOGACTIONS_REPLACED)
1680 return MAKEINTRESOURCE(IDS_PATHACTIONS_RENAME);
1681 if(action & CTGitPath::LOGACTIONS_COPY)
1682 return MAKEINTRESOURCE(IDS_PATHACTIONS_COPY);
1684 if (action & CTGitPath::LOGACTIONS_ASSUMEVALID)
1685 return MAKEINTRESOURCE(IDS_PATHACTIONS_ASSUMEUNCHANGED);
1686 if (action & CTGitPath::LOGACTIONS_SKIPWORKTREE)
1687 return MAKEINTRESOURCE(IDS_PATHACTIONS_SKIPWORKTREE);
1689 if (action & CTGitPath::LOGACTIONS_IGNORE)
1690 return MAKEINTRESOURCE(IDS_PATHACTIONS_IGNORED);
1692 return MAKEINTRESOURCE(IDS_PATHACTIONS_UNKNOWN);
1695 CString CTGitPath::GetActionName() const
1697 return GetActionName(m_Action);
1700 int CTGitPathList::GetAction()
1702 return m_Action;
1705 CString CTGitPath::GetAbbreviatedRename()
1707 if (GetGitOldPathString().IsEmpty())
1708 return GetFileOrDirectoryName();
1710 // Find common prefix which ends with a slash
1711 int prefix_length = 0;
1712 for (int i = 0, maxLength = min(m_sOldFwdslashPath.GetLength(), m_sFwdslashPath.GetLength()); i < maxLength; ++i)
1714 if (m_sOldFwdslashPath[i] != m_sFwdslashPath[i])
1715 break;
1716 if (m_sOldFwdslashPath[i] == L'/')
1717 prefix_length = i + 1;
1720 LPCTSTR oldName = (LPCTSTR)m_sOldFwdslashPath + m_sOldFwdslashPath.GetLength();
1721 LPCTSTR newName = (LPCTSTR)m_sFwdslashPath + m_sFwdslashPath.GetLength();
1723 int suffix_length = 0;
1724 int prefix_adjust_for_slash = (prefix_length ? 1 : 0);
1725 while ((LPCTSTR)m_sOldFwdslashPath + prefix_length - prefix_adjust_for_slash <= oldName &&
1726 (LPCTSTR)m_sFwdslashPath + prefix_length - prefix_adjust_for_slash <= newName &&
1727 *oldName == *newName)
1729 if (*oldName == L'/')
1730 suffix_length = m_sOldFwdslashPath.GetLength() - (int)(oldName - (LPCTSTR)m_sOldFwdslashPath);
1731 --oldName;
1732 --newName;
1736 * pfx{old_midlen => new_midlen}sfx
1737 * {pfx-old => pfx-new}sfx
1738 * pfx{sfx-old => sfx-new}
1739 * name-old => name-new
1741 int old_midlen = m_sOldFwdslashPath.GetLength() - prefix_length - suffix_length;
1742 int new_midlen = m_sFwdslashPath.GetLength() - prefix_length - suffix_length;
1743 if (old_midlen < 0)
1744 old_midlen = 0;
1745 if (new_midlen < 0)
1746 new_midlen = 0;
1748 CString ret;
1749 if (prefix_length + suffix_length)
1751 ret = m_sOldFwdslashPath.Left(prefix_length);
1752 ret += L'{';
1754 ret += m_sOldFwdslashPath.Mid(prefix_length, old_midlen);
1755 ret += L" => ";
1756 ret += m_sFwdslashPath.Mid(prefix_length, new_midlen);
1757 if (prefix_length + suffix_length)
1759 ret += L'}';
1760 ret += m_sFwdslashPath.Mid(m_sFwdslashPath.GetLength() - suffix_length, suffix_length);
1762 return ret;