bool CLogFile::Open(const CTGitPath& logfile) { m_lines.clear(); m_logfile = logfile; if (!logfile.Exists()) { CPathUtils::MakeSureDirectoryPathExists(logfile.GetContainingDirectory().GetWinPath()); return true; } try { CString strLine; CStdioFile file; int retrycounter = 10; // try to open the file for about two seconds - some other TSVN process might be // writing to the file and we just wait a little for this to finish while (!file.Open(logfile.GetWinPath(), CFile::typeText | CFile::modeRead | CFile::shareDenyWrite) && retrycounter) { retrycounter--; Sleep(200); } if (retrycounter == 0) return false; while (file.ReadString(strLine)) { m_lines.push_back(strLine); } file.Close(); } catch (CFileException* pE) { TRACE("CFileException loading autolist regex file\n"); pE->Delete(); return false; } return true; }
bool DropMoveCommand::Execute() { CString droppath = parser.GetVal(_T("droptarget")); CString ProjectTop; if (!CTGitPath(droppath).HasAdminDir(&ProjectTop)) return FALSE; if (ProjectTop != g_Git.m_CurrentDir ) { CMessageBox::Show(NULL,_T("Target and source must be the same git repository"),_T("TortoiseGit"),MB_OK); return FALSE; } droppath = droppath.Right(droppath.GetLength()-ProjectTop.GetLength()-1); unsigned long count = 0; pathList.RemoveAdminPaths(); CString sNewName; if ((parser.HasKey(_T("rename")))&&(pathList.GetCount()==1)) { // ask for a new name of the source item do { CRenameDlg renDlg; renDlg.m_windowtitle.LoadString(IDS_PROC_MOVERENAME); renDlg.m_name = pathList[0].GetFileOrDirectoryName(); if (renDlg.DoModal() != IDOK) { return FALSE; } sNewName = renDlg.m_name; } while(sNewName.IsEmpty() || PathFileExists(droppath+_T("\\")+sNewName)); } CSysProgressDlg progress; if (progress.IsValid()) { progress.SetTitle(IDS_PROC_MOVING); progress.SetAnimation(IDR_MOVEANI); progress.SetTime(true); progress.ShowModeless(CWnd::FromHandle(hwndExplorer)); } for(int nPath = 0; nPath < pathList.GetCount(); nPath++) { CTGitPath destPath; if (sNewName.IsEmpty()) destPath = CTGitPath(droppath+_T("\\")+pathList[nPath].GetFileOrDirectoryName()); else destPath = CTGitPath(droppath+_T("\\")+sNewName); if (destPath.Exists()) { CString name = pathList[nPath].GetFileOrDirectoryName(); if (!sNewName.IsEmpty()) name = sNewName; progress.Stop(); CRenameDlg dlg; dlg.m_name = name; dlg.m_windowtitle.Format(IDS_PROC_NEWNAMEMOVE, (LPCTSTR)name); if (dlg.DoModal() != IDOK) { return FALSE; } destPath.SetFromWin(droppath+_T("\\")+dlg.m_name); } CString cmd,out; cmd.Format(_T("git.exe mv -- \"%s\" \"%s\""),pathList[nPath].GetGitPathString(),destPath.GetGitPathString()); if(g_Git.Run(cmd,&out,CP_ACP)) { if (CMessageBox::Show(hwndExplorer, out, _T("TortoiseGit"), MB_YESNO)==IDYES) { #if 0 if (!svn.Move(CTSVNPathList(pathList[nPath]), destPath, TRUE)) { CMessageBox::Show(hwndExplorer, svn.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR); return FALSE; //get out of here } CShellUpdater::Instance().AddPathForUpdate(destPath); #endif } else { //TRACE(_T("%s\n"), (LPCTSTR)svn.GetLastErrorMessage()); CMessageBox::Show(hwndExplorer, _T("Cancel"), _T("TortoiseGit"), MB_ICONERROR); return FALSE; //get out of here } } else CShellUpdater::Instance().AddPathForUpdate(destPath); count++; if (progress.IsValid()) { progress.FormatPathLine(1, IDS_PROC_MOVINGPROG, pathList[nPath].GetWinPath()); progress.FormatPathLine(2, IDS_PROC_CPYMVPROG2, destPath.GetWinPath()); progress.SetProgress(count, pathList.GetCount()); } if ((progress.IsValid())&&(progress.HasUserCancelled())) { CMessageBox::Show(hwndExplorer, IDS_SVN_USERCANCELLED, IDS_APPNAME, MB_ICONINFORMATION); return FALSE; } } return true; }
bool PasteMoveCommand::Execute() { CString sDroppath = parser.GetVal(_T("droptarget")); CTGitPath dropPath(sDroppath); if (dropPath.IsAdminDir()) return FALSE; if(!dropPath.HasAdminDir(&g_Git.m_CurrentDir)) return FALSE; GitStatus status; unsigned long count = 0; orgPathList.RemoveAdminPaths(); CString sNewName; CSysProgressDlg progress; progress.SetTitle(IDS_PROC_MOVING); progress.SetAnimation(IDR_MOVEANI); progress.SetTime(true); progress.ShowModeless(CWnd::FromHandle(hwndExplorer)); for (int nPath = 0; nPath < orgPathList.GetCount(); ++nPath) { CTGitPath destPath; if (sNewName.IsEmpty()) destPath = CTGitPath(sDroppath+_T("\\")+orgPathList[nPath].GetFileOrDirectoryName()); else destPath = CTGitPath(sDroppath+_T("\\")+sNewName); if (destPath.Exists()) { CString name = orgPathList[nPath].GetFileOrDirectoryName(); if (!sNewName.IsEmpty()) name = sNewName; progress.Stop(); CRenameDlg dlg; dlg.m_name = name; dlg.m_windowtitle.Format(IDS_PROC_NEWNAMEMOVE, (LPCTSTR)name); if (dlg.DoModal() != IDOK) { return FALSE; } destPath.SetFromWin(sDroppath+_T("\\")+dlg.m_name); } CString top; top.Empty(); orgPathList[nPath].HasAdminDir(&top); git_wc_status_kind s = status.GetAllStatus(orgPathList[nPath]); if ((s == git_wc_status_none)||(s == git_wc_status_unversioned)||(s == git_wc_status_ignored)||top != g_Git.m_CurrentDir) { // source file is unversioned: move the file to the target, then add it MoveFile(orgPathList[nPath].GetWinPath(), destPath.GetWinPath()); CString cmd,output; cmd.Format(_T("git.exe add -- \"%s\""),destPath.GetWinPath()); if (g_Git.Run(cmd, &output, CP_UTF8)) //if (!Git.Add(CTGitorgPathList(destPath), &props, Git_depth_infinity, true, false, true)) { TRACE(_T("%s\n"), output); CMessageBox::Show(hwndExplorer, output, _T("TortoiseGit"), MB_ICONERROR); return FALSE; //get out of here } CShellUpdater::Instance().AddPathForUpdate(destPath); } else { CString cmd,output; cmd.Format(_T("git.exe mv \"%s\" \"%s\""),orgPathList[nPath].GetGitPathString(),destPath.GetGitPathString()); if (g_Git.Run(cmd, &output, CP_UTF8)) //if (!Git.Move(CTGitorgPathList(orgPathList[nPath]), destPath, FALSE)) { #if 0 if (Git.Err && (Git.Err->apr_err == Git_ERR_UNVERSIONED_RESOURCE || Git.Err->apr_err == Git_ERR_CLIENT_MODIFIED)) { // file/folder seems to have local modifications. Ask the user if // a force is requested. CString temp = Git.GetLastErrorMessage(); CString sQuestion(MAKEINTRESOURCE(IDS_PROC_FORCEMOVE)); temp += _T("\n") + sQuestion; if (CMessageBox::Show(hwndExplorer, temp, _T("TortoiseGit"), MB_YESNO)==IDYES) { if (!Git.Move(CTGitPathList(pathList[nPath]), destPath, TRUE)) { CMessageBox::Show(hwndExplorer, Git.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR); return FALSE; //get out of here } CShellUpdater::Instance().AddPathForUpdate(destPath); } } else #endif { TRACE(_T("%s\n"), (LPCTSTR)output); CMessageBox::Show(hwndExplorer, output, _T("TortoiseGit"), MB_ICONERROR); return FALSE; //get out of here } } else CShellUpdater::Instance().AddPathForUpdate(destPath); } ++count; if (progress.IsValid()) { progress.FormatPathLine(1, IDS_PROC_MOVINGPROG, orgPathList[nPath].GetWinPath()); progress.FormatPathLine(2, IDS_PROC_CPYMVPROG2, destPath.GetWinPath()); progress.SetProgress(count, orgPathList.GetCount()); } if ((progress.IsValid())&&(progress.HasUserCancelled())) { CMessageBox::Show(hwndExplorer, IDS_USERCANCELLED, IDS_APPNAME, MB_ICONINFORMATION); return FALSE; } } return true; }
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(); }
BOOL CCachedDirectory::GetStatusCallback(const CString & path, git_wc_status_kind status,bool isDir, void *pUserData) { git_wc_status2_t _status; git_wc_status2_t *status2 = &_status; status2->prop_status = status2->text_status = status; CTGitPath gitPath; CString lowcasepath = path; lowcasepath.MakeLower(); gitPath.SetFromUnknown(lowcasepath); CCachedDirectory *pThis = CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath.GetContainingDirectory()); if(pThis == NULL) return FALSE; // if(status->entry) { if (isDir) { /*gitpath is directory*/ //if ( !gitPath.IsEquivalentToWithoutCase(pThis->m_directoryPath) ) { if (!gitPath.Exists()) { ATLTRACE(_T("Miss dir %s \n"), gitPath.GetWinPath()); pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_deleted); } if ( status < git_wc_status_normal) { if( ::PathFileExists(path+_T("\\.git"))) { // this is submodule ATLTRACE(_T("skip submodule %s\n"), path); return FALSE; } } if (pThis->m_bRecursive) { // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated //OutputDebugStringA("AddFolderCrawl: ");OutputDebugStringW(svnPath.GetWinPathString());OutputDebugStringA("\r\n"); if(status >= git_wc_status_normal) if(status != git_wc_status_missing) if(status != git_wc_status_deleted) CGitStatusCache::Instance().AddFolderForCrawling(gitPath); } // Make sure we know about this child directory // This initial status value is likely to be overwritten from below at some point git_wc_status_kind s = GitStatus::GetMoreImportant(status2->text_status, status2->prop_status); CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(gitPath); if (cdir) { // This child directory is already in our cache! // So ask this dir about its recursive status git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus()); AutoLocker lock(pThis->m_critSec); pThis->m_childDirectories[gitPath] = st; ATLTRACE(_T("call 1 Update dir %s %d\n"), gitPath.GetWinPath(), st); } else { AutoLocker lock(pThis->m_critSec); // the child directory is not in the cache. Create a new entry for it in the cache which is // initially 'unversioned'. But we added that directory to the crawling list above, which // means the cache will be updated soon. CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath); pThis->m_childDirectories[gitPath] = s; ATLTRACE(_T("call 2 Update dir %s %d\n"), gitPath.GetWinPath(), s); } } } else /* gitpath is file*/ { // Keep track of the most important status of all the files in this directory // Don't include subdirectories in this figure, because they need to provide their // own 'most important' value if (status2->text_status == git_wc_status_deleted || status2->text_status == git_wc_status_added) { // if just a file in a folder is deleted or added report back that the folder is modified and not deleted or added pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified); } else { pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->text_status); pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->prop_status); if (((status2->text_status == git_wc_status_unversioned)||(status2->text_status == git_wc_status_none)) &&(CGitStatusCache::Instance().IsUnversionedAsModified())) { // treat unversioned files as modified if (pThis->m_mostImportantFileStatus != git_wc_status_added) pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified); } } } } pThis->AddEntry(gitPath, status2); return FALSE; }