Allow to move item past last item
[TortoiseGit.git] / src / Git / TGitPath.cpp
blob527c9550275fa9359b25a9c029e6d3386fbc76f9
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 <memory>
32 #include <sys/stat.h>
34 extern CGit g_Git;
36 CTGitPath::CTGitPath(void)
37 : m_bDirectoryKnown(false)
38 , m_bIsDirectory(false)
39 , m_bHasAdminDirKnown(false)
40 , m_bHasAdminDir(false)
41 , m_bIsValidOnWindowsKnown(false)
42 , m_bIsValidOnWindows(false)
43 , m_bIsReadOnly(false)
44 , m_bIsAdminDirKnown(false)
45 , m_bIsAdminDir(false)
46 , m_bExists(false)
47 , m_bExistsKnown(false)
48 , m_bLastWriteTimeKnown(0)
49 , m_lastWriteTime(0)
50 , m_bIsWCRootKnown(false)
51 , m_bIsWCRoot(false)
52 , m_fileSize(0)
53 , m_Checked(false)
54 , m_Action(0)
55 , m_ParentNo(0)
56 , m_Stage(0)
60 CTGitPath::~CTGitPath(void)
63 // Create a TGitPath object from an unknown path type (same as using SetFromUnknown)
64 CTGitPath::CTGitPath(const CString& sUnknownPath) : CTGitPath()
66 SetFromUnknown(sUnknownPath);
69 int CTGitPath::ParserAction(BYTE action)
71 //action=action.TrimLeft();
72 //TCHAR c=action.GetAt(0);
73 if(action == 'M')
74 m_Action|= LOGACTIONS_MODIFIED;
75 if(action == 'R')
76 m_Action|= LOGACTIONS_REPLACED;
77 if(action == 'A')
78 m_Action|= LOGACTIONS_ADDED;
79 if(action == 'D')
80 m_Action|= LOGACTIONS_DELETED;
81 if(action == 'U')
82 m_Action|= LOGACTIONS_UNMERGED;
83 if(action == 'K')
84 m_Action|= LOGACTIONS_DELETED;
85 if(action == 'C' )
86 m_Action|= LOGACTIONS_COPY;
87 if(action == 'T')
88 m_Action|= LOGACTIONS_MODIFIED;
90 return m_Action;
93 int CTGitPath::ParserAction(git_delta_t action)
95 if (action == GIT_DELTA_MODIFIED)
96 m_Action |= LOGACTIONS_MODIFIED;
97 if (action == GIT_DELTA_RENAMED)
98 m_Action |= LOGACTIONS_REPLACED;
99 if (action == GIT_DELTA_ADDED)
100 m_Action |= LOGACTIONS_ADDED;
101 if (action == GIT_DELTA_DELETED)
102 m_Action |= LOGACTIONS_DELETED;
103 if (action == GIT_DELTA_UNMODIFIED)
104 m_Action |= LOGACTIONS_UNMERGED;
105 if (action == GIT_DELTA_COPIED)
106 m_Action |= LOGACTIONS_COPY;
107 if (action == GIT_DELTA_TYPECHANGE)
108 m_Action |= LOGACTIONS_MODIFIED;
110 return m_Action;
113 void CTGitPath::SetFromGit(const char* pPath)
115 Reset();
116 if (!pPath)
117 return;
118 int len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, nullptr, 0);
119 if (len)
121 len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, m_sFwdslashPath.GetBuffer(len+1), len+1);
122 m_sFwdslashPath.ReleaseBuffer(len-1);
124 SanitizeRootPath(m_sFwdslashPath, true);
127 void CTGitPath::SetFromGit(const char* pPath, bool bIsDirectory)
129 SetFromGit(pPath);
130 m_bDirectoryKnown = true;
131 m_bIsDirectory = bIsDirectory;
134 void CTGitPath::SetFromGit(const TCHAR* pPath, bool bIsDirectory)
136 Reset();
137 if (pPath)
139 m_sFwdslashPath = pPath;
140 SanitizeRootPath(m_sFwdslashPath, true);
142 m_bDirectoryKnown = true;
143 m_bIsDirectory = bIsDirectory;
146 void CTGitPath::SetFromGit(const CString& sPath, CString* oldpath, int* bIsDirectory)
148 Reset();
149 m_sFwdslashPath = sPath;
150 SanitizeRootPath(m_sFwdslashPath, true);
151 if (bIsDirectory)
153 m_bDirectoryKnown = true;
154 m_bIsDirectory = *bIsDirectory != FALSE;
156 if(oldpath)
157 m_sOldFwdslashPath = *oldpath;
160 void CTGitPath::SetFromWin(LPCTSTR pPath)
162 Reset();
163 m_sBackslashPath = pPath;
164 m_sBackslashPath.Replace(L"\\\\?\\", L"");
165 SanitizeRootPath(m_sBackslashPath, false);
166 ATLASSERT(m_sBackslashPath.Find('/')<0);
168 void CTGitPath::SetFromWin(const CString& sPath)
170 Reset();
171 m_sBackslashPath = sPath;
172 m_sBackslashPath.Replace(L"\\\\?\\", L"");
173 SanitizeRootPath(m_sBackslashPath, false);
175 void CTGitPath::SetFromWin(LPCTSTR pPath, bool bIsDirectory)
177 Reset();
178 m_sBackslashPath = pPath;
179 m_bIsDirectory = bIsDirectory;
180 m_bDirectoryKnown = true;
181 SanitizeRootPath(m_sBackslashPath, false);
183 void CTGitPath::SetFromWin(const CString& sPath, bool bIsDirectory)
185 Reset();
186 m_sBackslashPath = sPath;
187 m_bIsDirectory = bIsDirectory;
188 m_bDirectoryKnown = true;
189 SanitizeRootPath(m_sBackslashPath, false);
191 void CTGitPath::SetFromUnknown(const CString& sPath)
193 Reset();
194 // Just set whichever path we think is most likely to be used
195 // GitAdminDir admin;
196 // CString p;
197 // if(admin.HasAdminDir(sPath,&p))
198 // SetFwdslashPath(sPath.Right(sPath.GetLength()-p.GetLength()));
199 // else
200 SetFwdslashPath(sPath);
203 LPCTSTR CTGitPath::GetWinPath() const
205 if(IsEmpty())
206 return L"";
207 if(m_sBackslashPath.IsEmpty())
208 SetBackslashPath(m_sFwdslashPath);
209 return m_sBackslashPath;
211 // This is a temporary function, to be used during the migration to
212 // the path class. Ultimately, functions consuming paths should take a CTGitPath&, not a CString
213 const CString& CTGitPath::GetWinPathString() const
215 if(m_sBackslashPath.IsEmpty())
216 SetBackslashPath(m_sFwdslashPath);
217 return m_sBackslashPath;
220 const CString& CTGitPath::GetGitPathString() const
222 if(m_sFwdslashPath.IsEmpty())
223 SetFwdslashPath(m_sBackslashPath);
224 return m_sFwdslashPath;
227 const CString &CTGitPath::GetGitOldPathString() const
229 return m_sOldFwdslashPath;
232 const CString& CTGitPath::GetUIPathString() const
234 if (m_sUIPath.IsEmpty())
235 m_sUIPath = GetWinPathString();
236 return m_sUIPath;
239 void CTGitPath::SetFwdslashPath(const CString& sPath) const
241 CString path = sPath;
242 path.Replace('\\', '/');
244 // We don't leave a trailing /
245 path.TrimRight('/');
246 path.Replace(L"//?/", L"");
248 SanitizeRootPath(path, true);
250 path.Replace(L"file:////", L"file://");
251 m_sFwdslashPath = path;
254 void CTGitPath::SetBackslashPath(const CString& sPath) const
256 CString path = sPath;
257 path.Replace('/', '\\');
258 path.TrimRight('\\');
259 SanitizeRootPath(path, false);
260 m_sBackslashPath = path;
263 void CTGitPath::SanitizeRootPath(CString& sPath, bool bIsForwardPath) const
265 // Make sure to add the trailing slash to root paths such as 'C:'
266 if (sPath.GetLength() == 2 && sPath[1] == ':')
267 sPath += (bIsForwardPath) ? L'/' : L'\\';
270 bool CTGitPath::IsDirectory() const
272 if(!m_bDirectoryKnown)
273 UpdateAttributes();
274 return m_bIsDirectory;
277 bool CTGitPath::Exists() const
279 if (!m_bExistsKnown)
280 UpdateAttributes();
281 return m_bExists;
284 bool CTGitPath::Delete(bool bTrash, bool bShowErrorUI) const
286 EnsureBackslashPathSet();
287 ::SetFileAttributes(m_sBackslashPath, FILE_ATTRIBUTE_NORMAL);
288 bool bRet = false;
289 if (Exists())
291 if ((bTrash)||(IsDirectory()))
293 auto buf = std::make_unique<TCHAR[]>(m_sBackslashPath.GetLength() + 2);
294 wcscpy_s(buf.get(), m_sBackslashPath.GetLength() + 2, m_sBackslashPath);
295 buf[m_sBackslashPath.GetLength()] = L'\0';
296 buf[m_sBackslashPath.GetLength() + 1] = L'\0';
297 bRet = CTGitPathList::DeleteViaShell(buf.get(), bTrash, bShowErrorUI);
299 else
300 bRet = !!::DeleteFile(m_sBackslashPath);
302 m_bExists = false;
303 m_bExistsKnown = true;
304 return bRet;
307 __int64 CTGitPath::GetLastWriteTime() const
309 if(!m_bLastWriteTimeKnown)
310 UpdateAttributes();
311 return m_lastWriteTime;
314 __int64 CTGitPath::GetFileSize() const
316 if(!m_bDirectoryKnown)
317 UpdateAttributes();
318 return m_fileSize;
321 bool CTGitPath::IsReadOnly() const
323 if(!m_bLastWriteTimeKnown)
324 UpdateAttributes();
325 return m_bIsReadOnly;
328 void CTGitPath::UpdateAttributes() const
330 EnsureBackslashPathSet();
331 WIN32_FILE_ATTRIBUTE_DATA attribs;
332 if (m_sBackslashPath.GetLength() >= 248)
334 if (!PathIsRelative(m_sBackslashPath))
335 m_sLongBackslashPath = L"\\\\?\\" + m_sBackslashPath;
336 else
337 m_sLongBackslashPath = L"\\\\?\\" + g_Git.CombinePath(m_sBackslashPath);
339 if(GetFileAttributesEx(m_sBackslashPath.GetLength() >= 248 ? m_sLongBackslashPath : m_sBackslashPath, GetFileExInfoStandard, &attribs))
341 m_bIsDirectory = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
342 // don't cast directly to an __int64:
343 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284%28v=vs.85%29.aspx
344 // "Do not cast a pointer to a FILETIME structure to either a ULARGE_INTEGER* or __int64* value
345 // because it can cause alignment faults on 64-bit Windows."
346 m_lastWriteTime = static_cast<__int64>(attribs.ftLastWriteTime.dwHighDateTime) << 32 | attribs.ftLastWriteTime.dwLowDateTime;
347 if (m_bIsDirectory)
348 m_fileSize = 0;
349 else
350 m_fileSize = ((INT64)( (DWORD)(attribs.nFileSizeLow) ) | ( (INT64)( (DWORD)(attribs.nFileSizeHigh) )<<32 ));
351 m_bIsReadOnly = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
352 m_bExists = true;
354 else
356 m_bIsDirectory = false;
357 m_lastWriteTime = 0;
358 m_fileSize = 0;
359 DWORD err = GetLastError();
360 if ((err == ERROR_FILE_NOT_FOUND)||(err == ERROR_PATH_NOT_FOUND)||(err == ERROR_INVALID_NAME))
361 m_bExists = false;
362 else
364 m_bExists = true;
365 return;
368 m_bDirectoryKnown = true;
369 m_bLastWriteTimeKnown = true;
370 m_bExistsKnown = true;
373 CTGitPath CTGitPath::GetSubPath(const CTGitPath &root)
375 CTGitPath path;
377 if (CStringUtils::StartsWith(GetWinPathString(), root.GetWinPathString()))
379 CString str=GetWinPathString();
380 path.SetFromWin(str.Right(str.GetLength()-root.GetWinPathString().GetLength()-1));
382 return path;
385 void CTGitPath::EnsureBackslashPathSet() const
387 if(m_sBackslashPath.IsEmpty())
389 SetBackslashPath(m_sFwdslashPath);
390 ATLASSERT(IsEmpty() || !m_sBackslashPath.IsEmpty());
393 void CTGitPath::EnsureFwdslashPathSet() const
395 if(m_sFwdslashPath.IsEmpty())
397 SetFwdslashPath(m_sBackslashPath);
398 ATLASSERT(IsEmpty() || !m_sFwdslashPath.IsEmpty());
403 // Reset all the caches
404 void CTGitPath::Reset()
406 m_bDirectoryKnown = false;
407 m_bLastWriteTimeKnown = false;
408 m_bHasAdminDirKnown = false;
409 m_bIsValidOnWindowsKnown = false;
410 m_bIsAdminDirKnown = false;
411 m_bExistsKnown = false;
413 m_sBackslashPath.Empty();
414 m_sFwdslashPath.Empty();
415 this->m_Action=0;
416 this->m_StatAdd.Empty();
417 this->m_StatDel.Empty();
418 m_ParentNo=0;
419 ATLASSERT(IsEmpty());
422 CTGitPath CTGitPath::GetDirectory() const
424 if ((IsDirectory())||(!Exists()))
425 return *this;
426 return GetContainingDirectory();
429 CTGitPath CTGitPath::GetContainingDirectory() const
431 EnsureBackslashPathSet();
433 CString sDirName = m_sBackslashPath.Left(m_sBackslashPath.ReverseFind('\\'));
434 if(sDirName.GetLength() == 2 && sDirName[1] == ':')
436 // This is a root directory, which needs a trailing slash
437 sDirName += '\\';
438 if(sDirName == m_sBackslashPath)
440 // We were clearly provided with a root path to start with - we should return nothing now
441 sDirName.Empty();
444 if(sDirName.GetLength() == 1 && sDirName[0] == '\\')
446 // We have an UNC path and we already are the root
447 sDirName.Empty();
449 CTGitPath retVal;
450 retVal.SetFromWin(sDirName);
451 return retVal;
454 CString CTGitPath::GetRootPathString() const
456 EnsureBackslashPathSet();
457 CString workingPath = m_sBackslashPath;
458 ATLVERIFY(::PathStripToRoot(CStrBuf(workingPath, MAX_PATH))); // MAX_PATH ok here.
459 return workingPath;
463 CString CTGitPath::GetFilename() const
465 //ATLASSERT(!IsDirectory());
466 return GetFileOrDirectoryName();
469 CString CTGitPath::GetFileOrDirectoryName() const
471 EnsureBackslashPathSet();
472 return m_sBackslashPath.Mid(m_sBackslashPath.ReverseFind('\\')+1);
475 CString CTGitPath::GetUIFileOrDirectoryName() const
477 GetUIPathString();
478 return m_sUIPath.Mid(m_sUIPath.ReverseFind('\\')+1);
481 CString CTGitPath::GetFileExtension() const
483 if(!IsDirectory())
485 EnsureBackslashPathSet();
486 int dotPos = m_sBackslashPath.ReverseFind('.');
487 int slashPos = m_sBackslashPath.ReverseFind('\\');
488 if (dotPos > slashPos)
489 return m_sBackslashPath.Mid(dotPos);
491 return CString();
493 CString CTGitPath::GetBaseFilename() const
495 int dot;
496 CString filename=GetFilename();
497 dot = filename.ReverseFind(L'.');
498 if(dot>0)
499 filename.Truncate(dot);
500 return filename;
503 bool CTGitPath::ArePathStringsEqual(const CString& sP1, const CString& sP2)
505 int length = sP1.GetLength();
506 if(length != sP2.GetLength())
508 // Different lengths
509 return false;
511 // We work from the end of the strings, because path differences
512 // are more likely to occur at the far end of a string
513 LPCTSTR pP1Start = sP1;
514 LPCTSTR pP1 = pP1Start+(length-1);
515 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
516 while(length-- > 0)
518 if(_totlower(*pP1--) != _totlower(*pP2--))
519 return false;
521 return true;
524 bool CTGitPath::ArePathStringsEqualWithCase(const CString& sP1, const CString& sP2)
526 int length = sP1.GetLength();
527 if(length != sP2.GetLength())
529 // Different lengths
530 return false;
532 // We work from the end of the strings, because path differences
533 // are more likely to occur at the far end of a string
534 LPCTSTR pP1Start = sP1;
535 LPCTSTR pP1 = pP1Start+(length-1);
536 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
537 while(length-- > 0)
539 if((*pP1--) != (*pP2--))
540 return false;
542 return true;
545 bool CTGitPath::IsEmpty() const
547 // Check the backward slash path first, since the chance that this
548 // one is set is higher. In case of a 'false' return value it's a little
549 // bit faster.
550 return m_sBackslashPath.IsEmpty() && m_sFwdslashPath.IsEmpty();
553 // Test if both paths refer to the same item
554 // Ignores case and slash direction
555 bool CTGitPath::IsEquivalentTo(const CTGitPath& rhs) const
557 // Try and find a slash direction which avoids having to convert
558 // both filenames
559 if(!m_sBackslashPath.IsEmpty())
561 // *We've* got a \ path - make sure that the RHS also has a \ path
562 rhs.EnsureBackslashPathSet();
563 return ArePathStringsEqualWithCase(m_sBackslashPath, rhs.m_sBackslashPath);
565 else
567 // Assume we've got a fwdslash path and make sure that the RHS has one
568 rhs.EnsureFwdslashPathSet();
569 return ArePathStringsEqualWithCase(m_sFwdslashPath, rhs.m_sFwdslashPath);
573 bool CTGitPath::IsEquivalentToWithoutCase(const CTGitPath& rhs) const
575 // Try and find a slash direction which avoids having to convert
576 // both filenames
577 if(!m_sBackslashPath.IsEmpty())
579 // *We've* got a \ path - make sure that the RHS also has a \ path
580 rhs.EnsureBackslashPathSet();
581 return ArePathStringsEqual(m_sBackslashPath, rhs.m_sBackslashPath);
583 else
585 // Assume we've got a fwdslash path and make sure that the RHS has one
586 rhs.EnsureFwdslashPathSet();
587 return ArePathStringsEqual(m_sFwdslashPath, rhs.m_sFwdslashPath);
591 bool CTGitPath::IsAncestorOf(const CTGitPath& possibleDescendant) const
593 possibleDescendant.EnsureBackslashPathSet();
594 EnsureBackslashPathSet();
596 bool bPathStringsEqual = ArePathStringsEqual(m_sBackslashPath, possibleDescendant.m_sBackslashPath.Left(m_sBackslashPath.GetLength()));
597 if (m_sBackslashPath.GetLength() >= possibleDescendant.GetWinPathString().GetLength())
599 return bPathStringsEqual;
602 return (bPathStringsEqual &&
603 ((possibleDescendant.m_sBackslashPath[m_sBackslashPath.GetLength()] == '\\')||
604 (m_sBackslashPath.GetLength()==3 && m_sBackslashPath[1]==':')));
607 // Get a string representing the file path, optionally with a base
608 // section stripped off the front.
609 CString CTGitPath::GetDisplayString(const CTGitPath* pOptionalBasePath /* = nullptr*/) const
611 EnsureFwdslashPathSet();
612 if (pOptionalBasePath)
614 // Find the length of the base-path without having to do an 'ensure' on it
615 int baseLength = max(pOptionalBasePath->m_sBackslashPath.GetLength(), pOptionalBasePath->m_sFwdslashPath.GetLength());
617 // Now, chop that baseLength of the front of the path
618 return m_sFwdslashPath.Mid(baseLength).TrimLeft('/');
620 return m_sFwdslashPath;
623 int CTGitPath::Compare(const CTGitPath& left, const CTGitPath& right)
625 left.EnsureBackslashPathSet();
626 right.EnsureBackslashPathSet();
627 return CStringUtils::FastCompareNoCase(left.m_sBackslashPath, right.m_sBackslashPath);
630 bool operator<(const CTGitPath& left, const CTGitPath& right)
632 return CTGitPath::Compare(left, right) < 0;
635 bool CTGitPath::PredLeftEquivalentToRight(const CTGitPath& left, const CTGitPath& right)
637 return left.IsEquivalentTo(right);
640 bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right)
642 if (left.IsAdminDir() && right.IsAdminDir())
644 CTGitPath l = left;
645 CTGitPath r = right;
648 l = l.GetContainingDirectory();
649 } while(l.HasAdminDir());
652 r = r.GetContainingDirectory();
653 } while(r.HasAdminDir());
654 return l.GetContainingDirectory().IsEquivalentTo(r.GetContainingDirectory());
656 return left.GetDirectory().IsEquivalentTo(right.GetDirectory());
659 bool CTGitPath::CheckChild(const CTGitPath &parent, const CTGitPath& child)
661 return parent.IsAncestorOf(child);
664 void CTGitPath::AppendRawString(const CString& sAppend)
666 EnsureFwdslashPathSet();
667 CString strCopy = m_sFwdslashPath += sAppend;
668 SetFromUnknown(strCopy);
671 void CTGitPath::AppendPathString(const CString& sAppend)
673 EnsureBackslashPathSet();
674 CString cleanAppend(sAppend);
675 cleanAppend.Replace(L'/', L'\\');
676 cleanAppend.TrimLeft(L'\\');
677 m_sBackslashPath.TrimRight(L'\\');
678 CString strCopy = m_sBackslashPath;
679 strCopy += L'\\';
680 strCopy += cleanAppend;
681 SetFromWin(strCopy);
684 bool CTGitPath::IsWCRoot() const
686 if (m_bIsWCRootKnown)
687 return m_bIsWCRoot;
689 m_bIsWCRootKnown = true;
690 m_bIsWCRoot = false;
692 CString topDirectory;
693 if (!IsDirectory() || !HasAdminDir(&topDirectory))
694 return m_bIsWCRoot;
696 if (IsEquivalentToWithoutCase(topDirectory))
697 m_bIsWCRoot = true;
699 return m_bIsWCRoot;
702 bool CTGitPath::HasSubmodules() const
704 if (HasAdminDir())
706 CString path = m_sProjectRoot;
707 path += L"\\.gitmodules";
708 if( PathFileExists(path) )
709 return true;
711 return false;
714 int CTGitPath::GetAdminDirMask() const
716 int status = 0;
717 if (!HasAdminDir())
718 return status;
720 // ITEMIS_INGIT will be revoked if necessary in TortoiseShell/ContextMenu.cpp
721 status |= ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER;
723 if (IsDirectory())
725 status |= ITEMIS_FOLDERINGIT;
726 if (IsWCRoot())
728 status |= ITEMIS_WCROOT;
730 CString topProjectDir;
731 if (GitAdminDir::HasAdminDir(GetWinPathString(), false, &topProjectDir))
733 if (PathFileExists(topProjectDir + L"\\.gitmodules"))
735 CAutoConfig config(true);
736 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(topProjectDir + L"\\.gitmodules"), GIT_CONFIG_LEVEL_APP, FALSE);
737 CString relativePath = GetWinPathString().Mid(topProjectDir.GetLength());
738 relativePath.Replace(L'\\', L'/');
739 relativePath.Trim(L'/');
740 CStringA submodulePath = CUnicodeUtils::GetUTF8(relativePath);
741 if (git_config_foreach_match(config, "submodule\\..*\\.path",
742 [](const git_config_entry *entry, void *data) { return entry->value == *(CStringA *)data ? GIT_EUSER : 0; }, &submodulePath) == GIT_EUSER)
743 status |= ITEMIS_SUBMODULE;
749 CString dotGitPath;
750 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
752 if (PathFileExists(dotGitPath + L"BISECT_START"))
753 status |= ITEMIS_BISECT;
755 if (PathFileExists(dotGitPath + L"MERGE_HEAD"))
756 status |= ITEMIS_MERGEACTIVE;
758 if (HasStashDir(dotGitPath))
759 status |= ITEMIS_STASH;
761 if (PathFileExists(dotGitPath + L"svn"))
762 status |= ITEMIS_GITSVN;
764 if (PathFileExists(m_sProjectRoot + L"\\.gitmodules"))
765 status |= ITEMIS_SUBMODULECONTAINER;
767 return status;
770 bool CTGitPath::HasStashDir(const CString& dotGitPath) const
772 if (PathFileExists(dotGitPath + L"refs\\stash"))
773 return true;
775 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);
776 if (!hfile)
777 return false;
779 DWORD filesize = ::GetFileSize(hfile, nullptr);
780 if (filesize == 0)
781 return false;
783 DWORD size = 0;
784 auto buff = std::make_unique<char[]>(filesize + 1);
785 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
786 buff.get()[filesize] = '\0';
788 if (size != filesize)
789 return false;
791 for (DWORD i = 0; i < filesize;)
793 if (buff[i] == '#' || buff[i] == '^')
795 while (buff[i] != '\n')
797 ++i;
798 if (i == filesize)
799 break;
801 ++i;
804 if (i >= filesize)
805 break;
807 while (buff[i] != ' ')
809 ++i;
810 if (i == filesize)
811 break;
814 ++i;
815 if (i >= filesize)
816 break;
818 if (i <= filesize - 10 && (buff[i + 10] == '\n' || buff[i + 10] == '\0') && !strncmp("refs/stash", buff.get() + i, 10))
819 return true;
820 while (buff[i] != '\n')
822 ++i;
823 if (i == filesize)
824 break;
827 while (buff[i] == '\n')
829 ++i;
830 if (i == filesize)
831 break;
834 return false;
837 bool CTGitPath::HasStashDir() const
839 if (!HasAdminDir())
840 return false;
842 CString dotGitPath;
843 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
845 return HasStashDir(dotGitPath);
848 bool CTGitPath::HasGitSVNDir() const
850 if (!HasAdminDir())
851 return false;
853 CString dotGitPath;
854 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
856 return !!PathFileExists(dotGitPath + L"svn");
858 bool CTGitPath::IsBisectActive() const
860 if (!HasAdminDir())
861 return false;
863 CString dotGitPath;
864 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
866 return !!PathFileExists(dotGitPath + L"BISECT_START");
868 bool CTGitPath::IsMergeActive() const
870 if (!HasAdminDir())
871 return false;
873 CString dotGitPath;
874 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
876 return !!PathFileExists(dotGitPath + L"MERGE_HEAD");
878 bool CTGitPath::HasRebaseApply() const
880 if (!HasAdminDir())
881 return false;
883 CString dotGitPath;
884 GitAdminDir::GetAdminDirPath(m_sProjectRoot, dotGitPath);
886 return !!PathFileExists(dotGitPath + L"rebase-apply");
889 bool CTGitPath::HasAdminDir(CString *ProjectTopDir) const
891 if (m_bHasAdminDirKnown)
893 if (ProjectTopDir)
894 *ProjectTopDir = m_sProjectRoot;
895 return m_bHasAdminDir;
898 EnsureBackslashPathSet();
899 bool isAdminDir = false;
900 m_bHasAdminDir = GitAdminDir::HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
901 m_bHasAdminDirKnown = true;
902 if ((m_bHasAdminDir || isAdminDir) && !m_bIsAdminDirKnown)
904 m_bIsAdminDir = isAdminDir;
905 m_bIsAdminDirKnown = true;
907 if (ProjectTopDir)
908 *ProjectTopDir = m_sProjectRoot;
909 return m_bHasAdminDir;
912 bool CTGitPath::IsAdminDir() const
914 if (m_bIsAdminDirKnown)
915 return m_bIsAdminDir;
917 EnsureBackslashPathSet();
918 m_bIsAdminDir = GitAdminDir::IsAdminDirPath(m_sBackslashPath);
919 m_bIsAdminDirKnown = true;
920 if (m_bIsAdminDir && !m_bIsAdminDirKnown)
922 m_bHasAdminDir = false;
923 m_bIsAdminDirKnown = true;
925 return m_bIsAdminDir;
928 bool CTGitPath::IsValidOnWindows() const
930 if (m_bIsValidOnWindowsKnown)
931 return m_bIsValidOnWindows;
933 m_bIsValidOnWindows = false;
934 EnsureBackslashPathSet();
935 CString sMatch = m_sBackslashPath + L"\r\n";
936 std::wstring sPattern;
937 // the 'file://' URL is just a normal windows path:
938 if (CStringUtils::StartsWithI(sMatch, L"file:\\\\"))
940 sMatch = sMatch.Mid(7);
941 sMatch.TrimLeft(L'\\');
942 sPattern = L"^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$";
944 else
945 sPattern = L"^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$";
949 std::tr1::wregex rx(sPattern, std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
950 std::tr1::wsmatch match;
952 std::wstring rmatch = std::wstring((LPCTSTR)sMatch);
953 if (std::tr1::regex_match(rmatch, match, rx))
955 if (std::wstring(match[0]).compare(sMatch)==0)
956 m_bIsValidOnWindows = true;
958 if (m_bIsValidOnWindows)
960 // now check for illegal filenames
961 std::tr1::wregex rx2(L"\\\\(lpt\\d|com\\d|aux|nul|prn|con)(\\\\|$)", std::tr1::regex_constants::icase | std::tr1::regex_constants::ECMAScript);
962 rmatch = m_sBackslashPath;
963 if (std::tr1::regex_search(rmatch, rx2, std::tr1::regex_constants::match_default))
964 m_bIsValidOnWindows = false;
967 catch (std::exception&) {}
969 m_bIsValidOnWindowsKnown = true;
970 return m_bIsValidOnWindows;
973 //////////////////////////////////////////////////////////////////////////
975 CTGitPathList::CTGitPathList()
977 m_Action = 0;
980 // A constructor which allows a path list to be easily built which one initial entry in
981 CTGitPathList::CTGitPathList(const CTGitPath& firstEntry)
983 m_Action = 0;
984 AddPath(firstEntry);
986 int CTGitPathList::ParserFromLsFile(BYTE_VECTOR &out,bool /*staged*/)
988 size_t pos = 0;
989 CString one;
990 CTGitPath path;
991 CString part;
992 this->Clear();
994 while (pos < out.size())
996 one.Empty();
997 path.Reset();
999 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1000 int tabstart=0;
1001 // m_Action is never used and propably never worked (needs to be set after path.SetFromGit)
1002 // also dropped LOGACTIONS_CACHE for 'H'
1003 // path.m_Action=path.ParserAction(out[pos]);
1004 one.Tokenize(L"\t", tabstart);
1006 if (tabstart < 0)
1007 return -1;
1009 CString pathstring = one.Right(one.GetLength() - tabstart);
1011 tabstart=0;
1013 part = one.Tokenize(L" ", tabstart); //Tag
1014 if (tabstart < 0)
1015 return -1;
1017 part = one.Tokenize(L" ", tabstart); //Mode
1018 if (tabstart < 0)
1019 return -1;
1020 int mode = wcstol(part, nullptr, 8);
1021 path.SetFromGit(pathstring, (mode & S_IFDIR) == S_IFDIR);
1023 part = one.Tokenize(L" ", tabstart); //Hash
1024 if (tabstart < 0)
1025 return -1;
1027 part = one.Tokenize(L"\t", tabstart); //Stage
1028 if (tabstart < 0)
1029 return -1;
1031 path.m_Stage = _wtol(part);
1033 this->AddPath(path);
1035 pos=out.findNextString(pos);
1037 return 0;
1040 int CTGitPathList::FillUnRev(unsigned int action, const CTGitPathList* list, CString* err)
1042 this->Clear();
1043 CTGitPath path;
1045 int count;
1046 if (!list)
1047 count=1;
1048 else
1049 count=list->GetCount();
1050 for (int i = 0; i < count; ++i)
1052 CString cmd;
1053 CString ignored;
1054 if(action & CTGitPath::LOGACTIONS_IGNORE)
1055 ignored = L" -i";
1057 if (!list)
1059 cmd = L"git.exe ls-files --exclude-standard --full-name --others -z";
1060 cmd+=ignored;
1063 else
1065 cmd.Format(L"git.exe ls-files --exclude-standard --full-name --others -z%s -- \"%s\"",
1066 (LPCTSTR)ignored,
1067 (*list)[i].GetWinPath());
1070 BYTE_VECTOR out, errb;
1071 out.clear();
1072 if (g_Git.Run(cmd, &out, &errb))
1074 if (err != nullptr)
1075 CGit::StringAppend(err, &errb[0], CP_UTF8, (int)errb.size());
1076 return -1;
1079 size_t pos = 0;
1080 CString one;
1081 while (pos < out.size())
1083 one.Empty();
1084 CGit::StringAppend(&one, &out[pos], CP_UTF8);
1085 if(!one.IsEmpty())
1087 //SetFromGit will clear all status
1088 if (CStringUtils::EndsWith(one, L'/'))
1090 one.Truncate(one.GetLength() - 1);
1091 path.SetFromGit(one, true);
1093 else
1094 path.SetFromGit(one);
1095 path.m_Action=action;
1096 AddPath(path);
1098 pos=out.findNextString(pos);
1102 return 0;
1105 int CTGitPathList::FillBasedOnIndexFlags(unsigned short flag, unsigned short flagextended, const CTGitPathList* list /*nullptr*/)
1107 Clear();
1108 CTGitPath path;
1110 CAutoRepository repository(g_Git.GetGitRepository());
1111 if (!repository)
1112 return -1;
1114 CAutoIndex index;
1115 if (git_repository_index(index.GetPointer(), repository))
1116 return -1;
1118 int count;
1119 if (list == nullptr)
1120 count = 1;
1121 else
1122 count = list->GetCount();
1123 for (int j = 0; j < count; ++j)
1125 for (size_t i = 0, ecount = git_index_entrycount(index); i < ecount; ++i)
1127 const git_index_entry *e = git_index_get_byindex(index, i);
1129 if (!e || !((e->flags & flag) || (e->flags_extended & flagextended)) || !e->path)
1130 continue;
1132 CString one = CUnicodeUtils::GetUnicode(e->path);
1134 if (!(!list || (*list)[j].GetWinPathString().IsEmpty() || one == (*list)[j].GetGitPathString() || (PathIsDirectory(g_Git.CombinePath((*list)[j].GetWinPathString())) && CStringUtils::StartsWith(one, (*list)[j].GetGitPathString() + L'/'))))
1135 continue;
1137 //SetFromGit will clear all status
1138 path.SetFromGit(one, (e->mode & S_IFDIR) == S_IFDIR);
1139 if (e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE)
1140 path.m_Action = CTGitPath::LOGACTIONS_SKIPWORKTREE;
1141 else if (e->flags & GIT_IDXENTRY_VALID)
1142 path.m_Action = CTGitPath::LOGACTIONS_ASSUMEVALID;
1143 AddPath(path);
1146 RemoveDuplicates();
1147 return 0;
1149 int CTGitPathList::ParserFromLog(BYTE_VECTOR &log, bool parseDeletes /*false*/)
1151 this->Clear();
1152 std::map<CString, size_t> duplicateMap;
1153 size_t pos = 0;
1154 CTGitPath path;
1155 m_Action=0;
1156 size_t logend = log.size();
1157 while (pos < logend)
1159 path.Reset();
1160 if(log[pos]=='\n')
1161 ++pos;
1163 if (pos >= logend)
1164 return -1;
1166 if(log[pos]==':')
1168 bool merged=false;
1169 if (pos + 1 >= logend)
1170 return -1;
1171 if (log[pos + 1] == ':')
1173 merged = true;
1174 ++pos;
1177 int modenew = 0;
1178 int modeold = 0;
1179 size_t end = log.find(0, pos);
1180 size_t actionstart = BYTE_VECTOR::npos;
1181 size_t file1 = BYTE_VECTOR::npos, file2 = BYTE_VECTOR::npos;
1182 if (end != BYTE_VECTOR::npos && end > 7)
1184 modeold = strtol((const char*)&log[pos + 1], nullptr, 8);
1185 modenew = strtol((const char*)&log[pos + 7], nullptr, 8);
1186 actionstart=log.find(' ',end-6);
1187 pos=actionstart;
1189 if (actionstart != BYTE_VECTOR::npos && actionstart > 0)
1191 ++actionstart;
1192 if (actionstart >= logend)
1193 return -1;
1195 file1 = log.find(0,actionstart);
1196 if (file1 != BYTE_VECTOR::npos)
1198 ++file1;
1199 pos=file1;
1201 if( log[actionstart] == 'C' || log[actionstart] == 'R' )
1203 file2=file1;
1204 file1 = log.find(0,file1);
1205 if (file1 != BYTE_VECTOR::npos)
1207 ++file1;
1208 pos=file1;
1213 CString pathname1;
1214 CString pathname2;
1216 if (file1 != BYTE_VECTOR::npos)
1217 CGit::StringAppend(&pathname1, &log[file1], CP_UTF8);
1218 if (file2 != BYTE_VECTOR::npos)
1219 CGit::StringAppend(&pathname2, &log[file2], CP_UTF8);
1221 if (actionstart == BYTE_VECTOR::npos)
1222 return -1;
1224 auto existing = duplicateMap.find(pathname1);
1225 if (existing != duplicateMap.end())
1227 CTGitPath& p = m_paths[existing->second];
1228 p.ParserAction(log[actionstart]);
1230 // reset submodule/folder status if a staged entry is not a folder
1231 if (p.IsDirectory() && ((modeold && !(modeold & S_IFDIR)) || (modenew && !(modenew & S_IFDIR))))
1232 p.UnsetDirectoryStatus();
1234 if(merged)
1235 p.m_Action |= CTGitPath::LOGACTIONS_MERGED;
1236 m_Action |= p.m_Action;
1238 else
1240 int ac=path.ParserAction(log[actionstart] );
1241 ac |= merged?CTGitPath::LOGACTIONS_MERGED:0;
1243 int isSubmodule = FALSE;
1244 if (ac & (CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_UNMERGED))
1245 isSubmodule = (modeold & S_IFDIR) == S_IFDIR;
1246 else
1247 isSubmodule = (modenew & S_IFDIR) == S_IFDIR;
1249 path.SetFromGit(pathname1, &pathname2, &isSubmodule);
1250 path.m_Action=ac;
1251 //action must be set after setfromgit. SetFromGit will clear all status.
1252 this->m_Action|=ac;
1254 AddPath(path);
1255 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1258 else
1260 path.Reset();
1261 CString StatAdd;
1262 CString StatDel;
1263 CString file1;
1264 CString file2;
1265 int isSubmodule = FALSE;
1266 size_t tabstart = log.find('\t', pos);
1267 if (tabstart != BYTE_VECTOR::npos)
1269 int modenew = strtol((const char*)&log[pos + 2], nullptr, 8);
1270 isSubmodule = (modenew & S_IFDIR) == S_IFDIR;
1271 log[tabstart]=0;
1272 CGit::StringAppend(&StatAdd, &log[pos], CP_UTF8);
1273 pos=tabstart+1;
1276 tabstart=log.find('\t',pos);
1277 if (tabstart != BYTE_VECTOR::npos)
1279 log[tabstart]=0;
1281 CGit::StringAppend(&StatDel, &log[pos], CP_UTF8);
1282 pos=tabstart+1;
1285 if(log[pos] == 0) //rename
1287 ++pos;
1288 CGit::StringAppend(&file2, &log[pos], CP_UTF8);
1289 size_t sec = log.find(0, pos);
1290 if (sec != BYTE_VECTOR::npos)
1292 ++sec;
1293 CGit::StringAppend(&file1, &log[sec], CP_UTF8);
1295 pos=sec;
1297 else
1299 CGit::StringAppend(&file1, &log[pos], CP_UTF8);
1301 path.SetFromGit(file1, &file2, &isSubmodule);
1303 auto existing = duplicateMap.find(path.GetGitPathString());
1304 if (existing != duplicateMap.end())
1306 CTGitPath& p = m_paths[existing->second];
1307 p.m_StatAdd = StatAdd;
1308 p.m_StatDel = StatDel;
1310 else
1312 //path.SetFromGit(pathname);
1313 if (parseDeletes)
1315 path.m_StatAdd = L"0";
1316 path.m_StatDel = L"0";
1317 path.m_Action |= CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_MISSING;
1319 else
1321 path.m_StatAdd=StatAdd;
1322 path.m_StatDel=StatDel;
1324 AddPath(path);
1325 duplicateMap.insert(std::pair<CString, size_t>(path.GetGitPathString(), m_paths.size() - 1));
1328 pos=log.findNextString(pos);
1330 return 0;
1333 void CTGitPathList::AddPath(const CTGitPath& newPath)
1335 m_paths.push_back(newPath);
1336 m_commonBaseDirectory.Reset();
1338 int CTGitPathList::GetCount() const
1340 return (int)m_paths.size();
1342 bool CTGitPathList::IsEmpty() const
1344 return m_paths.empty();
1346 void CTGitPathList::Clear()
1348 m_paths.clear();
1349 m_commonBaseDirectory.Reset();
1352 const CTGitPath& CTGitPathList::operator[](INT_PTR index) const
1354 ATLASSERT(index >= 0 && index < (INT_PTR)m_paths.size());
1355 return m_paths[index];
1358 bool CTGitPathList::AreAllPathsFiles() const
1360 // Look through the vector for any directories - if we find them, return false
1361 return std::find_if(m_paths.cbegin(), m_paths.cend(), std::mem_fun_ref(&CTGitPath::IsDirectory)) == m_paths.end();
1364 #if defined(_MFC_VER)
1366 bool CTGitPathList::LoadFromFile(const CTGitPath& filename)
1368 Clear();
1371 CString strLine;
1372 CStdioFile file(filename.GetWinPath(), CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite);
1374 // for every selected file/folder
1375 CTGitPath path;
1376 while (file.ReadString(strLine))
1378 path.SetFromUnknown(strLine);
1379 AddPath(path);
1381 file.Close();
1383 catch (CFileException* pE)
1385 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException loading target file list\n");
1386 TCHAR error[10000] = {0};
1387 pE->GetErrorMessage(error, 10000);
1388 // CMessageBox::Show(nullptr, error, L"TortoiseGit", MB_ICONERROR);
1389 pE->Delete();
1390 return false;
1392 return true;
1395 bool CTGitPathList::WriteToFile(const CString& sFilename, bool bANSI /* = false */) const
1399 if (bANSI)
1401 CStdioFile file(sFilename, CFile::typeText | CFile::modeReadWrite | CFile::modeCreate);
1402 for (const auto& path : m_paths)
1404 CStringA line = CStringA(path.GetGitPathString()) + '\n';
1405 file.Write(line, line.GetLength());
1407 file.Close();
1409 else
1411 CStdioFile file(sFilename, CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
1412 for (const auto& path : m_paths)
1413 file.WriteString(path.GetGitPathString() + L'\n');
1414 file.Close();
1417 catch (CFileException* pE)
1419 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in writing temp file\n");
1420 pE->Delete();
1421 return false;
1423 return true;
1426 void CTGitPathList::LoadFromAsteriskSeparatedString(const CString& sPathString)
1428 int pos = 0;
1429 CString temp;
1430 for(;;)
1432 temp = sPathString.Tokenize(L"*", pos);
1433 if(temp.IsEmpty())
1434 break;
1435 AddPath(CTGitPath(CPathUtils::GetLongPathname(temp)));
1439 CString CTGitPathList::CreateAsteriskSeparatedString() const
1441 CString sRet;
1442 for (const auto& path : m_paths)
1444 if (!sRet.IsEmpty())
1445 sRet += L'*';
1446 sRet += path.GetWinPathString();
1448 return sRet;
1450 #endif // _MFC_VER
1452 bool
1453 CTGitPathList::AreAllPathsFilesInOneDirectory() const
1455 // Check if all the paths are files and in the same directory
1456 m_commonBaseDirectory.Reset();
1457 for (const auto& path : m_paths)
1459 if (path.IsDirectory())
1460 return false;
1461 const CTGitPath& baseDirectory = path.GetDirectory();
1462 if(m_commonBaseDirectory.IsEmpty())
1463 m_commonBaseDirectory = baseDirectory;
1464 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1466 // Different path
1467 m_commonBaseDirectory.Reset();
1468 return false;
1471 return true;
1474 CTGitPath CTGitPathList::GetCommonDirectory() const
1476 if (m_commonBaseDirectory.IsEmpty())
1478 for (const auto& path : m_paths)
1480 const CTGitPath& baseDirectory = path.GetDirectory();
1481 if(m_commonBaseDirectory.IsEmpty())
1482 m_commonBaseDirectory = baseDirectory;
1483 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1485 // Different path
1486 m_commonBaseDirectory.Reset();
1487 break;
1491 // since we only checked strings, not paths,
1492 // we have to make sure now that we really return a *path* here
1493 for (const auto& path : m_paths)
1495 if (!m_commonBaseDirectory.IsAncestorOf(path))
1497 m_commonBaseDirectory = m_commonBaseDirectory.GetContainingDirectory();
1498 break;
1501 return m_commonBaseDirectory;
1504 CTGitPath CTGitPathList::GetCommonRoot() const
1506 if (IsEmpty())
1507 return CTGitPath();
1509 if (GetCount() == 1)
1510 return m_paths[0];
1512 // first entry is common root for itself
1513 // (add trailing '\\' to detect partial matches of the last path element)
1514 CString root = m_paths[0].GetWinPathString() + L'\\';
1515 int rootLength = root.GetLength();
1517 // determine common path string prefix
1518 for (auto it = m_paths.cbegin() + 1; it != m_paths.cend(); ++it)
1520 CString path = it->GetWinPathString() + L'\\';
1522 int newLength = CStringUtils::GetMatchingLength(root, path);
1523 if (newLength != rootLength)
1525 root.Delete(newLength, rootLength);
1526 rootLength = newLength;
1530 // remove the last (partial) path element
1531 if (rootLength > 0)
1532 root.Delete(root.ReverseFind(L'\\'), rootLength);
1534 // done
1535 return CTGitPath(root);
1538 void CTGitPathList::SortByPathname(bool bReverse /*= false*/)
1540 std::sort(m_paths.begin(), m_paths.end());
1541 if (bReverse)
1542 std::reverse(m_paths.begin(), m_paths.end());
1545 void CTGitPathList::DeleteAllFiles(bool bTrash, bool bFilesOnly, bool bShowErrorUI)
1547 if (m_paths.empty())
1548 return;
1549 PathVector::const_iterator it;
1550 SortByPathname(true); // nested ones first
1552 CString sPaths;
1553 for (it = m_paths.cbegin(); it != m_paths.cend(); ++it)
1555 if ((it->Exists()) && ((it->IsDirectory() != bFilesOnly) || !bFilesOnly))
1557 if (!it->IsDirectory())
1558 ::SetFileAttributes(it->GetWinPath(), FILE_ATTRIBUTE_NORMAL);
1560 sPaths += it->GetWinPath();
1561 sPaths += '\0';
1564 if (sPaths.IsEmpty())
1565 return;
1566 sPaths += '\0';
1567 sPaths += '\0';
1568 DeleteViaShell((LPCTSTR)sPaths, bTrash, bShowErrorUI);
1569 Clear();
1572 bool CTGitPathList::DeleteViaShell(LPCTSTR path, bool bTrash, bool bShowErrorUI)
1574 SHFILEOPSTRUCT shop = {0};
1575 shop.wFunc = FO_DELETE;
1576 shop.pFrom = path;
1577 shop.fFlags = FOF_NOCONFIRMATION|FOF_NO_CONNECTED_ELEMENTS;
1578 if (!bShowErrorUI)
1579 shop.fFlags |= FOF_NOERRORUI | FOF_SILENT;
1580 if (bTrash)
1581 shop.fFlags |= FOF_ALLOWUNDO;
1582 const bool bRet = (SHFileOperation(&shop) == 0);
1583 return bRet;
1586 void CTGitPathList::RemoveDuplicates()
1588 SortByPathname();
1589 // Remove the duplicates
1590 // (Unique moves them to the end of the vector, then erase chops them off)
1591 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::PredLeftEquivalentToRight), m_paths.end());
1594 void CTGitPathList::RemoveAdminPaths()
1596 PathVector::iterator it;
1597 for(it = m_paths.begin(); it != m_paths.end(); )
1599 if (it->IsAdminDir())
1601 m_paths.erase(it);
1602 it = m_paths.begin();
1604 else
1605 ++it;
1609 void CTGitPathList::RemovePath(const CTGitPath& path)
1611 PathVector::iterator it;
1612 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1614 if (it->IsEquivalentTo(path))
1616 m_paths.erase(it);
1617 return;
1622 void CTGitPathList::RemoveItem(CTGitPath & path)
1624 PathVector::iterator it;
1625 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1627 if (CTGitPath::ArePathStringsEqualWithCase(it->GetGitPathString(), path.GetGitPathString()))
1629 m_paths.erase(it);
1630 return;
1634 void CTGitPathList::RemoveChildren()
1636 SortByPathname();
1637 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::CheckChild), m_paths.end());
1640 bool CTGitPathList::IsEqual(const CTGitPathList& list)
1642 if (list.GetCount() != GetCount())
1643 return false;
1644 for (int i=0; i<list.GetCount(); ++i)
1646 if (!list[i].IsEquivalentTo(m_paths[i]))
1647 return false;
1649 return true;
1652 const CTGitPath* CTGitPathList::LookForGitPath(const CString& path)
1654 int i=0;
1655 for (i = 0; i < this->GetCount(); ++i)
1657 if (CTGitPath::ArePathStringsEqualWithCase((*this)[i].GetGitPathString(), path))
1658 return (CTGitPath*)&(*this)[i];
1660 return nullptr;
1663 CString CTGitPath::GetActionName(int action)
1665 if(action & CTGitPath::LOGACTIONS_UNMERGED)
1666 return MAKEINTRESOURCE(IDS_PATHACTIONS_CONFLICT);
1667 if(action & CTGitPath::LOGACTIONS_ADDED)
1668 return MAKEINTRESOURCE(IDS_PATHACTIONS_ADD);
1669 if (action & CTGitPath::LOGACTIONS_MISSING)
1670 return MAKEINTRESOURCE(IDS_PATHACTIONS_MISSING);
1671 if(action & CTGitPath::LOGACTIONS_DELETED)
1672 return MAKEINTRESOURCE(IDS_PATHACTIONS_DELETE);
1673 if(action & CTGitPath::LOGACTIONS_MERGED )
1674 return MAKEINTRESOURCE(IDS_PATHACTIONS_MERGED);
1676 if(action & CTGitPath::LOGACTIONS_MODIFIED)
1677 return MAKEINTRESOURCE(IDS_PATHACTIONS_MODIFIED);
1678 if(action & CTGitPath::LOGACTIONS_REPLACED)
1679 return MAKEINTRESOURCE(IDS_PATHACTIONS_RENAME);
1680 if(action & CTGitPath::LOGACTIONS_COPY)
1681 return MAKEINTRESOURCE(IDS_PATHACTIONS_COPY);
1683 if (action & CTGitPath::LOGACTIONS_ASSUMEVALID)
1684 return MAKEINTRESOURCE(IDS_PATHACTIONS_ASSUMEUNCHANGED);
1685 if (action & CTGitPath::LOGACTIONS_SKIPWORKTREE)
1686 return MAKEINTRESOURCE(IDS_PATHACTIONS_SKIPWORKTREE);
1688 if (action & CTGitPath::LOGACTIONS_IGNORE)
1689 return MAKEINTRESOURCE(IDS_PATHACTIONS_IGNORED);
1691 return MAKEINTRESOURCE(IDS_PATHACTIONS_UNKNOWN);
1694 CString CTGitPath::GetActionName() const
1696 return GetActionName(m_Action);
1699 int CTGitPathList::GetAction()
1701 return m_Action;
1704 CString CTGitPath::GetAbbreviatedRename()
1706 if (GetGitOldPathString().IsEmpty())
1707 return GetFileOrDirectoryName();
1709 // Find common prefix which ends with a slash
1710 int prefix_length = 0;
1711 for (int i = 0, maxLength = min(m_sOldFwdslashPath.GetLength(), m_sFwdslashPath.GetLength()); i < maxLength; ++i)
1713 if (m_sOldFwdslashPath[i] != m_sFwdslashPath[i])
1714 break;
1715 if (m_sOldFwdslashPath[i] == L'/')
1716 prefix_length = i + 1;
1719 LPCTSTR oldName = (LPCTSTR)m_sOldFwdslashPath + m_sOldFwdslashPath.GetLength();
1720 LPCTSTR newName = (LPCTSTR)m_sFwdslashPath + m_sFwdslashPath.GetLength();
1722 int suffix_length = 0;
1723 int prefix_adjust_for_slash = (prefix_length ? 1 : 0);
1724 while ((LPCTSTR)m_sOldFwdslashPath + prefix_length - prefix_adjust_for_slash <= oldName &&
1725 (LPCTSTR)m_sFwdslashPath + prefix_length - prefix_adjust_for_slash <= newName &&
1726 *oldName == *newName)
1728 if (*oldName == L'/')
1729 suffix_length = m_sOldFwdslashPath.GetLength() - (int)(oldName - (LPCTSTR)m_sOldFwdslashPath);
1730 --oldName;
1731 --newName;
1735 * pfx{old_midlen => new_midlen}sfx
1736 * {pfx-old => pfx-new}sfx
1737 * pfx{sfx-old => sfx-new}
1738 * name-old => name-new
1740 int old_midlen = m_sOldFwdslashPath.GetLength() - prefix_length - suffix_length;
1741 int new_midlen = m_sFwdslashPath.GetLength() - prefix_length - suffix_length;
1742 if (old_midlen < 0)
1743 old_midlen = 0;
1744 if (new_midlen < 0)
1745 new_midlen = 0;
1747 CString ret;
1748 if (prefix_length + suffix_length)
1750 ret = m_sOldFwdslashPath.Left(prefix_length);
1751 ret += L'{';
1753 ret += m_sOldFwdslashPath.Mid(prefix_length, old_midlen);
1754 ret += L" => ";
1755 ret += m_sFwdslashPath.Mid(prefix_length, new_midlen);
1756 if (prefix_length + suffix_length)
1758 ret += L'}';
1759 ret += m_sFwdslashPath.Mid(m_sFwdslashPath.GetLength() - suffix_length, suffix_length);
1761 return ret;