bool CGitStatusCache::UnBlockPath(const CTGitPath& path) { bool ret = false; AutoLocker lock(m_NoWatchPathCritSec); std::map<CTGitPath, DWORD>::iterator it = m_NoWatchPaths.find(path.GetDirectory()); if (it != m_NoWatchPaths.end()) { CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": path removed from no good: %s\n"), it->first.GetWinPath()); m_NoWatchPaths.erase(it); ret = true; } AddFolderForCrawling(path.GetDirectory()); return ret; }
bool CGitStatusCache::UnBlockPath(const CTGitPath& path) { bool ret = false; AutoLocker lock(m_NoWatchPathCritSec); std::map<CTGitPath, DWORD>::iterator it = m_NoWatchPaths.find(path.GetDirectory()); if (it != m_NoWatchPaths.end()) { ATLTRACE(_T("path removed from no good: %s\n"), it->first.GetWinPath()); m_NoWatchPaths.erase(it); ret = true; } AddFolderForCrawling(path.GetDirectory()); return ret; }
bool CRemoteCacheLink::ReleaseLockForPath(const CTGitPath& path) { EnsureCommandPipeOpen(); if (m_hCommandPipe) { DWORD cbWritten; TGITCacheCommand cmd; SecureZeroMemory(&cmd, sizeof(TGITCacheCommand)); cmd.command = TGITCACHECOMMAND_RELEASE; wcsncpy_s(cmd.path, path.GetDirectory().GetWinPath(), MAX_PATH); BOOL fSuccess = WriteFile( m_hCommandPipe, // handle to pipe &cmd, // buffer to write from sizeof(cmd), // number of bytes to write &cbWritten, // number of bytes written NULL); // not overlapped I/O if (! fSuccess || sizeof(cmd) != cbWritten) { CloseCommandPipe(); return false; } return true; } return false; }
bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right) { if (left.IsAdminDir() && right.IsAdminDir()) { CTGitPath l = left; CTGitPath r = right; do { l = l.GetContainingDirectory(); } while(l.HasAdminDir()); do { r = r.GetContainingDirectory(); } while(r.HasAdminDir()); return l.GetContainingDirectory().IsEquivalentTo(r.GetContainingDirectory()); } return left.GetDirectory().IsEquivalentTo(right.GetDirectory()); }
bool CGitStatusCache::BlockPath(const CTGitPath& path, DWORD timeout /* = 0 */) { if (timeout == 0) timeout = BLOCK_PATH_DEFAULT_TIMEOUT; if (timeout > BLOCK_PATH_MAX_TIMEOUT) timeout = BLOCK_PATH_MAX_TIMEOUT; timeout = GetTickCount() + (timeout * 1000); // timeout is in seconds, but we need the milliseconds AutoLocker lock(m_NoWatchPathCritSec); m_NoWatchPaths[path.GetDirectory()] = timeout; return true; }
/* Fetch is false, means fetch status from cache */ CStatusCacheEntry CGitStatusCache::GetStatusForPath(const CTGitPath& path, DWORD flags, bool bFetch /* = true */) { bool bRecursive = !!(flags & TGITCACHE_FLAGS_RECUSIVE_STATUS); // Check a very short-lived 'mini-cache' of the last thing we were asked for. long now = (long)GetTickCount(); if(now-m_mostRecentExpiresAt < 0) { if(path.IsEquivalentToWithoutCase(m_mostRecentPath)) { return m_mostRecentStatus; } } m_mostRecentPath = path; m_mostRecentExpiresAt = now+1000; if (IsPathGood(path)) { // Stop the crawler starting on a new folder while we're doing this much more important task... // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath(). CCrawlInhibitor crawlInhibit(&m_folderCrawler); CTGitPath dirpath = path.GetContainingDirectory(); if ((dirpath.IsEmpty()) || (!m_shellCache.IsPathAllowed(dirpath.GetWinPath()))) dirpath = path.GetDirectory(); CCachedDirectory * cachedDir = GetDirectoryCacheEntry(dirpath); if (cachedDir != NULL) { //ATLTRACE(_T("GetStatusForMember %d\n"), bFetch); m_mostRecentStatus = cachedDir->GetStatusForMember(path, bRecursive, bFetch); return m_mostRecentStatus; } } ATLTRACE(_T("ignored no good path %s\n"), path.GetWinPath()); m_mostRecentStatus = CStatusCacheEntry(); if (m_shellCache.ShowExcludedAsNormal() && path.IsDirectory() && m_shellCache.HasGITAdminDir(path.GetWinPath(), true)) { ATLTRACE(_T("force status %s\n"), path.GetWinPath()); m_mostRecentStatus.ForceStatus(git_wc_status_normal); } return m_mostRecentStatus; }
/* Fetch is false, means fetch status from cache */ CStatusCacheEntry CGitStatusCache::GetStatusForPath(const CTGitPath& path, DWORD flags, bool bFetch /* = true */) { bool bRecursive = !!(flags & TGITCACHE_FLAGS_RECUSIVE_STATUS); // Check a very short-lived 'mini-cache' of the last thing we were asked for. long now = (long)GetTickCount(); if(now-m_mostRecentExpiresAt < 0) { if(path.IsEquivalentToWithoutCase(m_mostRecentPath)) { return m_mostRecentStatus; } } { AutoLocker lock(m_critSec); m_mostRecentPath = path; m_mostRecentExpiresAt = now + 1000; } if (IsPathGood(path)) { // Stop the crawler starting on a new folder while we're doing this much more important task... // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath(). CCrawlInhibitor crawlInhibit(&m_folderCrawler); CTGitPath dirpath = path.GetContainingDirectory(); if ((dirpath.IsEmpty()) || (!m_shellCache.IsPathAllowed(dirpath.GetWinPath()))) dirpath = path.GetDirectory(); CCachedDirectory * cachedDir = GetDirectoryCacheEntry(dirpath); if (cachedDir != NULL) { //ATLTRACE(_T("GetStatusForMember %d\n"), bFetch); CStatusCacheEntry entry = cachedDir->GetStatusForMember(path, bRecursive, bFetch); { AutoLocker lock(m_critSec); m_mostRecentStatus = entry; return m_mostRecentStatus; } } } else { // path is blocked for some reason: return the cached status if we have one // we do here only a cache search, absolutely no disk access is allowed! CCachedDirectory::ItDir itMap = m_directoryCache.find(path.GetDirectory()); if ((itMap != m_directoryCache.end())&&(itMap->second)) { if (path.IsDirectory()) { CStatusCacheEntry entry = itMap->second->GetOwnStatus(false); AutoLocker lock(m_critSec); m_mostRecentStatus = entry; return m_mostRecentStatus; } else { // We've found this directory in the cache CCachedDirectory * cachedDir = itMap->second; CStatusCacheEntry entry = cachedDir->GetCacheStatusForMember(path); { AutoLocker lock(m_critSec); m_mostRecentStatus = entry; return m_mostRecentStatus; } } } } AutoLocker lock(m_critSec); ATLTRACE(_T("ignored no good path %s\n"), path.GetWinPath()); m_mostRecentStatus = CStatusCacheEntry(); if (m_shellCache.ShowExcludedAsNormal() && path.IsDirectory() && m_shellCache.HasGITAdminDir(path.GetWinPath(), true)) { ATLTRACE(_T("force status %s\n"), path.GetWinPath()); m_mostRecentStatus.ForceStatus(git_wc_status_normal); } return m_mostRecentStatus; }
bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right) { return left.GetDirectory().IsEquivalentTo(right.GetDirectory()); }
DWORD WINAPI CommandThread(LPVOID lpvParam) { ATLTRACE("CommandThread started\n"); DWORD cbBytesRead; BOOL fSuccess; CAutoFile hPipe; // The thread's parameter is a handle to a pipe instance. hPipe = lpvParam; while (bRun) { // Read client requests from the pipe. TGITCacheCommand command; fSuccess = ReadFile( hPipe, // handle to pipe &command, // buffer to receive data sizeof(command), // size of buffer &cbBytesRead, // number of bytes read NULL); // not overlapped I/O if (! fSuccess || cbBytesRead == 0) { DisconnectNamedPipe(hPipe); ATLTRACE("Command thread exited\n"); return 1; } switch (command.command) { case TGITCACHECOMMAND_END: FlushFileBuffers(hPipe); DisconnectNamedPipe(hPipe); ATLTRACE("Command thread exited\n"); return 0; case TGITCACHECOMMAND_CRAWL: { CTGitPath changedpath; changedpath.SetFromWin(command.path, true); // remove the path from our cache - that will 'invalidate' it. { CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard()); CGitStatusCache::Instance().RemoveCacheForPath(changedpath); } CGitStatusCache::Instance().AddFolderForCrawling(changedpath.GetDirectory()); } break; case TGITCACHECOMMAND_REFRESHALL: { CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard()); CGitStatusCache::Instance().Refresh(); } break; case TGITCACHECOMMAND_RELEASE: { CTGitPath changedpath; changedpath.SetFromWin(command.path, true); ATLTRACE(_T("release handle for path %s\n"), changedpath.GetWinPath()); CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard()); CGitStatusCache::Instance().CloseWatcherHandles(changedpath); CGitStatusCache::Instance().RemoveCacheForPath(changedpath); } break; case TGITCACHECOMMAND_BLOCK: { CTGitPath changedpath; changedpath.SetFromWin(command.path); ATLTRACE(_T("block path %s\n"), changedpath.GetWinPath()); CGitStatusCache::Instance().BlockPath(changedpath); } break; case TGITCACHECOMMAND_UNBLOCK: { CTGitPath changedpath; changedpath.SetFromWin(command.path); ATLTRACE(_T("block path %s\n"), changedpath.GetWinPath()); CGitStatusCache::Instance().UnBlockPath(changedpath); } break; } } // Flush the pipe to allow the client to read the pipe's contents // before disconnecting. Then disconnect the pipe, and close the // handle to this pipe instance. FlushFileBuffers(hPipe); DisconnectNamedPipe(hPipe); ATLTRACE("Command thread exited\n"); return 0; }
void CFolderCrawler::WorkerThread() { HANDLE hWaitHandles[2]; hWaitHandles[0] = m_hTerminationEvent; hWaitHandles[1] = m_hWakeEvent; CTGitPath workingPath; bool bFirstRunAfterWakeup = false; DWORD currentTicks = 0; // Quick check if we're on Vista OSVERSIONINFOEX inf; SecureZeroMemory(&inf, sizeof(OSVERSIONINFOEX)); inf.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); GetVersionEx((OSVERSIONINFO *)&inf); WORD fullver = MAKEWORD(inf.dwMinorVersion, inf.dwMajorVersion); for(;;) { bool bRecursive = !!(DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\RecursiveOverlay"), TRUE); if (fullver >= 0x0600) { SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END); } DWORD waitResult = WaitForMultipleObjects(_countof(hWaitHandles), hWaitHandles, FALSE, INFINITE); // exit event/working loop if the first event (m_hTerminationEvent) // has been signaled or if one of the events has been abandoned // (i.e. ~CFolderCrawler() is being executed) if(m_bRun == false || waitResult == WAIT_OBJECT_0 || waitResult == WAIT_ABANDONED_0 || waitResult == WAIT_ABANDONED_0+1) { // Termination event break; } if (fullver >= 0x0600) { SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN); } // If we get here, we've been woken up by something being added to the queue. // However, it's important that we don't do our crawling while // the shell is still asking for items bFirstRunAfterWakeup = true; for(;;) { if (!m_bRun) break; // Any locks today? if (CGitStatusCache::Instance().m_bClearMemory) { CGitStatusCache::Instance().WaitToWrite(); CGitStatusCache::Instance().ClearCache(); CGitStatusCache::Instance().Done(); CGitStatusCache::Instance().m_bClearMemory = false; } if(m_lCrawlInhibitSet > 0) { // We're in crawl hold-off ATLTRACE("Crawl hold-off\n"); Sleep(50); continue; } if (bFirstRunAfterWakeup) { Sleep(20); ATLTRACE("Crawl bFirstRunAfterWakeup\n"); bFirstRunAfterWakeup = false; continue; } if ((m_blockReleasesAt < GetTickCount())&&(!m_blockedPath.IsEmpty())) { ATLTRACE(_T("Crawl stop blocking path %s\n"), m_blockedPath.GetWinPath()); m_blockedPath.Reset(); } if ((m_foldersToUpdate.empty())&&(m_pathsToUpdate.empty())) { // Nothing left to do break; } currentTicks = GetTickCount(); if (!m_pathsToUpdate.empty()) { { AutoLocker lock(m_critSec); m_bPathsAddedSinceLastCrawl = false; workingPath = m_pathsToUpdate.front(); //m_pathsToUpdateUnique.erase (workingPath); m_pathsToUpdate.pop_front(); if ((DWORD(workingPath.GetCustomData()) >= currentTicks) || ((!m_blockedPath.IsEmpty())&&(m_blockedPath.IsAncestorOf(workingPath)))) { // move the path to the end of the list //m_pathsToUpdateUnique.insert (workingPath); m_pathsToUpdate.push_back(workingPath); if (m_pathsToUpdate.size() < 3) Sleep(50); continue; } } // don't crawl paths that are excluded if (!CGitStatusCache::Instance().IsPathAllowed(workingPath)) continue; // check if the changed path is inside an .git folder CString projectroot; if ((workingPath.HasAdminDir(&projectroot)&&workingPath.IsDirectory()) || workingPath.IsAdminDir()) { // we don't crawl for paths changed in a tmp folder inside an .git folder. // Because we also get notifications for those even if we just ask for the status! // And changes there don't affect the file status at all, so it's safe // to ignore notifications on those paths. if (workingPath.IsAdminDir()) { // TODO: add git specific filters here. is there really any change besides index file in .git // that is relevant for overlays? /*CString lowerpath = workingPath.GetWinPathString(); lowerpath.MakeLower(); if (lowerpath.Find(_T("\\tmp\\"))>0) continue; if (lowerpath.Find(_T("\\tmp")) == (lowerpath.GetLength()-4)) continue; if (lowerpath.Find(_T("\\log"))>0) continue;*/ // Here's a little problem: // the lock file is also created for fetching the status // and not just when committing. // If we could find out why the lock file was changed // we could decide to crawl the folder again or not. // But for now, we have to crawl the parent folder // no matter what. //if (lowerpath.Find(_T("\\lock"))>0) // continue; } else if (!workingPath.Exists()) { CGitStatusCache::Instance().WaitToWrite(); CGitStatusCache::Instance().RemoveCacheForPath(workingPath); CGitStatusCache::Instance().Done(); continue; } do { workingPath = workingPath.GetContainingDirectory(); } while(workingPath.IsAdminDir()); ATLTRACE(_T("Invalidating and refreshing folder: %s\n"), workingPath.GetWinPath()); { AutoLocker print(critSec); _stprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _T("Invalidating and refreshing folder: %s"), workingPath.GetWinPath()); nCurrentCrawledpathIndex++; if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS) nCurrentCrawledpathIndex = 0; } InvalidateRect(hWnd, NULL, FALSE); CGitStatusCache::Instance().WaitToRead(); // Invalidate the cache of this folder, to make sure its status is fetched again. CCachedDirectory * pCachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(workingPath); if (pCachedDir) { git_wc_status_kind status = pCachedDir->GetCurrentFullStatus(); pCachedDir->Invalidate(); if (workingPath.Exists()) { pCachedDir->RefreshStatus(bRecursive); // if the previous status wasn't normal and now it is, then // send a notification too. // We do this here because GetCurrentFullStatus() doesn't send // notifications for 'normal' status - if it would, we'd get tons // of notifications when crawling a working copy not yet in the cache. if ((status != git_wc_status_normal)&&(pCachedDir->GetCurrentFullStatus() != status)) { CGitStatusCache::Instance().UpdateShell(workingPath); ATLTRACE(_T("shell update in crawler for %s\n"), workingPath.GetWinPath()); } } else { CGitStatusCache::Instance().Done(); CGitStatusCache::Instance().WaitToWrite(); CGitStatusCache::Instance().RemoveCacheForPath(workingPath); } } CGitStatusCache::Instance().Done(); //In case that svn_client_stat() modified a file and we got //a notification about that in the directory watcher, //remove that here again - this is to prevent an endless loop AutoLocker lock(m_critSec); m_pathsToUpdate.erase(std::remove(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), workingPath), m_pathsToUpdate.end()); } else if (workingPath.HasAdminDir()) { if (!workingPath.Exists()) { CGitStatusCache::Instance().WaitToWrite(); CGitStatusCache::Instance().RemoveCacheForPath(workingPath); CGitStatusCache::Instance().Done(); continue; } if (!workingPath.Exists()) continue; ATLTRACE(_T("Updating path: %s\n"), workingPath.GetWinPath()); { AutoLocker print(critSec); _stprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _T("Updating path: %s"), workingPath.GetWinPath()); nCurrentCrawledpathIndex++; if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS) nCurrentCrawledpathIndex = 0; } InvalidateRect(hWnd, NULL, FALSE); // HasAdminDir() already checks if the path points to a dir DWORD flags = TGITCACHE_FLAGS_FOLDERISKNOWN; flags |= (workingPath.IsDirectory() ? TGITCACHE_FLAGS_ISFOLDER : 0); flags |= (bRecursive ? TGITCACHE_FLAGS_RECUSIVE_STATUS : 0); CGitStatusCache::Instance().WaitToRead(); // Invalidate the cache of folders manually. The cache of files is invalidated // automatically if the status is asked for it and the file times don't match // anymore, so we don't need to manually invalidate those. if (workingPath.IsDirectory()) { CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(workingPath); if (cachedDir) cachedDir->Invalidate(); } CStatusCacheEntry ce = CGitStatusCache::Instance().GetStatusForPath(workingPath, flags); if (ce.GetEffectiveStatus() > git_wc_status_unversioned) { CGitStatusCache::Instance().UpdateShell(workingPath); ATLTRACE(_T("shell update in folder crawler for %s\n"), workingPath.GetWinPath()); } CGitStatusCache::Instance().Done(); AutoLocker lock(m_critSec); m_pathsToUpdate.erase(std::remove(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), workingPath), m_pathsToUpdate.end()); } else { if (!workingPath.Exists()) { CGitStatusCache::Instance().WaitToWrite(); CGitStatusCache::Instance().RemoveCacheForPath(workingPath); CGitStatusCache::Instance().Done(); } } } else if (!m_foldersToUpdate.empty()) { { AutoLocker lock(m_critSec); m_bItemsAddedSinceLastCrawl = false; // create a new CTSVNPath object to make sure the cached flags are requested again. // without this, a missing file/folder is still treated as missing even if it is available // now when crawling. CTGitPath& folderToUpdate = m_foldersToUpdate.front(); workingPath = CTGitPath(folderToUpdate.GetWinPath()); workingPath.SetCustomData(folderToUpdate.GetCustomData()); m_foldersToUpdate.pop_front(); if ((DWORD(workingPath.GetCustomData()) >= currentTicks) || ((!m_blockedPath.IsEmpty())&&(m_blockedPath.IsAncestorOf(workingPath)))) { // move the path to the end of the list m_foldersToUpdate.push_back (workingPath); if (m_foldersToUpdate.size() < 3) Sleep(50); continue; } } if (DWORD(workingPath.GetCustomData()) >= currentTicks) { Sleep(50); continue; } if ((!m_blockedPath.IsEmpty())&&(m_blockedPath.IsAncestorOf(workingPath))) continue; if (!CGitStatusCache::Instance().IsPathAllowed(workingPath)) continue; ATLTRACE(_T("Crawling folder: %s\n"), workingPath.GetWinPath()); { AutoLocker print(critSec); _stprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _T("Crawling folder: %s"), workingPath.GetWinPath()); nCurrentCrawledpathIndex++; if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS) nCurrentCrawledpathIndex = 0; } InvalidateRect(hWnd, NULL, FALSE); CGitStatusCache::Instance().WaitToRead(); // Now, we need to visit this folder, to make sure that we know its 'most important' status CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(workingPath.GetDirectory()); // check if the path is monitored by the watcher. If it isn't, then we have to invalidate the cache // for that path and add it to the watcher. if (!CGitStatusCache::Instance().IsPathWatched(workingPath)) { if (workingPath.HasAdminDir()) { ATLTRACE(_T("Add watch path %s\n"), workingPath.GetWinPath()); CGitStatusCache::Instance().AddPathToWatch(workingPath); } if (cachedDir) cachedDir->Invalidate(); else { CGitStatusCache::Instance().Done(); CGitStatusCache::Instance().WaitToWrite(); CGitStatusCache::Instance().RemoveCacheForPath(workingPath); } } if (cachedDir) cachedDir->RefreshStatus(bRecursive); #if 0 // While refreshing the status, we could get another crawl request for the same folder. // This can happen if the crawled folder has a lower status than one of the child folders // (recursively). To avoid double crawlings, remove such a crawl request here AutoLocker lock(m_critSec); if (m_bItemsAddedSinceLastCrawl) { if (m_foldersToUpdate.back().IsEquivalentToWithoutCase(workingPath)) { m_foldersToUpdate.pop_back(); m_bItemsAddedSinceLastCrawl = false; } } #endif CGitStatusCache::Instance().Done(); } } } _endthread(); }
void CFolderCrawler::WorkerThread() { HANDLE hWaitHandles[2]; hWaitHandles[0] = m_hTerminationEvent; hWaitHandles[1] = m_hWakeEvent; CTGitPath workingPath; ULONGLONG currentTicks = 0; for(;;) { bool bRecursive = !!(DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\RecursiveOverlay", TRUE); SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END); DWORD waitResult = WaitForMultipleObjects(_countof(hWaitHandles), hWaitHandles, FALSE, INFINITE); // exit event/working loop if the first event (m_hTerminationEvent) // has been signaled or if one of the events has been abandoned // (i.e. ~CFolderCrawler() is being executed) if(m_bRun == false || waitResult == WAIT_OBJECT_0 || waitResult == WAIT_ABANDONED_0 || waitResult == WAIT_ABANDONED_0+1) { // Termination event break; } SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN); // If we get here, we've been woken up by something being added to the queue. // However, it's important that we don't do our crawling while // the shell is still asking for items bool bFirstRunAfterWakeup = true; for(;;) { if (!m_bRun) break; // Any locks today? if (CGitStatusCache::Instance().m_bClearMemory) { CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard()); CGitStatusCache::Instance().ClearCache(); CGitStatusCache::Instance().m_bClearMemory = false; } if(m_lCrawlInhibitSet > 0) { // We're in crawl hold-off CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Crawl hold-off\n"); Sleep(50); continue; } if (bFirstRunAfterWakeup) { Sleep(20); CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Crawl bFirstRunAfterWakeup\n"); bFirstRunAfterWakeup = false; continue; } if ((m_blockReleasesAt < GetTickCount64()) && (!m_blockedPath.IsEmpty())) { CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Crawl stop blocking path %s\n", m_blockedPath.GetWinPath()); m_blockedPath.Reset(); } CGitStatusCache::Instance().RemoveTimedoutBlocks(); while (!m_pathsToRelease.empty()) { AutoLocker lock(m_critSec); CTGitPath path = m_pathsToRelease.Pop(); GitStatus::ReleasePath(path.GetWinPathString()); } if (m_foldersToUpdate.empty() && m_pathsToUpdate.empty()) { // Nothing left to do break; } currentTicks = GetTickCount64(); if (!m_pathsToUpdate.empty()) { { AutoLocker lock(m_critSec); m_bPathsAddedSinceLastCrawl = false; workingPath = m_pathsToUpdate.Pop(); if ((!m_blockedPath.IsEmpty()) && (m_blockedPath.IsAncestorOf(workingPath))) { // move the path to the end of the list m_pathsToUpdate.Push(workingPath); if (m_pathsToUpdate.size() < 3) Sleep(50); continue; } } // don't crawl paths that are excluded if (!CGitStatusCache::Instance().IsPathAllowed(workingPath)) continue; // check if the changed path is inside an .git folder CString projectroot; if ((workingPath.HasAdminDir(&projectroot)&&workingPath.IsDirectory()) || workingPath.IsAdminDir()) { // we don't crawl for paths changed in a tmp folder inside an .git folder. // Because we also get notifications for those even if we just ask for the status! // And changes there don't affect the file status at all, so it's safe // to ignore notifications on those paths. if (workingPath.IsAdminDir()) { // TODO: add git specific filters here. is there really any change besides index file in .git // that is relevant for overlays? /*CString lowerpath = workingPath.GetWinPathString(); lowerpath.MakeLower(); if (lowerpath.Find(L"\\tmp\\") > 0) continue; if (CStringUtils::EndsWith(lowerpath, L"\\tmp")) continue; if (lowerpath.Find(L"\\log") > 0) continue;*/ // Here's a little problem: // the lock file is also created for fetching the status // and not just when committing. // If we could find out why the lock file was changed // we could decide to crawl the folder again or not. // But for now, we have to crawl the parent folder // no matter what. //if (lowerpath.Find(L"\\lock") > 0) // continue; // only go back to wc root if we are in .git-dir do { workingPath = workingPath.GetContainingDirectory(); } while(workingPath.IsAdminDir()); } else if (!workingPath.Exists()) { CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard()); CGitStatusCache::Instance().RemoveCacheForPath(workingPath); continue; } if (!CGitStatusCache::Instance().IsPathGood(workingPath)) { AutoLocker lock(m_critSec); // move the path, the root of the repository, to the end of the list if (projectroot.IsEmpty()) m_pathsToUpdate.Push(workingPath); else m_pathsToUpdate.Push(CTGitPath(projectroot)); if (m_pathsToUpdate.size() < 3) Sleep(50); continue; } CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Invalidating and refreshing folder: %s\n", workingPath.GetWinPath()); { AutoLocker print(critSec); _sntprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _TRUNCATE, L"Invalidating and refreshing folder: %s", workingPath.GetWinPath()); ++nCurrentCrawledpathIndex; if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS) nCurrentCrawledpathIndex = 0; } InvalidateRect(hWndHidden, nullptr, FALSE); { CAutoReadLock readLock(CGitStatusCache::Instance().GetGuard()); // Invalidate the cache of this folder, to make sure its status is fetched again. CCachedDirectory * pCachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(workingPath); if (pCachedDir) { git_wc_status_kind status = pCachedDir->GetCurrentFullStatus(); pCachedDir->Invalidate(); if (workingPath.Exists()) { pCachedDir->RefreshStatus(bRecursive); // if the previous status wasn't normal and now it is, then // send a notification too. // We do this here because GetCurrentFullStatus() doesn't send // notifications for 'normal' status - if it would, we'd get tons // of notifications when crawling a working copy not yet in the cache. if ((status != git_wc_status_normal) && (pCachedDir->GetCurrentFullStatus() != status)) { CGitStatusCache::Instance().UpdateShell(workingPath); CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": shell update in crawler for %s\n", workingPath.GetWinPath()); } } else { CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard()); CGitStatusCache::Instance().RemoveCacheForPath(workingPath); } } } //In case that svn_client_stat() modified a file and we got //a notification about that in the directory watcher, //remove that here again - this is to prevent an endless loop AutoLocker lock(m_critSec); m_pathsToUpdate.erase(workingPath); } else if (workingPath.HasAdminDir()) { if (!workingPath.Exists()) { CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard()); CGitStatusCache::Instance().RemoveCacheForPath(workingPath); if (!workingPath.GetContainingDirectory().Exists()) continue; else workingPath = workingPath.GetContainingDirectory(); } CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Updating path: %s\n", workingPath.GetWinPath()); { AutoLocker print(critSec); _sntprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _TRUNCATE, L"Updating path: %s", workingPath.GetWinPath()); ++nCurrentCrawledpathIndex; if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS) nCurrentCrawledpathIndex = 0; } InvalidateRect(hWndHidden, nullptr, FALSE); { CAutoReadLock readLock(CGitStatusCache::Instance().GetGuard()); // Invalidate the cache of folders manually. The cache of files is invalidated // automatically if the status is asked for it and the file times don't match // anymore, so we don't need to manually invalidate those. CCachedDirectory* cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(workingPath.GetDirectory()); if (cachedDir && workingPath.IsDirectory()) cachedDir->Invalidate(); if (cachedDir && cachedDir->GetStatusForMember(workingPath, bRecursive).GetEffectiveStatus() > git_wc_status_unversioned) CGitStatusCache::Instance().UpdateShell(workingPath); } AutoLocker lock(m_critSec); m_pathsToUpdate.erase(workingPath); } else { if (!workingPath.Exists()) { CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard()); CGitStatusCache::Instance().RemoveCacheForPath(workingPath); } } } if (!m_foldersToUpdate.empty()) { { AutoLocker lock(m_critSec); m_bItemsAddedSinceLastCrawl = false; // create a new CTGitPath object to make sure the cached flags are requested again. // without this, a missing file/folder is still treated as missing even if it is available // now when crawling. workingPath = CTGitPath(m_foldersToUpdate.Pop().GetWinPath()); if ((!m_blockedPath.IsEmpty())&&(m_blockedPath.IsAncestorOf(workingPath))) { // move the path to the end of the list m_foldersToUpdate.Push(workingPath); if (m_foldersToUpdate.size() < 3) Sleep(50); continue; } } if ((!m_blockedPath.IsEmpty())&&(m_blockedPath.IsAncestorOf(workingPath))) continue; if (!CGitStatusCache::Instance().IsPathAllowed(workingPath)) continue; if (!CGitStatusCache::Instance().IsPathGood(workingPath)) continue; CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Crawling folder: %s\n", workingPath.GetWinPath()); { AutoLocker print(critSec); _sntprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _TRUNCATE, L"Crawling folder: %s", workingPath.GetWinPath()); ++nCurrentCrawledpathIndex; if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS) nCurrentCrawledpathIndex = 0; } InvalidateRect(hWndHidden, nullptr, FALSE); { CAutoReadLock readLock(CGitStatusCache::Instance().GetGuard()); // Now, we need to visit this folder, to make sure that we know its 'most important' status CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(workingPath.GetDirectory()); // check if the path is monitored by the watcher. If it isn't, then we have to invalidate the cache // for that path and add it to the watcher. if (!CGitStatusCache::Instance().IsPathWatched(workingPath)) { if (workingPath.HasAdminDir()) { CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Add watch path %s\n", workingPath.GetWinPath()); CGitStatusCache::Instance().AddPathToWatch(workingPath); } if (cachedDir) cachedDir->Invalidate(); else { CAutoWriteLock writeLock(CGitStatusCache::Instance().GetGuard()); CGitStatusCache::Instance().RemoveCacheForPath(workingPath); // now cacheDir is invalid because it got deleted in the RemoveCacheForPath() call above. cachedDir = nullptr; } } if (cachedDir) cachedDir->RefreshStatus(bRecursive); } // While refreshing the status, we could get another crawl request for the same folder. // This can happen if the crawled folder has a lower status than one of the child folders // (recursively). To avoid double crawlings, remove such a crawl request here AutoLocker lock(m_critSec); if (m_bItemsAddedSinceLastCrawl) { m_foldersToUpdate.erase(workingPath); } } } } _endthread(); }
void CShellUpdater::UpdateShell() { // Tell the shell extension to purge its cache ATLTRACE("Setting cache invalidation event %d\n", GetTickCount()); SetEvent(m_hInvalidationEvent); // We use the SVN 'notify' call-back to add items to the list // Because this might call-back more than once per file (for example, when committing) // it's possible that there may be duplicates in the list. // There's no point asking the shell to do more than it has to, so we remove the duplicates before // passing the list on m_pathsForUpdating.RemoveDuplicates(); // if we use the external cache, we tell the cache directly that something // has changed, without the detour via the shell. HANDLE hPipe = CreateFile( GetCacheCommandPipeName(), // pipe name GENERIC_READ | // read and write access GENERIC_WRITE, 0, // no sharing NULL, // default security attributes OPEN_EXISTING, // opens existing pipe FILE_FLAG_OVERLAPPED, // default attributes NULL); // no template file if (hPipe != INVALID_HANDLE_VALUE) { // The pipe connected; change to message-read mode. DWORD dwMode; dwMode = PIPE_READMODE_MESSAGE; if(SetNamedPipeHandleState( hPipe, // pipe handle &dwMode, // new pipe mode NULL, // don't set maximum bytes NULL)) // don't set maximum time { CTGitPath path; for(int nPath = 0; nPath < m_pathsForUpdating.GetCount(); nPath++) { path.SetFromWin(g_Git.m_CurrentDir+_T("\\")+m_pathsForUpdating[nPath].GetWinPathString()); ATLTRACE(_T("Cache Item Update for %s (%d)\n"), path.GetWinPathString(), GetTickCount()); if (!path.IsDirectory()) { // send notifications to the shell for changed files - folders are updated by the cache itself. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, path.GetWinPath(), NULL); } DWORD cbWritten; TSVNCacheCommand cmd; cmd.command = TSVNCACHECOMMAND_CRAWL; wcsncpy_s(cmd.path, MAX_PATH+1, path.GetDirectory().GetWinPath(), MAX_PATH); BOOL fSuccess = WriteFile( hPipe, // handle to pipe &cmd, // buffer to write from sizeof(cmd), // number of bytes to write &cbWritten, // number of bytes written NULL); // not overlapped I/O if (! fSuccess || sizeof(cmd) != cbWritten) { DisconnectNamedPipe(hPipe); CloseHandle(hPipe); hPipe = INVALID_HANDLE_VALUE; break; } } if (hPipe != INVALID_HANDLE_VALUE) { // now tell the cache we don't need it's command thread anymore DWORD cbWritten; TSVNCacheCommand cmd; cmd.command = TSVNCACHECOMMAND_END; WriteFile( hPipe, // handle to pipe &cmd, // buffer to write from sizeof(cmd), // number of bytes to write &cbWritten, // number of bytes written NULL); // not overlapped I/O DisconnectNamedPipe(hPipe); CloseHandle(hPipe); hPipe = INVALID_HANDLE_VALUE; } } else { ATLTRACE("SetNamedPipeHandleState failed"); CloseHandle(hPipe); } } }
void CShellUpdater::UpdateShell() { // Tell the shell extension to purge its cache CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Setting cache invalidation event %I64u\n", GetTickCount64()); SetEvent(m_hInvalidationEvent); // We use the SVN 'notify' call-back to add items to the list // Because this might call-back more than once per file (for example, when committing) // it's possible that there may be duplicates in the list. // There's no point asking the shell to do more than it has to, so we remove the duplicates before // passing the list on m_pathsForUpdating.RemoveDuplicates(); // if we use the external cache, we tell the cache directly that something // has changed, without the detour via the shell. CAutoFile hPipe = CreateFile( GetCacheCommandPipeName(), // pipe name GENERIC_READ | // read and write access GENERIC_WRITE, 0, // no sharing nullptr, // default security attributes OPEN_EXISTING, // opens existing pipe FILE_FLAG_OVERLAPPED, // default attributes nullptr); // no template file if (!hPipe) return; // The pipe connected; change to message-read mode. DWORD dwMode = PIPE_READMODE_MESSAGE; if (!SetNamedPipeHandleState( hPipe, // pipe handle &dwMode, // new pipe mode nullptr, // don't set maximum bytes nullptr)) // don't set maximum time { CTraceToOutputDebugString::Instance()(__FUNCTION__ ": SetNamedPipeHandleState failed"); return; } CTGitPath path; for (int nPath = 0; nPath < m_pathsForUpdating.GetCount(); ++nPath) { path.SetFromWin(g_Git.CombinePath(m_pathsForUpdating[nPath])); CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Cache Item Update for %s (%I64u)\n", (LPCTSTR)path.GetWinPathString(), GetTickCount64()); if (!path.IsDirectory()) { // send notifications to the shell for changed files - folders are updated by the cache itself. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, path.GetWinPath(), nullptr); } DWORD cbWritten; TGITCacheCommand cmd; cmd.command = TGITCACHECOMMAND_CRAWL; wcsncpy_s(cmd.path, path.GetDirectory().GetWinPath(), _countof(cmd.path) - 1); BOOL fSuccess = WriteFile( hPipe, // handle to pipe &cmd, // buffer to write from sizeof(cmd), // number of bytes to write &cbWritten, // number of bytes written nullptr); // not overlapped I/O if (!fSuccess || sizeof(cmd) != cbWritten) { DisconnectNamedPipe(hPipe); return; } } // now tell the cache we don't need it's command thread anymore DWORD cbWritten; TGITCacheCommand cmd; cmd.command = TGITCACHECOMMAND_END; WriteFile( hPipe, // handle to pipe &cmd, // buffer to write from sizeof(cmd), // number of bytes to write &cbWritten, // number of bytes written nullptr); // not overlapped I/O DisconnectNamedPipe(hPipe); }
void CFileDiffDlg::OnContextMenu(CWnd* pWnd, CPoint point) { if ((pWnd==0)||(pWnd != &m_cFileList)) return; if (m_cFileList.GetSelectedCount() == 0) return; // if the context menu is invoked through the keyboard, we have to use // a calculated position on where to anchor the menu on if ((point.x == -1) && (point.y == -1)) { CRect rect; m_cFileList.GetItemRect(m_cFileList.GetSelectionMark(), &rect, LVIR_LABEL); m_cFileList.ClientToScreen(&rect); point = rect.CenterPoint(); } CIconMenu popup; if (popup.CreatePopupMenu()) { popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF); popup.AppendMenuIcon(ID_BLAME, IDS_FILEDIFF_POPBLAME, IDI_BLAME); popup.AppendMenuIcon(ID_LOG, IDS_FILEDIFF_LOG, IDI_LOG); popup.AppendMenu(MF_SEPARATOR, NULL); popup.AppendMenuIcon(ID_EXPORT, IDS_FILEDIFF_POPEXPORT, IDI_EXPORT); popup.AppendMenu(MF_SEPARATOR, NULL); popup.AppendMenuIcon(ID_SAVEAS, IDS_FILEDIFF_POPSAVELIST, IDI_SAVEAS); popup.AppendMenuIcon(ID_CLIPBOARD_PATH, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP); popup.AppendMenuIcon(ID_CLIPBOARD_ALL, IDS_STATUSLIST_CONTEXT_COPYEXT, IDI_COPYCLIP); int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0); m_bCancelled = false; switch (cmd) { case ID_COMPARE: { POSITION pos = m_cFileList.GetFirstSelectedItemPosition(); while (pos) { int index = m_cFileList.GetNextSelectedItem(pos); DoDiff(index, false); } } break; case ID_BLAME: { POSITION pos = m_cFileList.GetFirstSelectedItemPosition(); while (pos) { int index = m_cFileList.GetNextSelectedItem(pos); CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetWinPathString(), m_rev1.m_CommitHash.ToString()); } } break; case ID_LOG: { POSITION pos = m_cFileList.GetFirstSelectedItemPosition(); while (pos) { int index = m_cFileList.GetNextSelectedItem(pos); CString cmd = _T("/command:log"); cmd += _T(" /path:\"")+m_arFilteredList[index]->GetWinPathString()+_T("\" "); cmd += _T(" /endrev:")+m_rev1.m_CommitHash.ToString(); CAppUtils::RunTortoiseProc(cmd); } } break; case ID_SAVEAS: { if (m_cFileList.GetSelectedCount() > 0) { CString temp; CTGitPath savePath; CString pathSave; if (!CAppUtils::FileOpenSave(pathSave, NULL, IDS_REPOBROWSE_SAVEAS, IDS_COMMONFILEFILTER, false, m_hWnd)) { break; } savePath = CTGitPath(pathSave); // now open the selected file for writing try { CStdioFile file(savePath.GetWinPathString(), CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate); // temp.Format(IDS_FILEDIFF_CHANGEDLISTINTRO, (LPCTSTR)m_path1.GetGitPathString(), (LPCTSTR)m_rev1.ToString(), (LPCTSTR)m_path2.GetGitPathString(), (LPCTSTR)m_rev2.ToString()); file.WriteString(temp + _T("\n")); POSITION pos = m_cFileList.GetFirstSelectedItemPosition(); while (pos) { int index = m_cFileList.GetNextSelectedItem(pos); CTGitPath* fd = m_arFilteredList[index]; file.WriteString(fd->GetGitPathString()); file.WriteString(_T("\n")); } file.Close(); } catch (CFileException* pE) { pE->ReportError(); } } } break; case ID_CLIPBOARD_PATH: { CopySelectionToClipboard(); } break; case ID_CLIPBOARD_ALL: { CopySelectionToClipboard(TRUE); } break; case ID_EXPORT: { // export all changed files to a folder CBrowseFolder browseFolder; browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS; if (browseFolder.Show(GetSafeHwnd(), m_strExportDir) == CBrowseFolder::OK) { POSITION pos = m_cFileList.GetFirstSelectedItemPosition(); while (pos) { int index = m_cFileList.GetNextSelectedItem(pos); CTGitPath* fd = m_arFilteredList[index]; // we cannot export directories or folders if (fd->m_Action == CTGitPath::LOGACTIONS_DELETED || fd->IsDirectory()) continue; CAppUtils::CreateMultipleDirectory(m_strExportDir + _T("\\") + fd->GetDirectory().GetWinPathString()); CString filename = m_strExportDir + _T("\\") + fd->GetWinPathString(); if(m_rev1.m_CommitHash.ToString() == GIT_REV_ZERO) { if(!CopyFile(g_Git.m_CurrentDir + _T("\\") + fd->GetWinPath(), filename, false)) { MessageBox(CFormatMessageWrapper(), _T("TortoiseGit"), MB_OK | MB_ICONERROR); return; } } else { if(g_Git.GetOneFile(m_rev1.m_CommitHash, *fd, filename)) { CString out; out.Format(_T("Fail checkout one file %s;%s"), m_rev1.m_CommitHash.ToString(), fd->GetWinPath()); CMessageBox::Show(NULL, out, _T("TortoiseGit"), MB_OK); return; } } } } } break; } } }