From cea4bf41109e19017d98f6f353ad7336c13eb938 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Sat, 15 Jul 2017 22:10:46 +0200 Subject: [PATCH] Allow to have case insensitive overlay icons This is not recommended as commands such as "git log" are still case sensitive and, therefore, it needs to be additionally enabled. (fixes issue #2980) Signed-off-by: Sven Strickroth --- .../TortoiseGit/tgit_dug/dug_settings_advanced.xml | 10 ++ src/Changelog.txt | 1 + src/Git/GitIndex.cpp | 30 ++-- src/Git/GitStatus.cpp | 76 +++++---- src/Git/GitStatus.h | 2 +- src/Git/gitindex.h | 54 ++++-- src/TortoiseProc/Settings/SettingsAdvanced.cpp | 4 + test/UnitTests/GitIndexTest.cpp | 183 +++++++++++++-------- 8 files changed, 223 insertions(+), 137 deletions(-) diff --git a/doc/source/en/TortoiseGit/tgit_dug/dug_settings_advanced.xml b/doc/source/en/TortoiseGit/tgit_dug/dug_settings_advanced.xml index 5725fdde5..334dc938e 100644 --- a/doc/source/en/TortoiseGit/tgit_dug/dug_settings_advanced.xml +++ b/doc/source/en/TortoiseGit/tgit_dug/dug_settings_advanced.xml @@ -296,6 +296,16 @@ + OverlaysCaseSensitive + + + Starting with TortoiseGit 2.4.0 the overlay icons are case sensitive on filenames. The change was introduced to fix several issues related to casing (such as issue #2654) and git tools (such as git log) being case sensitive on paths. + Upon issue #2980 this is configurable starting from TortoiseGit 2.5.0, however, enabling is not recommended. + The default is true. + + + + ProgressDlgLinesLimit diff --git a/src/Changelog.txt b/src/Changelog.txt index 8ae3e4df9..1010ec8b6 100644 --- a/src/Changelog.txt +++ b/src/Changelog.txt @@ -28,6 +28,7 @@ Released: unreleased * Fixed issue #2775: Fetch And Rebase doesn't rebase if nothing is fetched It's configurable now whether opening the rebase dialog is skipped if nothing was fetched or current HEAD is up2date or newer * Fixed issue #3016: Add context menus to the header views in three way diff mode to open TMerge again with the diff shown in the corresponding file + * Fixed issue #2980: Since TortoiseGit 2.4.0 the icon overlays are case sensitive. if you really want to change this default, you can disable the advanced setting "OverlaysCaseSensitive". This is, however, not the default and not recommended as some git tools such as "git log" are case sensitive on paths and might show an incomplete history. == Bug Fixes == * Fixed issue #2909: Commit window unclosable after clicking "No" and "do not ask again" diff --git a/src/Git/GitIndex.cpp b/src/Git/GitIndex.cpp index 5489307ce..36e0797bf 100644 --- a/src/Git/GitIndex.cpp +++ b/src/Git/GitIndex.cpp @@ -56,7 +56,7 @@ CGitIndexList::CGitIndexList() : m_bHasConflicts(FALSE) , m_LastModifyTime(0) , m_LastFileSize(-1) -, m_iIndexCaps(GIT_INDEXCAP_NO_SYMLINKS) +, m_iIndexCaps(GIT_INDEXCAP_IGNORE_CASE | GIT_INDEXCAP_NO_SYMLINKS) { m_iMaxCheckSize = (__int64)CRegDWORD(L"Software\\TortoiseGit\\TGitCacheCheckContentMaxSize", 10 * 1024) * 1024; // stored in KiB } @@ -65,16 +65,6 @@ CGitIndexList::~CGitIndexList() { } -static bool SortIndex(const CGitIndex &Item1, const CGitIndex &Item2) -{ - return Item1.m_FileName.Compare(Item2.m_FileName) < 0; -} - -static bool SortTree(const CGitTreeItem &Item1, const CGitTreeItem &Item2) -{ - return Item1.m_FileName.Compare(Item2.m_FileName) < 0; -} - int CGitIndexList::ReadIndex(CString dgitdir) { #ifdef GTEST_INCLUDE_GTEST_GTEST_H_ @@ -121,6 +111,8 @@ int CGitIndexList::ReadIndex(CString dgitdir) m_bHasConflicts = FALSE; m_iIndexCaps = git_index_caps(index); + if (CRegDWORD(L"Software\\TortoiseGit\\OverlaysCaseSensitive", TRUE) != FALSE) + m_iIndexCaps &= ~GIT_INDEXCAP_IGNORE_CASE; size_t ecount = git_index_entrycount(index); try @@ -150,7 +142,7 @@ int CGitIndexList::ReadIndex(CString dgitdir) m_bHasConflicts |= GIT_IDXENTRY_STAGE(e); } - std::sort(this->begin(), this->end(), SortIndex); + DoSortFilenametSortVector(*this, IsIgnoreCase()); CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Reloaded index for repo: %s\n", (LPCTSTR)dgitdir); @@ -159,7 +151,7 @@ int CGitIndexList::ReadIndex(CString dgitdir) int CGitIndexList::GetFileStatus(const CString& gitdir, const CString& pathorg, git_wc_status2_t& status, __int64 time, __int64 filesize, bool isSymlink, CGitHash* pHash) { - size_t index = SearchInSortVector(*this, pathorg, -1); + size_t index = SearchInSortVector(*this, pathorg, -1, IsIgnoreCase()); if (index == NPOS) { @@ -173,7 +165,7 @@ int CGitIndexList::GetFileStatus(const CString& gitdir, const CString& pathorg, auto& entry = (*this)[index]; if (pHash) *pHash = entry.m_IndexHash; - ATLASSERT(pathorg == entry.m_FileName); + ATLASSERT(IsIgnoreCase() ? pathorg.CompareNoCase(entry.m_FileName) == 0 : pathorg.Compare(entry.m_FileName) == 0); CAutoRepository repository; return GetFileStatus(repository, gitdir, entry, status, time, filesize, isSymlink); } @@ -271,7 +263,7 @@ int CGitIndexList::GetFileStatus(const CString& gitdir, const CString& path, git if (CStringUtils::EndsWith(path, L'/')) { - size_t index = SearchInSortVector(*this, path, -1); + size_t index = SearchInSortVector(*this, path, -1, IsIgnoreCase()); if (index == NPOS) { status.status = git_wc_status_unversioned; @@ -641,7 +633,7 @@ int ReadTreeRecursive(git_repository &repo, const git_tree * tree, const CString } // ReadTree is/must only be executed on an empty list -int CGitHeadFileList::ReadTree() +int CGitHeadFileList::ReadTree(bool ignoreCase) { ATLASSERT(empty()); @@ -673,7 +665,7 @@ int CGitHeadFileList::ReadTree() return -1; } - std::sort(this->begin(), this->end(), SortTree); + DoSortFilenametSortVector(*this, ignoreCase); CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Reloaded HEAD tree (commit is %s) for repo: %s\n", (LPCTSTR)m_Head.ToString(), (LPCTSTR)m_Gitdir); @@ -1082,7 +1074,7 @@ int CGitIgnoreList::CheckIgnore(const CString &path, const CString &projectroot, return -1; } -void CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir) +void CGitHeadFileMap::CheckHeadAndUpdate(const CString& gitdir, bool ignoreCase) { SHARED_TREE_PTR ptr = this->SafeGet(gitdir); @@ -1090,7 +1082,7 @@ void CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir) return; ptr = std::make_shared(); - if (ptr->ReadHeadHash(gitdir) || ptr->ReadTree()) + if (ptr->ReadHeadHash(gitdir) || ptr->ReadTree(ignoreCase)) { SafeClear(gitdir); return; diff --git a/src/Git/GitStatus.cpp b/src/Git/GitStatus.cpp index e293ba4a1..74d81033e 100644 --- a/src/Git/GitStatus.cpp +++ b/src/Git/GitStatus.cpp @@ -184,7 +184,7 @@ int GitStatus::GetFileStatus(const CString& gitdir, CString path, git_wc_status2 if (IsFull) { if (update) - g_HeadFileMap.CheckHeadAndUpdate(gitdir); + g_HeadFileMap.CheckHeadAndUpdate(gitdir, pIndex->IsIgnoreCase()); // Check Head Tree Hash SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir); @@ -196,7 +196,7 @@ int GitStatus::GetFileStatus(const CString& gitdir, CString path, git_wc_status2 } // deleted only in index item? - if (SearchInSortVector(*treeptr, path, -1) != NPOS) + if (SearchInSortVector(*treeptr, path, -1, pIndex->IsIgnoreCase()) != NPOS) { status.status = git_wc_status_deleted; return 0; @@ -219,7 +219,7 @@ int GitStatus::GetFileStatus(const CString& gitdir, CString path, git_wc_status2 if ((status.status == git_wc_status_normal || status.status == git_wc_status_modified) && IsFull) { if (update) - g_HeadFileMap.CheckHeadAndUpdate(gitdir); + g_HeadFileMap.CheckHeadAndUpdate(gitdir, pIndex->IsIgnoreCase()); // Check Head Tree Hash SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir); @@ -231,7 +231,7 @@ int GitStatus::GetFileStatus(const CString& gitdir, CString path, git_wc_status2 } //add item - size_t start = SearchInSortVector(*treeptr, path, -1); + size_t start = SearchInSortVector(*treeptr, path, -1, pIndex->IsIgnoreCase()); if (start == NPOS) { status.status = git_wc_status_added; @@ -279,7 +279,7 @@ bool GitStatus::IsIgnored(const CString& gitdir, const CString& path, bool isDir return g_IgnoreList.IsIgnore(path, gitdir, isDir); } -int GitStatus::GetFileList(const CString& path, std::vector& list, bool& isRepoRoot) +int GitStatus::GetFileList(const CString& path, std::vector& list, bool& isRepoRoot, bool ignoreCase) { WIN32_FIND_DATA data; CAutoFindFile handle = ::FindFirstFileEx(CombinePath(path, L"*.*"), FindExInfoBasic, &data, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH); @@ -311,7 +311,8 @@ int GitStatus::GetFileList(const CString& path, std::vector& list, handle.CloseHandle(); // manually close handle here in order to keep handles open as short as possible - std::sort(list.begin(), list.end(), SortCGitFileName); + DoSortFilenametSortVector(list, ignoreCase); + return 0; } @@ -323,24 +324,27 @@ int GitStatus::EnumDirStatus(const CString& gitdir, const CString& subpath, git_ if (!path.IsEmpty() && path[path.GetLength() - 1] != L'/') path += L'/'; // Add trail / to show it is directory, not file name. - std::vector filelist; - bool isRepoRoot = false; - GetFileList(CombinePath(gitdir, subpath), filelist, isRepoRoot); - *dirstatus = git_wc_status_unknown; - if (isRepoRoot) - *dirstatus = git_wc_status_normal; - g_IndexFileMap.CheckAndUpdate(gitdir); - g_HeadFileMap.CheckHeadAndUpdate(gitdir); - SHARED_INDEX_PTR indexptr = g_IndexFileMap.SafeGet(gitdir); - SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir); + // there was an error loading the index + if (!indexptr) + return -1; + + g_HeadFileMap.CheckHeadAndUpdate(gitdir, indexptr->IsIgnoreCase()); - // there was an error loading the index or the HEAD commit/tree - if (!indexptr || !treeptr) + SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir); + // there was an error loading the HEAD commit/tree + if (!treeptr) return -1; + std::vector filelist; + bool isRepoRoot = false; + GetFileList(CombinePath(gitdir, subpath), filelist, isRepoRoot, indexptr->IsIgnoreCase()); + *dirstatus = git_wc_status_unknown; + if (isRepoRoot) + *dirstatus = git_wc_status_normal; + CAutoRepository repository; for (auto it = filelist.cbegin(), itend = filelist.cend(); it != itend; ++it) { @@ -356,8 +360,8 @@ int GitStatus::EnumDirStatus(const CString& gitdir, const CString& subpath, git_ int matchLength = -1; if (bIsDir) matchLength = onepath.GetLength(); - size_t pos = SearchInSortVector(*indexptr, onepath, matchLength); - size_t posintree = SearchInSortVector(*treeptr, onepath, matchLength); + size_t pos = SearchInSortVector(*indexptr, onepath, matchLength, indexptr->IsIgnoreCase()); + size_t posintree = SearchInSortVector(*treeptr, onepath, matchLength, indexptr->IsIgnoreCase()); git_wc_status2_t status = { git_wc_status_none, false, false }; @@ -411,10 +415,10 @@ int GitStatus::EnumDirStatus(const CString& gitdir, const CString& subpath, git_ /* Check deleted file in system */ size_t start = 0, end = 0; - size_t pos = SearchInSortVector(*indexptr, path, path.GetLength()); // match path prefix, (sub)folders end with slash + size_t pos = SearchInSortVector(*indexptr, path, path.GetLength(), indexptr->IsIgnoreCase()); // match path prefix, (sub)folders end with slash std::set alreadyReported; - if (GetRangeInSortVector(*indexptr, path, path.GetLength(), &start, &end, pos) == 0) + if (GetRangeInSortVector(*indexptr, path, path.GetLength(), indexptr->IsIgnoreCase(), &start, &end, pos) == 0) { *dirstatus = git_wc_status_normal; // here we know that this folder has versioned entries CString oldstring; @@ -434,7 +438,7 @@ int GitStatus::EnumDirStatus(const CString& gitdir, const CString& subpath, git_ oldstring = filename; int length = filename.GetLength(); bool isDir = filename[length - 1] == L'/'; - if (SearchInSortVector(filelist, filename, isDir ? length : -1) == NPOS) // do full match for filenames and only prefix-match ending with "/" for folders + if (SearchInSortVector(filelist, filename, isDir ? length : -1, indexptr->IsIgnoreCase()) == NPOS) // do full match for filenames and only prefix-match ending with "/" for folders { git_wc_status2_t status = { (!isDir || IsDirectSubmodule(entry.m_FileName, commonPrefixLength)) ? git_wc_status_deleted : git_wc_status_modified, false, false }; // only report deleted submodules and files as deletedy if ((entry.m_FlagsExtended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) @@ -451,7 +455,7 @@ int GitStatus::EnumDirStatus(const CString& gitdir, const CString& subpath, git_ { // folder might be replaced by symlink filename.TrimRight(L'/'); - auto filepos = SearchInSortVector(filelist, filename, -1); + auto filepos = SearchInSortVector(filelist, filename, -1, indexptr->IsIgnoreCase()); if (filepos == NPOS || !filelist[filepos].m_bSymlink) continue; status.status = git_wc_status_deleted; @@ -463,8 +467,8 @@ int GitStatus::EnumDirStatus(const CString& gitdir, const CString& subpath, git_ } start = end = 0; - pos = SearchInSortVector(*treeptr, path, path.GetLength()); // match path prefix, (sub)folders end with slash - if (GetRangeInSortVector(*treeptr, path, path.GetLength(), &start, &end, pos) == 0) + pos = SearchInSortVector(*treeptr, path, path.GetLength(), indexptr->IsIgnoreCase()); // match path prefix, (sub)folders end with slash + if (GetRangeInSortVector(*treeptr, path, path.GetLength(), indexptr->IsIgnoreCase(), &start, &end, pos) == 0) { *dirstatus = git_wc_status_normal; // here we know that this folder has versioned entries CString oldstring; @@ -484,7 +488,7 @@ int GitStatus::EnumDirStatus(const CString& gitdir, const CString& subpath, git_ oldstring = filename; int length = filename.GetLength(); bool isDir = filename[length - 1] == L'/'; - if (SearchInSortVector(filelist, filename, isDir ? length : -1) == NPOS) // do full match for filenames and only prefix-match ending with "/" for folders + if (SearchInSortVector(filelist, filename, isDir ? length : -1, indexptr->IsIgnoreCase()) == NPOS) // do full match for filenames and only prefix-match ending with "/" for folders { git_wc_status2_t status = { (!isDir || IsDirectSubmodule(entry.m_FileName, commonPrefixLength)) ? git_wc_status_deleted : git_wc_status_modified, false, false }; callback(CombinePath(gitdir, subpath, filename), &status, isDir, 0, pData); @@ -518,7 +522,7 @@ int GitStatus::GetDirStatus(const CString& gitdir, const CString& subpath, git_w return -1; } - size_t pos = SearchInSortVector(*indexptr, path, path.GetLength()); + size_t pos = SearchInSortVector(*indexptr, path, path.GetLength(), indexptr->IsIgnoreCase()); // Not In Version Contorl if (pos == NPOS) @@ -535,7 +539,7 @@ int GitStatus::GetDirStatus(const CString& gitdir, const CString& subpath, git_w return 0; } - g_HeadFileMap.CheckHeadAndUpdate(gitdir); + g_HeadFileMap.CheckHeadAndUpdate(gitdir, indexptr->IsIgnoreCase()); SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir); // broken HEAD @@ -546,7 +550,7 @@ int GitStatus::GetDirStatus(const CString& gitdir, const CString& subpath, git_w } // check whether there files in head with are not in index - pos = SearchInSortVector(*treeptr, path, path.GetLength()); + pos = SearchInSortVector(*treeptr, path, path.GetLength(), indexptr->IsIgnoreCase()); if (pos != NPOS) { *status = git_wc_status_deleted; @@ -576,7 +580,7 @@ int GitStatus::GetDirStatus(const CString& gitdir, const CString& subpath, git_w size_t start = 0; size_t end = 0; - GetRangeInSortVector(*indexptr, path, path.GetLength(), &start, &end, pos); + GetRangeInSortVector(*indexptr, path,path.GetLength(), indexptr->IsIgnoreCase(), &start, &end, pos); // Check Conflict; for (auto it = indexptr->cbegin() + start, itlast = indexptr->cbegin() + end; indexptr->m_bHasConflicts && it <= itlast; ++it) @@ -592,7 +596,7 @@ int GitStatus::GetDirStatus(const CString& gitdir, const CString& subpath, git_w if (IsFul) { - g_HeadFileMap.CheckHeadAndUpdate(gitdir); + g_HeadFileMap.CheckHeadAndUpdate(gitdir, indexptr->IsIgnoreCase()); // Check Add { @@ -609,7 +613,7 @@ int GitStatus::GetDirStatus(const CString& gitdir, const CString& subpath, git_w for (auto it = indexptr->cbegin() + start, itlast = indexptr->cbegin() + end; it <= itlast; ++it) { auto& indexentry = *it; - pos = SearchInSortVector(*treeptr, indexentry.m_FileName, -1); + pos = SearchInSortVector(*treeptr, indexentry.m_FileName, -1, indexptr->IsIgnoreCase()); if (pos == NPOS) { @@ -630,17 +634,17 @@ int GitStatus::GetDirStatus(const CString& gitdir, const CString& subpath, git_w // Check Delete if (*status == git_wc_status_normal) { - pos = SearchInSortVector(*treeptr, path, path.GetLength()); + pos = SearchInSortVector(*treeptr, path, path.GetLength(), indexptr->IsIgnoreCase()); if (pos == NPOS) *status = GetMoreImportant(git_wc_status_added, *status); // added file found else { size_t hstart, hend; // we know that pos exists in treeptr - GetRangeInSortVector(*treeptr, path, path.GetLength(), &hstart, &hend, pos); + GetRangeInSortVector(*treeptr, path, path.GetLength(), indexptr->IsIgnoreCase(), &hstart, &hend, pos); for (auto hit = treeptr->cbegin() + hstart, lastElement = treeptr->cbegin() + hend; hit <= lastElement; ++hit) { - if (SearchInSortVector(*indexptr, (*hit).m_FileName, -1) == NPOS) + if (SearchInSortVector(*indexptr, (*hit).m_FileName, -1, indexptr->IsIgnoreCase()) == NPOS) { *status = GetMoreImportant(git_wc_status_deleted, *status); // deleted file found break; diff --git a/src/Git/GitStatus.h b/src/Git/GitStatus.h index d19c1558e..def164f9a 100644 --- a/src/Git/GitStatus.h +++ b/src/Git/GitStatus.h @@ -79,7 +79,7 @@ public: static int GetFileStatus(const CString& gitdir, CString path, git_wc_status2_t& status, BOOL IsFull = FALSE, BOOL isIgnore = TRUE, bool update = true); static int GetDirStatus(const CString& gitdir, const CString& path, git_wc_status_kind* status, BOOL IsFull = false, BOOL IsRecursive = false, BOOL isIgnore = true); static int EnumDirStatus(const CString& gitdir, const CString& path, git_wc_status_kind* dirstatus, FILL_STATUS_CALLBACK callback, void* pData); - static int GetFileList(const CString& path, std::vector& list, bool& isRepoRoot); + static int GetFileList(const CString& path, std::vector& list, bool& isRepoRoot, bool ignoreCase); static bool CheckAndUpdateIgnoreFiles(const CString& gitdir, const CString& subpaths, bool isDir); /** Checks whether a file/directory is ignored - does not reload .ignore files */ static bool IsIgnored(const CString &gitdir, const CString &path, bool isDir); diff --git a/src/Git/gitindex.h b/src/Git/gitindex.h index 53d030d35..711c6512d 100644 --- a/src/Git/gitindex.h +++ b/src/Git/gitindex.h @@ -55,7 +55,7 @@ public: __time64_t m_LastModifyTime; __int64 m_LastFileSize; BOOL m_bHasConflicts; - int m_iIndexCaps; + inline bool IsIgnoreCase() { return m_iIndexCaps & GIT_INDEXCAP_IGNORE_CASE; } CGitIndexList(); ~CGitIndexList(); @@ -67,6 +67,7 @@ public: FRIEND_TEST(GitIndexCBasicGitWithTestRepoFixture, GetFileStatus); #endif protected: + int m_iIndexCaps; __int64 m_iMaxCheckSize; CAutoConfig config; int GetFileStatus(const CString& gitdir, const CString& path, git_wc_status2_t& status, __int64 time, __int64 filesize, bool isSymlink, CGitHash* pHash = nullptr); @@ -177,7 +178,7 @@ public: { } - int ReadTree(); + int ReadTree(bool ignoreCase); int ReadHeadHash(const CString& gitdir); bool CheckHeadUpdate(); static int CallBack(const unsigned char *, const char *, int, const char *, unsigned int, int, void *); @@ -235,7 +236,7 @@ public: this->erase(*it); return !toRemove.empty(); } - void CheckHeadAndUpdate(const CString& gitdir); + void CheckHeadAndUpdate(const CString& gitdir, bool ignoreCase); }; class CGitFileName @@ -255,11 +256,6 @@ public: bool m_bSymlink; }; -static bool SortCGitFileName(const CGitFileName& item1, const CGitFileName& item2) -{ - return item1.m_FileName.Compare(item2.m_FileName) < 0; -} - class CGitIgnoreItem { public: @@ -333,6 +329,15 @@ public: bool IsIgnore(CString path, const CString& root, bool isDir); }; +template +inline void DoSortFilenametSortVector(T& vector, bool ignoreCase) +{ + if (ignoreCase) + std::sort(vector.begin(), vector.end(), [](const auto& e1, const auto& e2) { return e1.m_FileName.CompareNoCase(e2.m_FileName) < 0; }); + else + std::sort(vector.begin(), vector.end(), [](const auto& e1, const auto& e2) { return e1.m_FileName.Compare(e2.m_FileName) < 0; }); +} + static const size_t NPOS = (size_t)-1; // bad/missing length/position static_assert(MAXSIZE_T == NPOS, "NPOS must equal MAXSIZE_T"); #pragma warning(push) @@ -341,7 +346,16 @@ static_assert(-1 == (int)NPOS, "NPOS must equal -1"); #pragma warning(pop) template -int GetRangeInSortVector(const T& vector, LPCTSTR pstr, size_t len, size_t* start, size_t* end, size_t pos) +inline int GetRangeInSortVector(const T& vector, LPCTSTR pstr, size_t len, bool ignoreCase, size_t* start, size_t* end, size_t pos) +{ + if (ignoreCase) + return GetRangeInSortVector_int(vector, pstr, len, _wcsnicmp, start, end, pos); + + return GetRangeInSortVector_int(vector, pstr, len, wcsncmp, start, end, pos); +} + +template +int GetRangeInSortVector_int(const T& vector, LPCTSTR pstr, size_t len, V compare, size_t* start, size_t* end, size_t pos) { if (pos == NPOS) return -1; @@ -356,7 +370,7 @@ int GetRangeInSortVector(const T& vector, LPCTSTR pstr, size_t len, size_t* star if (pos >= vector.size()) return -1; - if (wcsncmp(vector[pos].m_FileName, pstr, len) != 0) + if (compare(vector[pos].m_FileName, pstr, len) != 0) return -1; *start = 0; @@ -368,14 +382,14 @@ int GetRangeInSortVector(const T& vector, LPCTSTR pstr, size_t len, size_t* star for (size_t i = pos; i < vector.size(); ++i) { - if (wcsncmp(vector[i].m_FileName, pstr, len) != 0) + if (compare(vector[i].m_FileName, pstr, len) != 0) break; *end = i; } for (size_t i = pos + 1; i-- > 0;) { - if (wcsncmp(vector[i].m_FileName, pstr, len) != 0) + if (compare(vector[i].m_FileName, pstr, len) != 0) break; *start = i; @@ -385,16 +399,24 @@ int GetRangeInSortVector(const T& vector, LPCTSTR pstr, size_t len, size_t* star } template -inline size_t SearchInSortVector(const T& vector, LPCTSTR pstr, int len) +inline size_t SearchInSortVector(const T& vector, LPCTSTR pstr, int len, bool ignoreCase) { + if (ignoreCase) + { + if (len < 0) + return SearchInSortVector_int(vector, pstr, _wcsicmp); + + return SearchInSortVector_int(vector, pstr, [len](const auto& s1, const auto& s2) { return _wcsnicmp(s1, s2, len); }); + } + if (len < 0) - return SearchInSortVector(vector, pstr, wcscmp); + return SearchInSortVector_int(vector, pstr, wcscmp); - return SearchInSortVector(vector, pstr, [len](const auto& s1, const auto& s2) { return wcsncmp(s1, s2, len); }); + return SearchInSortVector_int(vector, pstr, [len](const auto& s1, const auto& s2) { return wcsncmp(s1, s2, len); }); } template -static size_t SearchInSortVector(const T& vector, LPCTSTR pstr, V compare) +size_t SearchInSortVector_int(const T& vector, LPCTSTR pstr, V compare) { size_t end = vector.size() - 1; size_t start = 0; diff --git a/src/TortoiseProc/Settings/SettingsAdvanced.cpp b/src/TortoiseProc/Settings/SettingsAdvanced.cpp index c355ca986..f064914d8 100644 --- a/src/TortoiseProc/Settings/SettingsAdvanced.cpp +++ b/src/TortoiseProc/Settings/SettingsAdvanced.cpp @@ -114,6 +114,10 @@ CSettingsAdvanced::CSettingsAdvanced() settings[i].type = CSettingsAdvanced::SettingTypeNumber; settings[i++].def.l = 10; + settings[i].sName = L"OverlaysCaseSensitive"; + settings[i].type = CSettingsAdvanced::SettingTypeBoolean; + settings[i++].def.b = true; + settings[i].sName = L"ProgressDlgLinesLimit"; settings[i].type = CSettingsAdvanced::SettingTypeNumber; settings[i++].def.l = 50000; diff --git a/test/UnitTests/GitIndexTest.cpp b/test/UnitTests/GitIndexTest.cpp index a5c4499ef..0a9ccd028 100644 --- a/test/UnitTests/GitIndexTest.cpp +++ b/test/UnitTests/GitIndexTest.cpp @@ -273,92 +273,139 @@ TEST_P(GitIndexCBasicGitWithTestRepoFixture, GetFileStatus) TEST(GitIndex, SearchInSortVector) { std::vector vector; - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 9)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 0)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", -1)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 9, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 0, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", -1, false)); vector.push_back(CGitFileName(L"One", 0, 0)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 9)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", -1)); - EXPECT_EQ(0, SearchInSortVector(vector, L"something", 0)); // do we really need this behavior? - EXPECT_EQ(0, SearchInSortVector(vector, L"One", 3)); - EXPECT_EQ(0, SearchInSortVector(vector, L"One", -1)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"One/", 4)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"one", 3)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"one", -1)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"one/", 4)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 9, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", -1, false)); + EXPECT_EQ(0, SearchInSortVector(vector, L"something", 0, false)); // do we really need this behavior? + EXPECT_EQ(0, SearchInSortVector(vector, L"One", 3, false)); + EXPECT_EQ(0, SearchInSortVector(vector, L"One", -1, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"One/", 4, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"one", 3, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"one", -1, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"one/", 4, false)); vector.push_back(CGitFileName(L"tWo", 0, 0)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 9)); - EXPECT_EQ(0, SearchInSortVector(vector, L"One", 3)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"one", 3)); - EXPECT_EQ(1, SearchInSortVector(vector, L"tWo", 3)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"two", 3)); - EXPECT_EQ(1, SearchInSortVector(vector, L"t", 1)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"0", 1)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"z", 1)); + DoSortFilenametSortVector(vector, false); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 9, false)); + EXPECT_EQ(0, SearchInSortVector(vector, L"One", 3, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"one", 3, false)); + EXPECT_EQ(1, SearchInSortVector(vector, L"tWo", 3, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"two", 3, false)); + EXPECT_EQ(1, SearchInSortVector(vector, L"t", 1, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"0", 1, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"z", 1, false)); + vector.push_back(CGitFileName(L"b/1", 0, 0)); + vector.push_back(CGitFileName(L"b/2", 0, 0)); vector.push_back(CGitFileName(L"a", 0, 0)); + vector.push_back(CGitFileName(L"b/3", 0, 0)); + vector.push_back(CGitFileName(L"b/4", 0, 0)); + vector.push_back(CGitFileName(L"b/5", 0, 0)); + DoSortFilenametSortVector(vector, false); + EXPECT_EQ(0, SearchInSortVector(vector, L"One", 3, false)); + EXPECT_EQ(3, SearchInSortVector(vector, L"b/2", 3, false)); + EXPECT_EQ(3, SearchInSortVector(vector, L"b/2", -1, false)); + EXPECT_LT((size_t)2, SearchInSortVector(vector, L"b/", 2, false)); + EXPECT_GE((size_t)6, SearchInSortVector(vector, L"b/", 2, false)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"b/6", 3, false)); + EXPECT_EQ(1, SearchInSortVector(vector, L"a", 1, false)); + EXPECT_EQ(7, SearchInSortVector(vector, L"tWo", 3, false)); +} + +TEST(GitIndex, SearchInSortVector_IgnoreCase) +{ + std::vector vector; + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 9, true)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 0, true)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", -1, true)); + + vector.push_back(CGitFileName(L"One", 0, 0)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 9, true)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", -1, true)); + EXPECT_EQ(0, SearchInSortVector(vector, L"something", 0, true)); // do we really need this behavior? + EXPECT_EQ(0, SearchInSortVector(vector, L"One", 3, true)); + EXPECT_EQ(0, SearchInSortVector(vector, L"One", -1, true)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"One/", 4, true)); + EXPECT_EQ(0, SearchInSortVector(vector, L"one", 3, true)); + EXPECT_EQ(0, SearchInSortVector(vector, L"one", -1, true)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"one/", 4, true)); + + vector.push_back(CGitFileName(L"tWo", 0, 0)); + DoSortFilenametSortVector(vector, true); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"something", 9, true)); + EXPECT_EQ(0, SearchInSortVector(vector, L"One", 3, true)); + EXPECT_EQ(0, SearchInSortVector(vector, L"one", 3, true)); + EXPECT_EQ(1, SearchInSortVector(vector, L"tWo", 3, true)); + EXPECT_EQ(1, SearchInSortVector(vector, L"two", 3, true)); + EXPECT_EQ(1, SearchInSortVector(vector, L"t", 1, true)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"0", 1, true)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"z", 1, true)); + vector.push_back(CGitFileName(L"b/1", 0, 0)); vector.push_back(CGitFileName(L"b/2", 0, 0)); + vector.push_back(CGitFileName(L"a", 0, 0)); vector.push_back(CGitFileName(L"b/3", 0, 0)); vector.push_back(CGitFileName(L"b/4", 0, 0)); vector.push_back(CGitFileName(L"b/5", 0, 0)); - std::sort(vector.begin(), vector.end(), SortCGitFileName); - EXPECT_EQ(0, SearchInSortVector(vector, L"One", 3)); - EXPECT_EQ(3, SearchInSortVector(vector, L"b/2", 3)); - EXPECT_EQ(3, SearchInSortVector(vector, L"b/2", -1)); - EXPECT_LT((size_t)2, SearchInSortVector(vector, L"b/", 2)); - EXPECT_GE((size_t)6, SearchInSortVector(vector, L"b/", 2)); - EXPECT_EQ(NPOS, SearchInSortVector(vector, L"b/6", 3)); - EXPECT_EQ(1, SearchInSortVector(vector, L"a", 1)); - EXPECT_EQ(7, SearchInSortVector(vector, L"tWo", 3)); + DoSortFilenametSortVector(vector, true); + EXPECT_EQ(0, SearchInSortVector(vector, L"a", 1, true)); + EXPECT_EQ(2, SearchInSortVector(vector, L"b/2", 3, true)); + EXPECT_EQ(2, SearchInSortVector(vector, L"b/2", -1, true)); + EXPECT_LT((size_t)1, SearchInSortVector(vector, L"b/", 2, true)); + EXPECT_GE((size_t)5, SearchInSortVector(vector, L"b/", 2, true)); + EXPECT_EQ(NPOS, SearchInSortVector(vector, L"b/6", 3, true)); + EXPECT_EQ(6, SearchInSortVector(vector, L"One", 3, true)); + EXPECT_EQ(7, SearchInSortVector(vector, L"tWo", 3, true)); } -TEST(GitIndex, GetRangeInSortVector) +static void CheckRangeInSortVector(bool ignoreCase) { std::vector vector; size_t start = NPOS; size_t end = NPOS; - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, &start, &end, NPOS)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, &start, &end, 0)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, &start, nullptr, 0)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, nullptr, &end, 0)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, nullptr, &end, 1)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"", 0, &start, &end, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, ignoreCase, &start, &end, NPOS)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, ignoreCase, &start, &end, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, ignoreCase, &start, nullptr, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, ignoreCase, nullptr, &end, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, ignoreCase, nullptr, &end, 1)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"", 0, ignoreCase, &start, &end, 0)); EXPECT_EQ(NPOS, start); EXPECT_EQ(NPOS, end); vector.push_back(CGitFileName(L"a", 0, 0)); start = end = NPOS; - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, &start, &end, NPOS)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, &start, nullptr, 0)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, nullptr, &end, 0)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, nullptr, &end, 1)); - - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"a", 1, &start, &end, NPOS)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"a", 1, &start, nullptr, 0)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"a", 1, nullptr, &end, 0)); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"a", 1, nullptr, &end, 1)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, ignoreCase, &start, &end, NPOS)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, ignoreCase, &start, nullptr, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, ignoreCase, nullptr, &end, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"something", 9, ignoreCase, nullptr, &end, 1)); + + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"a", 1, ignoreCase, &start, &end, NPOS)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"a", 1, ignoreCase, &start, nullptr, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"a", 1, ignoreCase, nullptr, &end, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"a", 1, ignoreCase, nullptr, &end, 1)); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"", 0, &start, &end, 0)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"", 0, ignoreCase, &start, &end, 0)); EXPECT_EQ(0, start); EXPECT_EQ(0, end); start = end = NPOS; - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"", 0, &start, &end, 1)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"", 0, ignoreCase, &start, &end, 1)); start = end = NPOS; - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"0", 1, &start, &end, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"0", 1, ignoreCase, &start, &end, 0)); EXPECT_EQ(NPOS, start); EXPECT_EQ(NPOS, end); start = end = NPOS; - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"b", 1, &start, &end, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"b", 1, ignoreCase, &start, &end, 0)); EXPECT_EQ(NPOS, start); EXPECT_EQ(NPOS, end); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"a", 1, &start, &end, 0)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"a", 1, ignoreCase, &start, &end, 0)); EXPECT_EQ(0, start); EXPECT_EQ(0, end); @@ -369,39 +416,39 @@ TEST(GitIndex, GetRangeInSortVector) vector.push_back(CGitFileName(L"b/5", 0, 0)); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"a", 1, &start, &end, 0)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"a", 1, ignoreCase, &start, &end, 0)); EXPECT_EQ(0, start); EXPECT_EQ(0, end); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, &start, &end, 1)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, ignoreCase, &start, &end, 1)); EXPECT_EQ(1, start); EXPECT_EQ(5, end); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, &start, &end, 2)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, ignoreCase, &start, &end, 2)); EXPECT_EQ(1, start); EXPECT_EQ(5, end); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, &start, &end, 4)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, ignoreCase, &start, &end, 4)); EXPECT_EQ(1, start); EXPECT_EQ(5, end); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, &start, &end, 5)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, ignoreCase, &start, &end, 5)); EXPECT_EQ(1, start); EXPECT_EQ(5, end); - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"b/", 2, &start, &end, 6)); // 6 is >= vector.size() + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"b/", 2, ignoreCase, &start, &end, 6)); // 6 is >= vector.size() start = end = NPOS; - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"c/", 2, &start, &end, 0)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"c/", 2, ignoreCase, &start, &end, 0)); EXPECT_EQ(NPOS, start); EXPECT_EQ(NPOS, end); start = end = NPOS; - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"c/", 2, &start, &end, 5)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"c/", 2, ignoreCase, &start, &end, 5)); EXPECT_EQ(NPOS, start); EXPECT_EQ(NPOS, end); @@ -409,41 +456,47 @@ TEST(GitIndex, GetRangeInSortVector) vector.push_back(CGitFileName(L"d", 0, 0)); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, &start, &end, 1)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, ignoreCase, &start, &end, 1)); EXPECT_EQ(1, start); EXPECT_EQ(5, end); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, &start, &end, 2)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, ignoreCase, &start, &end, 2)); EXPECT_EQ(1, start); EXPECT_EQ(5, end); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, &start, &end, 4)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, ignoreCase, &start, &end, 4)); EXPECT_EQ(1, start); EXPECT_EQ(5, end); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, &start, &end, 5)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"b/", 2, ignoreCase, &start, &end, 5)); EXPECT_EQ(1, start); EXPECT_EQ(5, end); start = end = NPOS; - EXPECT_EQ(-1, GetRangeInSortVector(vector, L"c/", 2, &start, &end, 6)); + EXPECT_EQ(-1, GetRangeInSortVector(vector, L"c/", 2, ignoreCase, &start, &end, 6)); EXPECT_EQ(NPOS, start); EXPECT_EQ(NPOS, end); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"c", 1, &start, &end, 6)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"c", 1, ignoreCase, &start, &end, 6)); EXPECT_EQ(6, start); EXPECT_EQ(6, end); start = end = NPOS; - EXPECT_EQ(0, GetRangeInSortVector(vector, L"", 0, &start, &end, 0)); + EXPECT_EQ(0, GetRangeInSortVector(vector, L"", 0, ignoreCase, &start, &end, 0)); EXPECT_EQ(0, start); EXPECT_EQ(7, end); } +TEST(GitIndex, GetRangeInSortVector) +{ + CheckRangeInSortVector(false); + CheckRangeInSortVector(true); +} + TEST(GitIndex, CGitIgnoreItem) { CAutoTempDir tempDir; -- 2.11.4.GIT