Also fix FillBasedOnIndexFlags for matching single files
[TortoiseGit.git] / src / Git / TGitPath.cpp
blob4d0312944889a6135fdabb6957e4d8c8babbb4b9
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - TortoiseGit
4 // Copyright (C) 2010-2014 Sven Strickroth <email@cs-ware.de>
5 // Copyright (C) 2003-2008 - TortoiseSVN
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "stdafx.h"
22 #include "TGitPath.h"
23 #include "UnicodeUtils.h"
24 #include "GitAdminDir.h"
25 #include "PathUtils.h"
26 #include <regex>
27 #include "Git.h"
28 #include "Globals.h"
29 #include "StringUtils.h"
30 #include "../Resources/LoglistCommonResource.h"
31 #include <memory>
33 #ifndef ASSERT
34 #define ASSERT()
35 #endif
36 extern CGit g_Git;
38 CTGitPath::CTGitPath(void)
39 : m_bDirectoryKnown(false)
40 , m_bIsDirectory(false)
41 , m_bURLKnown(false)
42 , m_bHasAdminDirKnown(false)
43 , m_bHasAdminDir(false)
44 , m_bIsValidOnWindowsKnown(false)
45 , m_bIsValidOnWindows(false)
46 , m_bIsReadOnly(false)
47 , m_bIsAdminDirKnown(false)
48 , m_bIsAdminDir(false)
49 , m_bExists(false)
50 , m_bExistsKnown(false)
51 , m_bLastWriteTimeKnown(0)
52 , m_lastWriteTime(0)
53 , m_customData(NULL)
54 , m_bIsSpecialDirectoryKnown(false)
55 , m_bIsSpecialDirectory(false)
56 , m_bIsWCRootKnown(false)
57 , m_bIsWCRoot(false)
58 , m_fileSize(0)
59 , m_Checked(false)
61 m_Action=0;
62 m_ParentNo=0;
63 m_Stage = 0;
66 CTGitPath::~CTGitPath(void)
69 // Create a TGitPath object from an unknown path type (same as using SetFromUnknown)
70 CTGitPath::CTGitPath(const CString& sUnknownPath) :
71 m_bDirectoryKnown(false)
72 , m_bIsDirectory(false)
73 , m_bURLKnown(false)
74 , m_bHasAdminDirKnown(false)
75 , m_bHasAdminDir(false)
76 , m_bIsValidOnWindowsKnown(false)
77 , m_bIsValidOnWindows(false)
78 , m_bIsReadOnly(false)
79 , m_bIsAdminDirKnown(false)
80 , m_bIsAdminDir(false)
81 , m_bExists(false)
82 , m_bExistsKnown(false)
83 , m_bLastWriteTimeKnown(0)
84 , m_lastWriteTime(0)
85 , m_customData(NULL)
86 , m_bIsSpecialDirectoryKnown(false)
87 , m_bIsSpecialDirectory(false)
88 , m_bIsWCRootKnown(false)
89 , m_bIsWCRoot(false)
90 , m_fileSize(0)
91 , m_Checked(false)
93 SetFromUnknown(sUnknownPath);
94 m_Action=0;
95 m_Stage=0;
96 m_ParentNo=0;
99 int CTGitPath::ParserAction(BYTE action)
101 //action=action.TrimLeft();
102 //TCHAR c=action.GetAt(0);
103 if(action == 'M')
104 m_Action|= LOGACTIONS_MODIFIED;
105 if(action == 'R')
106 m_Action|= LOGACTIONS_REPLACED;
107 if(action == 'A')
108 m_Action|= LOGACTIONS_ADDED;
109 if(action == 'D')
110 m_Action|= LOGACTIONS_DELETED;
111 if(action == 'U')
112 m_Action|= LOGACTIONS_UNMERGED;
113 if(action == 'K')
114 m_Action|= LOGACTIONS_DELETED;
115 if(action == 'H')
116 m_Action|= LOGACTIONS_CACHE;
117 if(action == 'C' )
118 m_Action|= LOGACTIONS_COPY;
119 if(action == 'T')
120 m_Action|= LOGACTIONS_MODIFIED;
122 return m_Action;
124 void CTGitPath::SetFromGit(const char* pPath)
126 Reset();
127 if (pPath == NULL)
128 return;
129 int len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, NULL, 0);
130 if (len)
132 len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, m_sFwdslashPath.GetBuffer(len+1), len+1);
133 m_sFwdslashPath.ReleaseBuffer(len-1);
135 SanitizeRootPath(m_sFwdslashPath, true);
138 void CTGitPath::SetFromGit(const char* pPath, bool bIsDirectory)
140 SetFromGit(pPath);
141 m_bDirectoryKnown = true;
142 m_bIsDirectory = bIsDirectory;
145 void CTGitPath::SetFromGit(const TCHAR* pPath, bool bIsDirectory)
147 Reset();
148 if (pPath)
150 m_sFwdslashPath = pPath;
151 SanitizeRootPath(m_sFwdslashPath, true);
153 m_bDirectoryKnown = true;
154 m_bIsDirectory = bIsDirectory;
157 void CTGitPath::SetFromGit(const CString& sPath,CString *oldpath)
159 Reset();
160 m_sFwdslashPath = sPath;
161 SanitizeRootPath(m_sFwdslashPath, true);
162 if(oldpath)
163 m_sOldFwdslashPath = *oldpath;
166 void CTGitPath::SetFromWin(LPCTSTR pPath)
168 Reset();
169 m_sBackslashPath = pPath;
170 m_sBackslashPath.Replace(L"\\\\?\\", L"");
171 SanitizeRootPath(m_sBackslashPath, false);
172 ATLASSERT(m_sBackslashPath.Find('/')<0);
174 void CTGitPath::SetFromWin(const CString& sPath)
176 Reset();
177 m_sBackslashPath = sPath;
178 m_sBackslashPath.Replace(L"\\\\?\\", L"");
179 SanitizeRootPath(m_sBackslashPath, false);
181 void CTGitPath::SetFromWin(LPCTSTR pPath, bool bIsDirectory)
183 Reset();
184 m_sBackslashPath = pPath;
185 m_bIsDirectory = bIsDirectory;
186 m_bDirectoryKnown = true;
187 SanitizeRootPath(m_sBackslashPath, false);
189 void CTGitPath::SetFromWin(const CString& sPath, bool bIsDirectory)
191 Reset();
192 m_sBackslashPath = sPath;
193 m_bIsDirectory = bIsDirectory;
194 m_bDirectoryKnown = true;
195 SanitizeRootPath(m_sBackslashPath, false);
197 void CTGitPath::SetFromUnknown(const CString& sPath)
199 Reset();
200 // Just set whichever path we think is most likely to be used
201 // GitAdminDir admin;
202 // CString p;
203 // if(admin.HasAdminDir(sPath,&p))
204 // SetFwdslashPath(sPath.Right(sPath.GetLength()-p.GetLength()));
205 // else
206 SetFwdslashPath(sPath);
209 LPCTSTR CTGitPath::GetWinPath() const
211 if(IsEmpty())
213 return _T("");
215 if(m_sBackslashPath.IsEmpty())
217 SetBackslashPath(m_sFwdslashPath);
219 return m_sBackslashPath;
221 // This is a temporary function, to be used during the migration to
222 // the path class. Ultimately, functions consuming paths should take a CTGitPath&, not a CString
223 const CString& CTGitPath::GetWinPathString() const
225 if(m_sBackslashPath.IsEmpty())
227 SetBackslashPath(m_sFwdslashPath);
229 return m_sBackslashPath;
232 const CString& CTGitPath::GetGitPathString() const
234 if(m_sFwdslashPath.IsEmpty())
236 SetFwdslashPath(m_sBackslashPath);
238 return m_sFwdslashPath;
241 const CString &CTGitPath::GetGitOldPathString() const
243 return m_sOldFwdslashPath;
246 const CString& CTGitPath::GetUIPathString() const
248 if (m_sUIPath.IsEmpty())
250 m_sUIPath = GetWinPathString();
252 return m_sUIPath;
255 void CTGitPath::SetFwdslashPath(const CString& sPath) const
257 m_sFwdslashPath = sPath;
258 m_sFwdslashPath.Replace('\\', '/');
260 // We don't leave a trailing /
261 m_sFwdslashPath.TrimRight('/');
262 m_sFwdslashPath.Replace(L"//?/", L"");
264 SanitizeRootPath(m_sFwdslashPath, true);
266 m_sFwdslashPath.Replace(_T("file:////"), _T("file://"));
268 m_sUTF8FwdslashPath.Empty();
271 void CTGitPath::SetBackslashPath(const CString& sPath) const
273 m_sBackslashPath = sPath;
274 m_sBackslashPath.Replace('/', '\\');
275 m_sBackslashPath.TrimRight('\\');
276 SanitizeRootPath(m_sBackslashPath, false);
279 void CTGitPath::SetUTF8FwdslashPath(const CString& sPath) const
281 m_sUTF8FwdslashPath = CUnicodeUtils::GetUTF8(sPath);
284 void CTGitPath::SanitizeRootPath(CString& sPath, bool bIsForwardPath) const
286 // Make sure to add the trailing slash to root paths such as 'C:'
287 if (sPath.GetLength() == 2 && sPath[1] == ':')
289 sPath += (bIsForwardPath) ? _T("/") : _T("\\");
293 bool CTGitPath::IsDirectory() const
295 if(!m_bDirectoryKnown)
297 UpdateAttributes();
299 return m_bIsDirectory;
302 bool CTGitPath::Exists() const
304 if (!m_bExistsKnown)
306 UpdateAttributes();
308 return m_bExists;
311 bool CTGitPath::Delete(bool bTrash) const
313 EnsureBackslashPathSet();
314 ::SetFileAttributes(m_sBackslashPath, FILE_ATTRIBUTE_NORMAL);
315 bool bRet = false;
316 if (Exists())
318 if ((bTrash)||(IsDirectory()))
320 std::unique_ptr<TCHAR[]> buf(new TCHAR[m_sBackslashPath.GetLength() + 2]);
321 _tcscpy_s(buf.get(), m_sBackslashPath.GetLength() + 2, m_sBackslashPath);
322 buf[m_sBackslashPath.GetLength()] = 0;
323 buf[m_sBackslashPath.GetLength()+1] = 0;
324 bRet = CTGitPathList::DeleteViaShell(buf.get(), bTrash);
326 else
328 bRet = !!::DeleteFile(m_sBackslashPath);
331 m_bExists = false;
332 m_bExistsKnown = true;
333 return bRet;
336 __int64 CTGitPath::GetLastWriteTime() const
338 if(!m_bLastWriteTimeKnown)
340 UpdateAttributes();
342 return m_lastWriteTime;
345 __int64 CTGitPath::GetFileSize() const
347 if(!m_bDirectoryKnown)
349 UpdateAttributes();
351 return m_fileSize;
354 bool CTGitPath::IsReadOnly() const
356 if(!m_bLastWriteTimeKnown)
358 UpdateAttributes();
360 return m_bIsReadOnly;
363 void CTGitPath::UpdateAttributes() const
365 EnsureBackslashPathSet();
366 WIN32_FILE_ATTRIBUTE_DATA attribs;
367 if (m_sBackslashPath.GetLength() >= 248)
368 m_sLongBackslashPath = _T("\\\\?\\") + m_sBackslashPath;
369 if(GetFileAttributesEx(m_sBackslashPath.GetLength() >= 248 ? m_sLongBackslashPath : m_sBackslashPath, GetFileExInfoStandard, &attribs))
371 m_bIsDirectory = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
372 m_lastWriteTime = *(__int64*)&attribs.ftLastWriteTime;
373 if (m_bIsDirectory)
375 m_fileSize = 0;
377 else
379 m_fileSize = ((INT64)( (DWORD)(attribs.nFileSizeLow) ) | ( (INT64)( (DWORD)(attribs.nFileSizeHigh) )<<32 ));
381 m_bIsReadOnly = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
382 m_bExists = true;
384 else
386 m_bIsDirectory = false;
387 m_lastWriteTime = 0;
388 m_fileSize = 0;
389 DWORD err = GetLastError();
390 if ((err == ERROR_FILE_NOT_FOUND)||(err == ERROR_PATH_NOT_FOUND)||(err == ERROR_INVALID_NAME))
392 m_bExists = false;
394 else
396 m_bExists = true;
397 return;
400 m_bDirectoryKnown = true;
401 m_bLastWriteTimeKnown = true;
402 m_bExistsKnown = true;
405 CTGitPath CTGitPath::GetSubPath(const CTGitPath &root)
407 CTGitPath path;
409 if(GetWinPathString().Left(root.GetWinPathString().GetLength()) == root.GetWinPathString())
411 CString str=GetWinPathString();
412 path.SetFromWin(str.Right(str.GetLength()-root.GetWinPathString().GetLength()-1));
414 return path;
417 void CTGitPath::EnsureBackslashPathSet() const
419 if(m_sBackslashPath.IsEmpty())
421 SetBackslashPath(m_sFwdslashPath);
422 ATLASSERT(IsEmpty() || !m_sBackslashPath.IsEmpty());
425 void CTGitPath::EnsureFwdslashPathSet() const
427 if(m_sFwdslashPath.IsEmpty())
429 SetFwdslashPath(m_sBackslashPath);
430 ATLASSERT(IsEmpty() || !m_sFwdslashPath.IsEmpty());
435 // Reset all the caches
436 void CTGitPath::Reset()
438 m_bDirectoryKnown = false;
439 m_bURLKnown = false;
440 m_bLastWriteTimeKnown = false;
441 m_bHasAdminDirKnown = false;
442 m_bIsValidOnWindowsKnown = false;
443 m_bIsAdminDirKnown = false;
444 m_bExistsKnown = false;
445 m_bIsSpecialDirectoryKnown = false;
446 m_bIsSpecialDirectory = false;
448 m_sBackslashPath.Empty();
449 m_sFwdslashPath.Empty();
450 m_sUTF8FwdslashPath.Empty();
451 this->m_Action=0;
452 this->m_StatAdd=_T("");
453 this->m_StatDel=_T("");
454 m_ParentNo=0;
455 ATLASSERT(IsEmpty());
458 CTGitPath CTGitPath::GetDirectory() const
460 if ((IsDirectory())||(!Exists()))
462 return *this;
464 return GetContainingDirectory();
467 CTGitPath CTGitPath::GetContainingDirectory() const
469 EnsureBackslashPathSet();
471 CString sDirName = m_sBackslashPath.Left(m_sBackslashPath.ReverseFind('\\'));
472 if(sDirName.GetLength() == 2 && sDirName[1] == ':')
474 // This is a root directory, which needs a trailing slash
475 sDirName += '\\';
476 if(sDirName == m_sBackslashPath)
478 // We were clearly provided with a root path to start with - we should return nothing now
479 sDirName.Empty();
482 if(sDirName.GetLength() == 1 && sDirName[0] == '\\')
484 // We have an UNC path and we already are the root
485 sDirName.Empty();
487 CTGitPath retVal;
488 retVal.SetFromWin(sDirName);
489 return retVal;
492 CString CTGitPath::GetRootPathString() const
494 EnsureBackslashPathSet();
495 CString workingPath = m_sBackslashPath;
496 LPTSTR pPath = workingPath.GetBuffer(MAX_PATH); // MAX_PATH ok here.
497 ATLVERIFY(::PathStripToRoot(pPath));
498 workingPath.ReleaseBuffer();
499 return workingPath;
503 CString CTGitPath::GetFilename() const
505 //ATLASSERT(!IsDirectory());
506 return GetFileOrDirectoryName();
509 CString CTGitPath::GetFileOrDirectoryName() const
511 EnsureBackslashPathSet();
512 return m_sBackslashPath.Mid(m_sBackslashPath.ReverseFind('\\')+1);
515 CString CTGitPath::GetUIFileOrDirectoryName() const
517 GetUIPathString();
518 return m_sUIPath.Mid(m_sUIPath.ReverseFind('\\')+1);
521 CString CTGitPath::GetFileExtension() const
523 if(!IsDirectory())
525 EnsureBackslashPathSet();
526 int dotPos = m_sBackslashPath.ReverseFind('.');
527 int slashPos = m_sBackslashPath.ReverseFind('\\');
528 if (dotPos > slashPos)
529 return m_sBackslashPath.Mid(dotPos);
531 return CString();
533 CString CTGitPath::GetBaseFilename() const
535 int dot;
536 CString filename=GetFilename();
537 dot = filename.ReverseFind(_T('.'));
538 if(dot>0)
539 return filename.Left(dot);
540 else
541 return filename;
544 bool CTGitPath::ArePathStringsEqual(const CString& sP1, const CString& sP2)
546 int length = sP1.GetLength();
547 if(length != sP2.GetLength())
549 // Different lengths
550 return false;
552 // We work from the end of the strings, because path differences
553 // are more likely to occur at the far end of a string
554 LPCTSTR pP1Start = sP1;
555 LPCTSTR pP1 = pP1Start+(length-1);
556 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
557 while(length-- > 0)
559 if(_totlower(*pP1--) != _totlower(*pP2--))
561 return false;
564 return true;
567 bool CTGitPath::ArePathStringsEqualWithCase(const CString& sP1, const CString& sP2)
569 int length = sP1.GetLength();
570 if(length != sP2.GetLength())
572 // Different lengths
573 return false;
575 // We work from the end of the strings, because path differences
576 // are more likely to occur at the far end of a string
577 LPCTSTR pP1Start = sP1;
578 LPCTSTR pP1 = pP1Start+(length-1);
579 LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1);
580 while(length-- > 0)
582 if((*pP1--) != (*pP2--))
584 return false;
587 return true;
590 bool CTGitPath::IsEmpty() const
592 // Check the backward slash path first, since the chance that this
593 // one is set is higher. In case of a 'false' return value it's a little
594 // bit faster.
595 return m_sBackslashPath.IsEmpty() && m_sFwdslashPath.IsEmpty();
598 // Test if both paths refer to the same item
599 // Ignores case and slash direction
600 bool CTGitPath::IsEquivalentTo(const CTGitPath& rhs) const
602 // Try and find a slash direction which avoids having to convert
603 // both filenames
604 if(!m_sBackslashPath.IsEmpty())
606 // *We've* got a \ path - make sure that the RHS also has a \ path
607 rhs.EnsureBackslashPathSet();
608 return ArePathStringsEqualWithCase(m_sBackslashPath, rhs.m_sBackslashPath);
610 else
612 // Assume we've got a fwdslash path and make sure that the RHS has one
613 rhs.EnsureFwdslashPathSet();
614 return ArePathStringsEqualWithCase(m_sFwdslashPath, rhs.m_sFwdslashPath);
618 bool CTGitPath::IsEquivalentToWithoutCase(const CTGitPath& rhs) const
620 // Try and find a slash direction which avoids having to convert
621 // both filenames
622 if(!m_sBackslashPath.IsEmpty())
624 // *We've* got a \ path - make sure that the RHS also has a \ path
625 rhs.EnsureBackslashPathSet();
626 return ArePathStringsEqual(m_sBackslashPath, rhs.m_sBackslashPath);
628 else
630 // Assume we've got a fwdslash path and make sure that the RHS has one
631 rhs.EnsureFwdslashPathSet();
632 return ArePathStringsEqual(m_sFwdslashPath, rhs.m_sFwdslashPath);
636 bool CTGitPath::IsAncestorOf(const CTGitPath& possibleDescendant) const
638 possibleDescendant.EnsureBackslashPathSet();
639 EnsureBackslashPathSet();
641 bool bPathStringsEqual = ArePathStringsEqual(m_sBackslashPath, possibleDescendant.m_sBackslashPath.Left(m_sBackslashPath.GetLength()));
642 if (m_sBackslashPath.GetLength() >= possibleDescendant.GetWinPathString().GetLength())
644 return bPathStringsEqual;
647 return (bPathStringsEqual &&
648 ((possibleDescendant.m_sBackslashPath[m_sBackslashPath.GetLength()] == '\\')||
649 (m_sBackslashPath.GetLength()==3 && m_sBackslashPath[1]==':')));
652 // Get a string representing the file path, optionally with a base
653 // section stripped off the front.
654 CString CTGitPath::GetDisplayString(const CTGitPath* pOptionalBasePath /* = NULL*/) const
656 EnsureFwdslashPathSet();
657 if(pOptionalBasePath != NULL)
659 // Find the length of the base-path without having to do an 'ensure' on it
660 int baseLength = max(pOptionalBasePath->m_sBackslashPath.GetLength(), pOptionalBasePath->m_sFwdslashPath.GetLength());
662 // Now, chop that baseLength of the front of the path
663 return m_sFwdslashPath.Mid(baseLength).TrimLeft('/');
665 return m_sFwdslashPath;
668 int CTGitPath::Compare(const CTGitPath& left, const CTGitPath& right)
670 left.EnsureBackslashPathSet();
671 right.EnsureBackslashPathSet();
672 return left.m_sBackslashPath.CompareNoCase(right.m_sBackslashPath);
675 bool operator<(const CTGitPath& left, const CTGitPath& right)
677 return CTGitPath::Compare(left, right) < 0;
680 bool CTGitPath::PredLeftEquivalentToRight(const CTGitPath& left, const CTGitPath& right)
682 return left.IsEquivalentTo(right);
685 bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right)
687 if (left.IsAdminDir() && right.IsAdminDir())
689 CTGitPath l = left;
690 CTGitPath r = right;
693 l = l.GetContainingDirectory();
694 } while(l.HasAdminDir());
697 r = r.GetContainingDirectory();
698 } while(r.HasAdminDir());
699 return l.GetContainingDirectory().IsEquivalentTo(r.GetContainingDirectory());
701 return left.GetDirectory().IsEquivalentTo(right.GetDirectory());
704 bool CTGitPath::CheckChild(const CTGitPath &parent, const CTGitPath& child)
706 return parent.IsAncestorOf(child);
709 void CTGitPath::AppendRawString(const CString& sAppend)
711 EnsureFwdslashPathSet();
712 CString strCopy = m_sFwdslashPath += sAppend;
713 SetFromUnknown(strCopy);
716 void CTGitPath::AppendPathString(const CString& sAppend)
718 EnsureBackslashPathSet();
719 CString cleanAppend(sAppend);
720 cleanAppend.Replace('/', '\\');
721 cleanAppend.TrimLeft('\\');
722 m_sBackslashPath.TrimRight('\\');
723 CString strCopy = m_sBackslashPath + _T("\\") + cleanAppend;
724 SetFromWin(strCopy);
727 bool CTGitPath::IsWCRoot() const
729 if (m_bIsWCRootKnown)
730 return m_bIsWCRoot;
732 m_bIsWCRootKnown = true;
733 m_bIsWCRoot = false;
735 CString topDirectory;
736 if (!IsDirectory() || !HasAdminDir(&topDirectory))
738 return m_bIsWCRoot;
741 if (IsEquivalentToWithoutCase(topDirectory))
743 m_bIsWCRoot = true;
746 return m_bIsWCRoot;
749 bool CTGitPath::HasAdminDir() const
751 if (m_bHasAdminDirKnown)
752 return m_bHasAdminDir;
754 EnsureBackslashPathSet();
755 m_bHasAdminDir = g_GitAdminDir.HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
756 m_bHasAdminDirKnown = true;
757 return m_bHasAdminDir;
760 bool CTGitPath::HasSubmodules() const
762 if (HasAdminDir())
764 CString path = m_sProjectRoot;
765 path += _T("\\.gitmodules");
766 if( PathFileExists(path) )
767 return true;
769 return false;
772 int CTGitPath::GetAdminDirMask() const
774 int status = 0;
775 CString topdir;
776 if(!g_GitAdminDir.HasAdminDir(GetWinPathString(),&topdir))
778 return status;
781 // ITEMIS_INGIT will be revoked if necessary in TortoiseShell/ContextMenu.cpp
782 status |= ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER;
784 if (IsDirectory())
786 status |= ITEMIS_FOLDERINGIT;
787 if (IsWCRoot())
789 status |= ITEMIS_WCROOT;
791 CString topProjectDir;
792 if (g_GitAdminDir.HasAdminDir(GetWinPathString(), false, &topProjectDir))
794 if (PathFileExists(topProjectDir + _T("\\.gitmodules")))
796 CAutoConfig config(true);
797 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(topProjectDir + _T("\\.gitmodules")), GIT_CONFIG_LEVEL_APP, FALSE);
798 CString relativePath = GetWinPathString().Mid(topProjectDir.GetLength());
799 relativePath.Replace(_T("\\"), _T("/"));
800 relativePath.Trim(_T("/"));
801 CStringA submodulePath = CUnicodeUtils::GetUTF8(relativePath);
802 if (git_config_foreach_match(config, "submodule\\..*\\.path",
803 [](const git_config_entry *entry, void *data) { return entry->value == *(CStringA *)data ? GIT_EUSER : 0; }, &submodulePath) == GIT_EUSER)
804 status |= ITEMIS_SUBMODULE;
810 CString dotGitPath;
811 g_GitAdminDir.GetAdminDirPath(topdir, dotGitPath);
813 if (PathFileExists(dotGitPath + _T("BISECT_START")))
814 status |= ITEMIS_BISECT;
816 if (PathFileExists(dotGitPath + _T("MERGE_HEAD")))
817 status |= ITEMIS_MERGEACTIVE;
819 if (PathFileExists(dotGitPath + _T("refs\\stash")))
820 status |= ITEMIS_STASH;
822 if (PathFileExists(dotGitPath + _T("svn")))
823 status |= ITEMIS_GITSVN;
825 if (PathFileExists(topdir + _T("\\.gitmodules")))
826 status |= ITEMIS_SUBMODULECONTAINER;
828 return status;
831 bool CTGitPath::HasStashDir() const
833 CString topdir;
834 if(!g_GitAdminDir.HasAdminDir(GetWinPathString(),&topdir))
836 return false;
839 CString dotGitPath;
840 g_GitAdminDir.GetAdminDirPath(topdir, dotGitPath);
842 return !!PathFileExists(dotGitPath + _T("\\refs\\stash"));
844 bool CTGitPath::HasGitSVNDir() const
846 CString topdir;
847 if(!g_GitAdminDir.HasAdminDir(GetWinPathString(),&topdir))
849 return false;
852 CString dotGitPath;
853 g_GitAdminDir.GetAdminDirPath(topdir, dotGitPath);
855 return !!PathFileExists(dotGitPath + _T("svn"));
857 bool CTGitPath::IsBisectActive() const
859 CString topdir;
860 if(!g_GitAdminDir.HasAdminDir(GetWinPathString(),&topdir))
862 return false;
865 CString dotGitPath;
866 g_GitAdminDir.GetAdminDirPath(topdir, dotGitPath);
868 return !!PathFileExists(dotGitPath + _T("BISECT_START"));
870 bool CTGitPath::IsMergeActive() const
872 CString topdir;
873 if(!g_GitAdminDir.HasAdminDir(GetWinPathString(),&topdir))
875 return false;
878 CString dotGitPath;
879 g_GitAdminDir.GetAdminDirPath(topdir, dotGitPath);
881 return !!PathFileExists(dotGitPath + _T("MERGE_HEAD"));
883 bool CTGitPath::HasRebaseApply() const
885 CString topdir;
886 if(!g_GitAdminDir.HasAdminDir(GetWinPathString(),&topdir))
888 return false;
891 CString dotGitPath;
892 g_GitAdminDir.GetAdminDirPath(topdir, dotGitPath);
894 return !!PathFileExists(dotGitPath + _T("rebase-apply"));
897 bool CTGitPath::HasAdminDir(CString *ProjectTopDir) const
899 if (m_bHasAdminDirKnown)
901 if (ProjectTopDir)
902 *ProjectTopDir = m_sProjectRoot;
903 return m_bHasAdminDir;
906 EnsureBackslashPathSet();
907 m_bHasAdminDir = g_GitAdminDir.HasAdminDir(m_sBackslashPath, IsDirectory(), &m_sProjectRoot);
908 m_bHasAdminDirKnown = true;
909 if (ProjectTopDir)
910 *ProjectTopDir = m_sProjectRoot;
911 return m_bHasAdminDir;
914 bool CTGitPath::IsAdminDir() const
916 if (m_bIsAdminDirKnown)
917 return m_bIsAdminDir;
919 EnsureBackslashPathSet();
920 m_bIsAdminDir = g_GitAdminDir.IsAdminDirPath(m_sBackslashPath);
921 m_bIsAdminDirKnown = true;
922 return m_bIsAdminDir;
925 bool CTGitPath::IsValidOnWindows() const
927 if (m_bIsValidOnWindowsKnown)
928 return m_bIsValidOnWindows;
930 m_bIsValidOnWindows = false;
931 EnsureBackslashPathSet();
932 CString sMatch = m_sBackslashPath + _T("\r\n");
933 std::wstring sPattern;
934 // the 'file://' URL is just a normal windows path:
935 if (sMatch.Left(7).CompareNoCase(_T("file:\\\\"))==0)
937 sMatch = sMatch.Mid(7);
938 sMatch.TrimLeft(_T("\\"));
939 sPattern = _T("^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$");
941 else
943 sPattern = _T("^(\\\\\\\\\\?\\\\)?(([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(_T("\\\\(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 unsigned int pos=0;
988 CString one;
989 CTGitPath path;
990 CString part;
991 this->Clear();
993 while(pos>=0 && pos<out.size())
995 one.Empty();
996 path.Reset();
998 g_Git.StringAppend(&one, &out[pos], CP_UTF8);
999 int tabstart=0;
1000 path.m_Action=path.ParserAction(out[pos]);
1001 one.Tokenize(_T("\t"),tabstart);
1003 if(tabstart>=0)
1004 path.SetFromGit(one.Right(one.GetLength()-tabstart));
1005 else
1006 return -1;
1008 tabstart=0;
1010 part=one.Tokenize(_T(" "),tabstart); //Tag
1011 if (tabstart < 0)
1012 return -1;
1014 part=one.Tokenize(_T(" "),tabstart); //Mode
1015 if (tabstart < 0)
1016 return -1;
1018 part=one.Tokenize(_T(" "),tabstart); //Hash
1019 if (tabstart < 0)
1020 return -1;
1022 part=one.Tokenize(_T("\t"),tabstart); //Stage
1023 if (tabstart < 0)
1024 return -1;
1026 path.m_Stage=_ttol(part);
1028 this->AddPath(path);
1030 pos=out.findNextString(pos);
1032 return 0;
1034 int CTGitPathList::FillUnRev(unsigned int action, CTGitPathList *list, CString *err)
1036 this->Clear();
1037 CTGitPath path;
1039 int count;
1040 if(list==NULL)
1041 count=1;
1042 else
1043 count=list->GetCount();
1044 for (int i = 0; i < count; ++i)
1046 CString cmd;
1047 int pos = 0;
1049 CString ignored;
1050 if(action & CTGitPath::LOGACTIONS_IGNORE)
1051 ignored= _T(" -i");
1053 if(list==NULL)
1055 cmd=_T("git.exe ls-files --exclude-standard --full-name --others -z");
1056 cmd+=ignored;
1059 else
1060 { cmd.Format(_T("git.exe ls-files --exclude-standard --full-name --others -z%s -- \"%s\""),
1061 ignored,
1062 (*list)[i].GetWinPathString());
1065 BYTE_VECTOR out, errb;
1066 out.clear();
1067 if (g_Git.Run(cmd, &out, &errb))
1069 if (err != nullptr)
1070 g_Git.StringAppend(err, &errb[0], CP_UTF8, (int)errb.size());
1071 return -1;
1074 pos=0;
1075 CString one;
1076 while (pos >= 0 && pos < (int)out.size())
1078 one.Empty();
1079 g_Git.StringAppend(&one, &out[pos], CP_UTF8);
1080 if(!one.IsEmpty())
1082 //SetFromGit will clear all status
1083 path.SetFromGit(one);
1084 path.m_Action=action;
1085 AddPath(path);
1087 pos=out.findNextString(pos);
1091 return 0;
1093 int CTGitPathList::FillBasedOnIndexFlags(unsigned short flag, CTGitPathList* list /*nullptr*/)
1095 Clear();
1096 CTGitPath path;
1097 CString one;
1099 CAutoRepository repository(g_Git.GetGitRepository());
1100 if (!repository)
1101 return -1;
1103 CAutoIndex index;
1104 if (git_repository_index(index.GetPointer(), repository))
1105 return -1;
1107 int count;
1108 if (list == nullptr)
1109 count = 1;
1110 else
1111 count = list->GetCount();
1112 for (int j = 0; j < count; ++j)
1114 for (size_t i = 0, ecount = git_index_entrycount(index); i < ecount; ++i)
1116 const git_index_entry *e = git_index_get_byindex(index, i);
1118 if (!e || !((e->flags | e->flags_extended) & flag) || !e->path)
1119 continue;
1121 one.Empty();
1122 g_Git.StringAppend(&one, (BYTE*)e->path, CP_UTF8);
1124 if (!(!list || (*list)[j].GetWinPathString().IsEmpty() || one == (*list)[j].GetGitPathString() || (PathIsDirectory(g_Git.m_CurrentDir + L"\\" + (*list)[j].GetWinPathString()) && one.Find((*list)[j].GetGitPathString() + _T("/")) == 0)))
1125 continue;
1127 //SetFromGit will clear all status
1128 path.SetFromGit(one);
1129 if ((e->flags | e->flags_extended) & GIT_IDXENTRY_SKIP_WORKTREE)
1130 path.m_Action = CTGitPath::LOGACTIONS_SKIPWORKTREE;
1131 else if ((e->flags | e->flags_extended) & GIT_IDXENTRY_VALID)
1132 path.m_Action = CTGitPath::LOGACTIONS_ASSUMEVALID;
1133 AddPath(path);
1136 RemoveDuplicates();
1137 return 0;
1139 int CTGitPathList::ParserFromLog(BYTE_VECTOR &log, bool parseDeletes /*false*/)
1141 this->Clear();
1142 int pos=0;
1143 //BYTE *p=&log[0];
1144 //CString one;
1145 CTGitPath path;
1146 m_Action=0;
1147 while (pos >= 0 && pos < (int)log.size())
1149 //one=log.Tokenize(_T("\n"),pos);
1150 path.Reset();
1151 if(log[pos]=='\n')
1152 ++pos;
1154 if(log[pos]==':')
1156 bool merged=false;
1157 if(log[pos+1] ==':')
1159 merged=true;
1161 int end=log.find(0,pos);
1162 int actionstart=-1;
1163 int file1=-1,file2=-1;
1164 if( end>0 )
1166 actionstart=log.find(' ',end-6);
1167 pos=actionstart;
1169 if( actionstart>0 )
1171 ++actionstart;
1173 file1 = log.find(0,actionstart);
1174 if( file1>=0 )
1176 ++file1;
1177 pos=file1;
1179 if( log[actionstart] == 'C' || log[actionstart] == 'R' )
1181 file2=file1;
1182 file1 = log.find(0,file1);
1183 if(file1>=0 )
1185 ++file1;
1186 pos=file1;
1192 CString pathname1;
1193 CString pathname2;
1195 if( file1>=0 )
1196 g_Git.StringAppend(&pathname1, &log[file1], CP_UTF8);
1197 if( file2>=0 )
1198 g_Git.StringAppend(&pathname2, &log[file2], CP_UTF8);
1200 CTGitPath *GitPath=LookForGitPath(pathname1);
1202 if(GitPath)
1204 GitPath->ParserAction( log[actionstart] );
1206 if(merged)
1208 GitPath->m_Action |= CTGitPath::LOGACTIONS_MERGED;
1209 GitPath->m_Action &= ~CTGitPath::LOGACTIONS_FORWORD;
1211 m_Action |=GitPath->m_Action;
1214 else
1216 int ac=path.ParserAction(log[actionstart] );
1217 ac |= merged?CTGitPath::LOGACTIONS_MERGED:0;
1219 path.SetFromGit(pathname1,&pathname2);
1220 path.m_Action=ac;
1221 //action must be set after setfromgit. SetFromGit will clear all status.
1222 this->m_Action|=ac;
1224 AddPath(path);
1229 else
1231 int tabstart=0;
1232 path.Reset();
1233 CString StatAdd;
1234 CString StatDel;
1235 CString file1;
1236 CString file2;
1238 tabstart=log.find('\t',pos);
1239 if(tabstart >=0)
1241 log[tabstart]=0;
1242 g_Git.StringAppend(&StatAdd,&log[pos],CP_UTF8);
1243 pos=tabstart+1;
1246 tabstart=log.find('\t',pos);
1247 if(tabstart >=0)
1249 log[tabstart]=0;
1251 g_Git.StringAppend(&StatDel,&log[pos],CP_UTF8);
1252 pos=tabstart+1;
1255 if(log[pos] == 0) //rename
1257 ++pos;
1258 g_Git.StringAppend(&file2, &log[pos], CP_UTF8);
1259 int sec=log.find(0,pos);
1260 if(sec>=0)
1262 ++sec;
1263 g_Git.StringAppend(&file1, &log[sec], CP_UTF8);
1265 pos=sec;
1268 else
1270 g_Git.StringAppend(&file1, &log[pos], CP_UTF8);
1272 path.SetFromGit(file1,&file2);
1274 CTGitPath *GitPath=LookForGitPath(path.GetGitPathString());
1275 if(GitPath)
1277 GitPath->m_StatAdd=StatAdd;
1278 GitPath->m_StatDel=StatDel;
1280 else
1282 //path.SetFromGit(pathname);
1283 if (parseDeletes)
1285 path.m_StatAdd=_T("0");
1286 path.m_StatDel=_T("0");
1287 path.m_Action |= CTGitPath::LOGACTIONS_DELETED;
1289 else
1291 path.m_StatAdd=StatAdd;
1292 path.m_StatDel=StatDel;
1293 path.m_Action |= CTGitPath::LOGACTIONS_FORWORD;
1295 AddPath(path);
1299 pos=log.findNextString(pos);
1301 return pos;
1304 void CTGitPathList::AddPath(const CTGitPath& newPath)
1306 m_paths.push_back(newPath);
1307 m_commonBaseDirectory.Reset();
1309 int CTGitPathList::GetCount() const
1311 return (int)m_paths.size();
1313 bool CTGitPathList::IsEmpty() const
1315 return m_paths.empty();
1317 void CTGitPathList::Clear()
1319 m_paths.clear();
1320 m_commonBaseDirectory.Reset();
1323 const CTGitPath& CTGitPathList::operator[](INT_PTR index) const
1325 ATLASSERT(index >= 0 && index < (INT_PTR)m_paths.size());
1326 return m_paths[index];
1329 bool CTGitPathList::AreAllPathsFiles() const
1331 // Look through the vector for any directories - if we find them, return false
1332 return std::find_if(m_paths.begin(), m_paths.end(), std::mem_fun_ref(&CTGitPath::IsDirectory)) == m_paths.end();
1336 #if defined(_MFC_VER)
1338 bool CTGitPathList::LoadFromFile(const CTGitPath& filename)
1340 Clear();
1343 CString strLine;
1344 CStdioFile file(filename.GetWinPath(), CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite);
1346 // for every selected file/folder
1347 CTGitPath path;
1348 while (file.ReadString(strLine))
1350 path.SetFromUnknown(strLine);
1351 AddPath(path);
1353 file.Close();
1355 catch (CFileException* pE)
1357 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException loading target file list\n");
1358 TCHAR error[10000] = {0};
1359 pE->GetErrorMessage(error, 10000);
1360 // CMessageBox::Show(NULL, error, _T("TortoiseGit"), MB_ICONERROR);
1361 pE->Delete();
1362 return false;
1364 return true;
1367 bool CTGitPathList::WriteToFile(const CString& sFilename, bool bANSI /* = false */) const
1371 if (bANSI)
1373 CStdioFile file(sFilename, CFile::typeText | CFile::modeReadWrite | CFile::modeCreate);
1374 PathVector::const_iterator it;
1375 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1377 CStringA line = CStringA(it->GetGitPathString()) + '\n';
1378 file.Write(line, line.GetLength());
1380 file.Close();
1382 else
1384 CStdioFile file(sFilename, CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
1385 PathVector::const_iterator it;
1386 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1388 file.WriteString(it->GetGitPathString()+_T("\n"));
1390 file.Close();
1393 catch (CFileException* pE)
1395 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": CFileException in writing temp file\n");
1396 pE->Delete();
1397 return false;
1399 return true;
1403 void CTGitPathList::LoadFromAsteriskSeparatedString(const CString& sPathString)
1405 int pos = 0;
1406 CString temp;
1407 for(;;)
1409 temp = sPathString.Tokenize(_T("*"),pos);
1410 if(temp.IsEmpty())
1412 break;
1414 AddPath(CTGitPath(CPathUtils::GetLongPathname(temp)));
1418 CString CTGitPathList::CreateAsteriskSeparatedString() const
1420 CString sRet;
1421 PathVector::const_iterator it;
1422 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1424 if (!sRet.IsEmpty())
1425 sRet += _T("*");
1426 sRet += it->GetWinPathString();
1428 return sRet;
1430 #endif // _MFC_VER
1432 bool
1433 CTGitPathList::AreAllPathsFilesInOneDirectory() const
1435 // Check if all the paths are files and in the same directory
1436 PathVector::const_iterator it;
1437 m_commonBaseDirectory.Reset();
1438 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1440 if(it->IsDirectory())
1442 return false;
1444 const CTGitPath& baseDirectory = it->GetDirectory();
1445 if(m_commonBaseDirectory.IsEmpty())
1447 m_commonBaseDirectory = baseDirectory;
1449 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1451 // Different path
1452 m_commonBaseDirectory.Reset();
1453 return false;
1456 return true;
1459 CTGitPath CTGitPathList::GetCommonDirectory() const
1461 if (m_commonBaseDirectory.IsEmpty())
1463 PathVector::const_iterator it;
1464 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1466 const CTGitPath& baseDirectory = it->GetDirectory();
1467 if(m_commonBaseDirectory.IsEmpty())
1469 m_commonBaseDirectory = baseDirectory;
1471 else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory))
1473 // Different path
1474 m_commonBaseDirectory.Reset();
1475 break;
1479 // since we only checked strings, not paths,
1480 // we have to make sure now that we really return a *path* here
1481 PathVector::const_iterator iter;
1482 for(iter = m_paths.begin(); iter != m_paths.end(); ++iter)
1484 if (!m_commonBaseDirectory.IsAncestorOf(*iter))
1486 m_commonBaseDirectory = m_commonBaseDirectory.GetContainingDirectory();
1487 break;
1490 return m_commonBaseDirectory;
1493 CTGitPath CTGitPathList::GetCommonRoot() const
1495 if (IsEmpty())
1496 return CTGitPath();
1498 if (GetCount() == 1)
1499 return m_paths[0];
1501 // first entry is common root for itself
1502 // (add trailing '\\' to detect partial matches of the last path element)
1503 CString root = m_paths[0].GetWinPathString() + _T('\\');
1504 int rootLength = root.GetLength();
1506 // determine common path string prefix
1507 for (PathVector::const_iterator it = m_paths.begin() + 1; it != m_paths.end(); ++it)
1509 CString path = it->GetWinPathString() + _T('\\');
1511 int newLength = CStringUtils::GetMatchingLength(root, path);
1512 if (newLength != rootLength)
1514 root.Delete(newLength, rootLength);
1515 rootLength = newLength;
1519 // remove the last (partial) path element
1520 if (rootLength > 0)
1521 root.Delete(root.ReverseFind(_T('\\')), rootLength);
1523 // done
1524 return CTGitPath(root);
1527 void CTGitPathList::SortByPathname(bool bReverse /*= false*/)
1529 std::sort(m_paths.begin(), m_paths.end());
1530 if (bReverse)
1531 std::reverse(m_paths.begin(), m_paths.end());
1534 void CTGitPathList::DeleteAllFiles(bool bTrash, bool bFilesOnly)
1536 PathVector::const_iterator it;
1537 SortByPathname(true); // nested ones first
1539 CString sPaths;
1540 for (it = m_paths.begin(); it != m_paths.end(); ++it)
1542 if ((it->Exists()) && ((it->IsDirectory() != bFilesOnly) || !bFilesOnly))
1544 if (!it->IsDirectory())
1545 ::SetFileAttributes(it->GetWinPath(), FILE_ATTRIBUTE_NORMAL);
1547 sPaths += it->GetWinPath();
1548 sPaths += '\0';
1551 sPaths += '\0';
1552 sPaths += '\0';
1553 DeleteViaShell((LPCTSTR)sPaths, bTrash);
1554 Clear();
1557 bool CTGitPathList::DeleteViaShell(LPCTSTR path, bool bTrash)
1559 SHFILEOPSTRUCT shop = {0};
1560 shop.wFunc = FO_DELETE;
1561 shop.pFrom = path;
1562 shop.fFlags = FOF_NOCONFIRMATION|FOF_NOERRORUI|FOF_SILENT|FOF_NO_CONNECTED_ELEMENTS;
1563 if (bTrash)
1564 shop.fFlags |= FOF_ALLOWUNDO;
1565 const bool bRet = (SHFileOperation(&shop) == 0);
1566 return bRet;
1569 void CTGitPathList::RemoveDuplicates()
1571 SortByPathname();
1572 // Remove the duplicates
1573 // (Unique moves them to the end of the vector, then erase chops them off)
1574 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::PredLeftEquivalentToRight), m_paths.end());
1577 void CTGitPathList::RemoveAdminPaths()
1579 PathVector::iterator it;
1580 for(it = m_paths.begin(); it != m_paths.end(); )
1582 if (it->IsAdminDir())
1584 m_paths.erase(it);
1585 it = m_paths.begin();
1587 else
1588 ++it;
1592 void CTGitPathList::RemovePath(const CTGitPath& path)
1594 PathVector::iterator it;
1595 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1597 if (it->IsEquivalentTo(path))
1599 m_paths.erase(it);
1600 return;
1605 void CTGitPathList::RemoveItem(CTGitPath & path)
1607 PathVector::iterator it;
1608 for(it = m_paths.begin(); it != m_paths.end(); ++it)
1610 if (it->GetGitPathString()==path.GetGitPathString())
1612 m_paths.erase(it);
1613 return;
1617 void CTGitPathList::RemoveChildren()
1619 SortByPathname();
1620 m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::CheckChild), m_paths.end());
1623 bool CTGitPathList::IsEqual(const CTGitPathList& list)
1625 if (list.GetCount() != GetCount())
1626 return false;
1627 for (int i=0; i<list.GetCount(); ++i)
1629 if (!list[i].IsEquivalentTo(m_paths[i]))
1630 return false;
1632 return true;
1635 #if 0
1636 #if defined(_DEBUG)
1637 // Some test cases for these classes
1638 static class CTGitPathTests
1640 public:
1641 CTGitPathTests()
1643 GetDirectoryTest();
1644 AdminDirTest();
1645 SortTest();
1646 RawAppendTest();
1647 PathAppendTest();
1648 RemoveDuplicatesTest();
1649 RemoveChildrenTest();
1650 ContainingDirectoryTest();
1651 AncestorTest();
1652 SubversionPathTest();
1653 GetCommonRootTest();
1654 #if defined(_MFC_VER)
1655 ValidPathAndUrlTest();
1656 ListLoadingTest();
1657 #endif
1660 private:
1661 void GetDirectoryTest()
1663 // Bit tricky, this test, because we need to know something about the file
1664 // layout on the machine which is running the test
1665 TCHAR winDir[MAX_PATH+1] = {0};
1666 GetWindowsDirectory(winDir, MAX_PATH);
1667 CString sWinDir(winDir);
1669 CTGitPath testPath;
1670 // This is a file which we know will always be there
1671 testPath.SetFromUnknown(sWinDir + _T("\\win.ini"));
1672 ATLASSERT(!testPath.IsDirectory());
1673 ATLASSERT(testPath.GetDirectory().GetWinPathString() == sWinDir);
1674 ATLASSERT(testPath.GetContainingDirectory().GetWinPathString() == sWinDir);
1676 // Now do the test on the win directory itself - It's hard to be sure about the containing directory
1677 // but we know it must be different to the directory itself
1678 testPath.SetFromUnknown(sWinDir);
1679 ATLASSERT(testPath.IsDirectory());
1680 ATLASSERT(testPath.GetDirectory().GetWinPathString() == sWinDir);
1681 ATLASSERT(testPath.GetContainingDirectory().GetWinPathString() != sWinDir);
1682 ATLASSERT(testPath.GetContainingDirectory().GetWinPathString().GetLength() < sWinDir.GetLength());
1684 // Try a root path
1685 testPath.SetFromUnknown(_T("C:\\"));
1686 ATLASSERT(testPath.IsDirectory());
1687 ATLASSERT(testPath.GetDirectory().GetWinPathString().CompareNoCase(_T("C:\\"))==0);
1688 ATLASSERT(testPath.GetContainingDirectory().IsEmpty());
1689 // Try a root UNC path
1690 testPath.SetFromUnknown(_T("\\MYSTATION"));
1691 ATLASSERT(testPath.GetContainingDirectory().IsEmpty());
1694 void AdminDirTest()
1696 CTGitPath testPath;
1697 testPath.SetFromUnknown(_T("c:\\.svndir"));
1698 ATLASSERT(!testPath.IsAdminDir());
1699 testPath.SetFromUnknown(_T("c:\\test.svn"));
1700 ATLASSERT(!testPath.IsAdminDir());
1701 testPath.SetFromUnknown(_T("c:\\.svn"));
1702 ATLASSERT(testPath.IsAdminDir());
1703 testPath.SetFromUnknown(_T("c:\\.svndir\\test"));
1704 ATLASSERT(!testPath.IsAdminDir());
1705 testPath.SetFromUnknown(_T("c:\\.svn\\test"));
1706 ATLASSERT(testPath.IsAdminDir());
1708 CTGitPathList pathList;
1709 pathList.AddPath(CTGitPath(_T("c:\\.svndir")));
1710 pathList.AddPath(CTGitPath(_T("c:\\.svn")));
1711 pathList.AddPath(CTGitPath(_T("c:\\.svn\\test")));
1712 pathList.AddPath(CTGitPath(_T("c:\\test")));
1713 pathList.RemoveAdminPaths();
1714 ATLASSERT(pathList.GetCount()==2);
1715 pathList.Clear();
1716 pathList.AddPath(CTGitPath(_T("c:\\test")));
1717 pathList.RemoveAdminPaths();
1718 ATLASSERT(pathList.GetCount()==1);
1721 void SortTest()
1723 CTGitPathList testList;
1724 CTGitPath testPath;
1725 testPath.SetFromUnknown(_T("c:/Z"));
1726 testList.AddPath(testPath);
1727 testPath.SetFromUnknown(_T("c:/B"));
1728 testList.AddPath(testPath);
1729 testPath.SetFromUnknown(_T("c:\\a"));
1730 testList.AddPath(testPath);
1731 testPath.SetFromUnknown(_T("c:/Test"));
1732 testList.AddPath(testPath);
1734 testList.SortByPathname();
1736 ATLASSERT(testList[0].GetWinPathString() == _T("c:\\a"));
1737 ATLASSERT(testList[1].GetWinPathString() == _T("c:\\B"));
1738 ATLASSERT(testList[2].GetWinPathString() == _T("c:\\Test"));
1739 ATLASSERT(testList[3].GetWinPathString() == _T("c:\\Z"));
1742 void RawAppendTest()
1744 CTGitPath testPath(_T("c:/test/"));
1745 testPath.AppendRawString(_T("/Hello"));
1746 ATLASSERT(testPath.GetWinPathString() == _T("c:\\test\\Hello"));
1748 testPath.AppendRawString(_T("\\T2"));
1749 ATLASSERT(testPath.GetWinPathString() == _T("c:\\test\\Hello\\T2"));
1751 CTGitPath testFilePath(_T("C:\\windows\\win.ini"));
1752 CTGitPath testBasePath(_T("c:/temp/myfile.txt"));
1753 testBasePath.AppendRawString(testFilePath.GetFileExtension());
1754 ATLASSERT(testBasePath.GetWinPathString() == _T("c:\\temp\\myfile.txt.ini"));
1757 void PathAppendTest()
1759 CTGitPath testPath(_T("c:/test/"));
1760 testPath.AppendPathString(_T("/Hello"));
1761 ATLASSERT(testPath.GetWinPathString() == _T("c:\\test\\Hello"));
1763 testPath.AppendPathString(_T("T2"));
1764 ATLASSERT(testPath.GetWinPathString() == _T("c:\\test\\Hello\\T2"));
1766 CTGitPath testFilePath(_T("C:\\windows\\win.ini"));
1767 CTGitPath testBasePath(_T("c:/temp/myfile.txt"));
1768 // You wouldn't want to do this in real life - you'd use append-raw
1769 testBasePath.AppendPathString(testFilePath.GetFileExtension());
1770 ATLASSERT(testBasePath.GetWinPathString() == _T("c:\\temp\\myfile.txt\\.ini"));
1773 void RemoveDuplicatesTest()
1775 CTGitPathList list;
1776 list.AddPath(CTGitPath(_T("Z")));
1777 list.AddPath(CTGitPath(_T("A")));
1778 list.AddPath(CTGitPath(_T("E")));
1779 list.AddPath(CTGitPath(_T("E")));
1781 ATLASSERT(list[2].IsEquivalentTo(list[3]));
1782 ATLASSERT(list[2]==list[3]);
1784 ATLASSERT(list.GetCount() == 4);
1786 list.RemoveDuplicates();
1788 ATLASSERT(list.GetCount() == 3);
1790 ATLASSERT(list[0].GetWinPathString() == _T("A"));
1791 ATLASSERT(list[1].GetWinPathString().Compare(_T("E")) == 0);
1792 ATLASSERT(list[2].GetWinPathString() == _T("Z"));
1795 void RemoveChildrenTest()
1797 CTGitPathList list;
1798 list.AddPath(CTGitPath(_T("c:\\test")));
1799 list.AddPath(CTGitPath(_T("c:\\test\\file")));
1800 list.AddPath(CTGitPath(_T("c:\\testfile")));
1801 list.AddPath(CTGitPath(_T("c:\\parent")));
1802 list.AddPath(CTGitPath(_T("c:\\parent\\child")));
1803 list.AddPath(CTGitPath(_T("c:\\parent\\child1")));
1804 list.AddPath(CTGitPath(_T("c:\\parent\\child2")));
1806 ATLASSERT(list.GetCount() == 7);
1808 list.RemoveChildren();
1810 ATLTRACE("count = %d\n", list.GetCount());
1811 ATLASSERT(list.GetCount() == 3);
1813 list.SortByPathname();
1815 ATLASSERT(list[0].GetWinPathString().Compare(_T("c:\\parent")) == 0);
1816 ATLASSERT(list[1].GetWinPathString().Compare(_T("c:\\test")) == 0);
1817 ATLASSERT(list[2].GetWinPathString().Compare(_T("c:\\testfile")) == 0);
1820 #if defined(_MFC_VER)
1821 void ListLoadingTest()
1823 TCHAR buf[MAX_PATH] = {0};
1824 GetCurrentDirectory(MAX_PATH, buf);
1825 CString sPathList(_T("Path1*c:\\path2 with spaces and stuff*\\funnypath\\*"));
1826 CTGitPathList testList;
1827 testList.LoadFromAsteriskSeparatedString(sPathList);
1829 ATLASSERT(testList.GetCount() == 3);
1830 ATLASSERT(testList[0].GetWinPathString() == CString(buf) + _T("\\Path1"));
1831 ATLASSERT(testList[1].GetWinPathString() == _T("c:\\path2 with spaces and stuff"));
1832 ATLASSERT(testList[2].GetWinPathString() == _T("\\funnypath"));
1834 ATLASSERT(testList.GetCommonRoot().GetWinPathString() == _T(""));
1835 testList.Clear();
1836 sPathList = _T("c:\\path2 with spaces and stuff*c:\\funnypath\\*");
1837 testList.LoadFromAsteriskSeparatedString(sPathList);
1838 ATLASSERT(testList.GetCommonRoot().GetWinPathString() == _T("c:\\"));
1840 #endif
1842 void ContainingDirectoryTest()
1845 CTGitPath testPath;
1846 testPath.SetFromWin(_T("c:\\a\\b\\c\\d\\e"));
1847 CTGitPath dir;
1848 dir = testPath.GetContainingDirectory();
1849 ATLASSERT(dir.GetWinPathString() == _T("c:\\a\\b\\c\\d"));
1850 dir = dir.GetContainingDirectory();
1851 ATLASSERT(dir.GetWinPathString() == _T("c:\\a\\b\\c"));
1852 dir = dir.GetContainingDirectory();
1853 ATLASSERT(dir.GetWinPathString() == _T("c:\\a\\b"));
1854 dir = dir.GetContainingDirectory();
1855 ATLASSERT(dir.GetWinPathString() == _T("c:\\a"));
1856 dir = dir.GetContainingDirectory();
1857 ATLASSERT(dir.GetWinPathString() == _T("c:\\"));
1858 dir = dir.GetContainingDirectory();
1859 ATLASSERT(dir.IsEmpty());
1860 ATLASSERT(dir.GetWinPathString() == _T(""));
1863 void AncestorTest()
1865 CTGitPath testPath;
1866 testPath.SetFromWin(_T("c:\\windows"));
1867 ATLASSERT(testPath.IsAncestorOf(CTGitPath(_T("c:\\")))==false);
1868 ATLASSERT(testPath.IsAncestorOf(CTGitPath(_T("c:\\windows"))));
1869 ATLASSERT(testPath.IsAncestorOf(CTGitPath(_T("c:\\windowsdummy")))==false);
1870 ATLASSERT(testPath.IsAncestorOf(CTGitPath(_T("c:\\windows\\test.txt"))));
1871 ATLASSERT(testPath.IsAncestorOf(CTGitPath(_T("c:\\windows\\system32\\test.txt"))));
1874 void SubversionPathTest()
1876 CTGitPath testPath;
1877 testPath.SetFromWin(_T("c:\\"));
1878 ATLASSERT(strcmp(testPath.GetGitApiPath(pool), "c:") == 0);
1879 testPath.SetFromWin(_T("c:\\folder"));
1880 ATLASSERT(strcmp(testPath.GetGitApiPath(pool), "c:/folder") == 0);
1881 testPath.SetFromWin(_T("c:\\a\\b\\c\\d\\e"));
1882 ATLASSERT(strcmp(testPath.GetGitApiPath(pool), "c:/a/b/c/d/e") == 0);
1883 testPath.SetFromUnknown(_T("http://testing/"));
1884 ATLASSERT(strcmp(testPath.GetGitApiPath(pool), "http://testing") == 0);
1885 testPath.SetFromGit(NULL);
1886 ATLASSERT(strlen(testPath.GetGitApiPath(pool))==0);
1887 #if defined(_MFC_VER)
1888 testPath.SetFromUnknown(_T("http://testing again"));
1889 ATLASSERT(strcmp(testPath.GetGitApiPath(pool), "http://testing%20again") == 0);
1890 testPath.SetFromUnknown(_T("http://testing%20again"));
1891 ATLASSERT(strcmp(testPath.GetGitApiPath(pool), "http://testing%20again") == 0);
1892 testPath.SetFromUnknown(_T("http://testing special chars \344\366\374"));
1893 ATLASSERT(strcmp(testPath.GetGitApiPath(pool), "http://testing%20special%20chars%20%c3%a4%c3%b6%c3%bc") == 0);
1894 #endif
1897 void GetCommonRootTest()
1899 CTGitPath pathA (_T("C:\\Development\\LogDlg.cpp"));
1900 CTGitPath pathB (_T("C:\\Development\\LogDlg.h"));
1901 CTGitPath pathC (_T("C:\\Development\\SomeDir\\LogDlg.h"));
1903 CTGitPathList list;
1904 list.AddPath(pathA);
1905 ATLASSERT(list.GetCommonRoot().GetWinPathString().CompareNoCase(_T("C:\\Development\\LogDlg.cpp"))==0);
1906 list.AddPath(pathB);
1907 ATLASSERT(list.GetCommonRoot().GetWinPathString().CompareNoCase(_T("C:\\Development"))==0);
1908 list.AddPath(pathC);
1909 ATLASSERT(list.GetCommonRoot().GetWinPathString().CompareNoCase(_T("C:\\Development"))==0);
1910 #ifdef _MFC_VER
1911 list.Clear();
1912 CString sPathList = _T("D:\\Development\\StExBar\\StExBar\\src\\setup\\Setup64.wxs*D:\\Development\\StExBar\\StExBar\\src\\setup\\Setup.wxs*D:\\Development\\StExBar\\SKTimeStamp\\src\\setup\\Setup.wxs*D:\\Development\\StExBar\\SKTimeStamp\\src\\setup\\Setup64.wxs");
1913 list.LoadFromAsteriskSeparatedString(sPathList);
1914 ATLASSERT(list.GetCommonRoot().GetWinPathString().CompareNoCase(_T("D:\\Development\\StExBar"))==0);
1916 list.Clear();
1917 sPathList = _T("c:\\windows\\explorer.exe*c:\\windows");
1918 list.LoadFromAsteriskSeparatedString(sPathList);
1919 ATLASSERT(list.GetCommonRoot().GetWinPathString().CompareNoCase(_T("c:\\windows"))==0);
1921 list.Clear();
1922 sPathList = _T("c:\\windows\\*c:\\windows");
1923 list.LoadFromAsteriskSeparatedString(sPathList);
1924 ATLASSERT(list.GetCommonRoot().GetWinPathString().CompareNoCase(_T("c:\\windows"))==0);
1926 list.Clear();
1927 sPathList = _T("c:\\windows\\system32*c:\\windows\\system");
1928 list.LoadFromAsteriskSeparatedString(sPathList);
1929 ATLASSERT(list.GetCommonRoot().GetWinPathString().CompareNoCase(_T("c:\\windows"))==0);
1931 list.Clear();
1932 sPathList = _T("c:\\windowsdummy*c:\\windows");
1933 list.LoadFromAsteriskSeparatedString(sPathList);
1934 ATLASSERT(list.GetCommonRoot().GetWinPathString().CompareNoCase(_T("c:\\"))==0);
1935 #endif
1938 void ValidPathAndUrlTest()
1940 CTGitPath testPath;
1941 testPath.SetFromWin(_T("c:\\a\\b\\c.test.txt"));
1942 ATLASSERT(testPath.IsValidOnWindows());
1943 testPath.SetFromWin(_T("c:\\"));
1944 ATLASSERT(testPath.IsValidOnWindows());
1945 testPath.SetFromWin(_T("D:\\.Net\\SpindleSearch\\"));
1946 ATLASSERT(testPath.IsValidOnWindows());
1947 testPath.SetFromWin(_T("c"));
1948 ATLASSERT(testPath.IsValidOnWindows());
1949 testPath.SetFromWin(_T("c:\\test folder\\file"));
1950 ATLASSERT(testPath.IsValidOnWindows());
1951 testPath.SetFromWin(_T("c:\\folder\\"));
1952 ATLASSERT(testPath.IsValidOnWindows());
1953 testPath.SetFromWin(_T("c:\\ext.ext.ext\\ext.ext.ext.ext"));
1954 ATLASSERT(testPath.IsValidOnWindows());
1955 testPath.SetFromWin(_T("c:\\.svn"));
1956 ATLASSERT(testPath.IsValidOnWindows());
1957 testPath.SetFromWin(_T("c:\\com\\file"));
1958 ATLASSERT(testPath.IsValidOnWindows());
1959 testPath.SetFromWin(_T("c:\\test\\conf"));
1960 ATLASSERT(testPath.IsValidOnWindows());
1961 testPath.SetFromWin(_T("c:\\LPT"));
1962 ATLASSERT(testPath.IsValidOnWindows());
1963 testPath.SetFromWin(_T("c:\\test\\LPT"));
1964 ATLASSERT(testPath.IsValidOnWindows());
1965 testPath.SetFromWin(_T("c:\\com1test"));
1966 ATLASSERT(testPath.IsValidOnWindows());
1967 testPath.SetFromWin(_T("\\\\?\\c:\\test\\com1test"));
1968 ATLASSERT(testPath.IsValidOnWindows());
1970 testPath.SetFromWin(_T("\\\\Share\\filename"));
1971 ATLASSERT(testPath.IsValidOnWindows());
1972 testPath.SetFromWin(_T("\\\\Share\\filename.extension"));
1973 ATLASSERT(testPath.IsValidOnWindows());
1974 testPath.SetFromWin(_T("\\\\Share\\.svn"));
1975 ATLASSERT(testPath.IsValidOnWindows());
1977 // now the negative tests
1978 testPath.SetFromWin(_T("c:\\test:folder"));
1979 ATLASSERT(!testPath.IsValidOnWindows());
1980 testPath.SetFromWin(_T("c:\\file<name"));
1981 ATLASSERT(!testPath.IsValidOnWindows());
1982 testPath.SetFromWin(_T("c:\\something*else"));
1983 ATLASSERT(!testPath.IsValidOnWindows());
1984 testPath.SetFromWin(_T("c:\\folder\\file?nofile"));
1985 ATLASSERT(!testPath.IsValidOnWindows());
1986 testPath.SetFromWin(_T("c:\\ext.>ension"));
1987 ATLASSERT(!testPath.IsValidOnWindows());
1988 testPath.SetFromWin(_T("c:\\com1\\filename"));
1989 ATLASSERT(!testPath.IsValidOnWindows());
1990 testPath.SetFromWin(_T("c:\\com1"));
1991 ATLASSERT(!testPath.IsValidOnWindows());
1992 testPath.SetFromWin(_T("c:\\com1\\AuX"));
1993 ATLASSERT(!testPath.IsValidOnWindows());
1995 testPath.SetFromWin(_T("\\\\Share\\lpt9\\filename"));
1996 ATLASSERT(!testPath.IsValidOnWindows());
1997 testPath.SetFromWin(_T("\\\\Share\\prn"));
1998 ATLASSERT(!testPath.IsValidOnWindows());
1999 testPath.SetFromWin(_T("\\\\Share\\NUL"));
2000 ATLASSERT(!testPath.IsValidOnWindows());
2002 // now come some URL tests
2003 testPath.SetFromGit(_T("http://myserver.com/repos/trunk"));
2004 ATLASSERT(testPath.IsValidOnWindows());
2005 testPath.SetFromGit(_T("https://myserver.com/repos/trunk/file%20with%20spaces"));
2006 ATLASSERT(testPath.IsValidOnWindows());
2007 testPath.SetFromGit(_T("svn://myserver.com/repos/trunk/file with spaces"));
2008 ATLASSERT(testPath.IsValidOnWindows());
2009 testPath.SetFromGit(_T("svn+ssh://www.myserver.com/repos/trunk"));
2010 ATLASSERT(testPath.IsValidOnWindows());
2011 testPath.SetFromGit(_T("http://localhost:90/repos/trunk"));
2012 ATLASSERT(testPath.IsValidOnWindows());
2013 testPath.SetFromGit(_T("file:///C:/GitRepos/Tester/Proj1/tags/t2"));
2014 ATLASSERT(testPath.IsValidOnWindows());
2015 // and some negative URL tests
2016 testPath.SetFromGit(_T("httpp://myserver.com/repos/trunk"));
2017 ATLASSERT(!testPath.IsValidOnWindows());
2018 testPath.SetFromGit(_T("https://myserver.com/rep:os/trunk/file%20with%20spaces"));
2019 ATLASSERT(!testPath.IsValidOnWindows());
2020 testPath.SetFromGit(_T("svn://myserver.com/rep<os/trunk/file with spaces"));
2021 ATLASSERT(!testPath.IsValidOnWindows());
2022 testPath.SetFromGit(_T("svn+ssh://www.myserver.com/repos/trunk/prn/"));
2023 ATLASSERT(!testPath.IsValidOnWindows());
2024 testPath.SetFromGit(_T("http://localhost:90/repos/trunk/com1"));
2025 ATLASSERT(!testPath.IsValidOnWindows());
2029 } TGitPathTestobject;
2030 #endif
2031 #endif
2033 CTGitPath * CTGitPathList::LookForGitPath(CString path)
2035 int i=0;
2036 for (i = 0; i < this->GetCount(); ++i)
2038 if((*this)[i].GetGitPathString() == path )
2039 return (CTGitPath*)&(*this)[i];
2041 return NULL;
2043 CString CTGitPath::GetActionName(int action)
2045 if(action & CTGitPath::LOGACTIONS_UNMERGED)
2046 return MAKEINTRESOURCE(IDS_PATHACTIONS_CONFLICT);
2047 if(action & CTGitPath::LOGACTIONS_ADDED)
2048 return MAKEINTRESOURCE(IDS_PATHACTIONS_ADD);
2049 if(action & CTGitPath::LOGACTIONS_DELETED)
2050 return MAKEINTRESOURCE(IDS_PATHACTIONS_DELETE);
2051 if(action & CTGitPath::LOGACTIONS_MERGED )
2052 return MAKEINTRESOURCE(IDS_PATHACTIONS_MERGED);
2054 if(action & CTGitPath::LOGACTIONS_MODIFIED)
2055 return MAKEINTRESOURCE(IDS_PATHACTIONS_MODIFIED);
2056 if(action & CTGitPath::LOGACTIONS_REPLACED)
2057 return MAKEINTRESOURCE(IDS_PATHACTIONS_RENAME);
2058 if(action & CTGitPath::LOGACTIONS_COPY)
2059 return MAKEINTRESOURCE(IDS_PATHACTIONS_COPY);
2061 if(action & CTGitPath::LOGACTIONS_FORWORD )
2062 return MAKEINTRESOURCE(IDS_PATHACTIONS_FORWARD);
2064 if (action & CTGitPath::LOGACTIONS_ASSUMEVALID)
2065 return MAKEINTRESOURCE(IDS_PATHACTIONS_ASSUMEUNCHANGED);
2066 if (action & CTGitPath::LOGACTIONS_SKIPWORKTREE)
2067 return MAKEINTRESOURCE(IDS_PATHACTIONS_SKIPWORKTREE);
2069 return MAKEINTRESOURCE(IDS_PATHACTIONS_UNKNOWN);
2071 CString CTGitPath::GetActionName()
2073 return GetActionName(m_Action);
2076 int CTGitPathList::GetAction()
2078 return m_Action;