LRESULT CRevertDlg::OnFileDropped(WPARAM, LPARAM lParam) { BringWindowToTop(); SetForegroundWindow(); SetActiveWindow(); // if multiple files/folders are dropped // this handler is called for every single item // separately. // To avoid creating multiple refresh threads and // causing crashes, we only add the items to the // list control and start a timer. // When the timer expires, we start the refresh thread, // but only if it isn't already running - otherwise we // restart the timer. CTGitPath path; path.SetFromWin((LPCTSTR)lParam); if (!m_RevertList.HasPath(path)) { if (m_pathList.AreAllPathsFiles()) { m_pathList.AddPath(path); m_pathList.RemoveDuplicates(); } else { // if the path list contains folders, we have to check whether // our just (maybe) added path is a child of one of those. If it is // a child of a folder already in the list, we must not add it. Otherwise // that path could show up twice in the list. bool bHasParentInList = false; for (int i=0; i<m_pathList.GetCount(); ++i) { if (m_pathList[i].IsAncestorOf(path)) { bHasParentInList = true; break; } } if (!bHasParentInList) { m_pathList.AddPath(path); m_pathList.RemoveDuplicates(); } } } // Always start the timer, since the status of an existing item might have changed SetTimer(REFRESHTIMER, 200, NULL); ATLTRACE(_T("Item %s dropped, timer started\n"), path.GetWinPath()); return 0; }
CTGitPath CTempFiles::GetTempFilePath(bool bRemoveAtEnd, const CTGitPath& path /* = CTGitPath() */, const GitRev revision /* = GitRev() */) { DWORD len = ::GetTempPath(0, NULL); TCHAR * temppath = new TCHAR[len+1]; TCHAR * tempF = new TCHAR[len+50]; ::GetTempPath (len+1, temppath); CTGitPath tempfile; CString possibletempfile; if (path.IsEmpty()) { ::GetTempFileName (temppath, TEXT("git"), 0, tempF); tempfile = CTGitPath(tempF); } else { int i=0; do { if (!((GitRev&)revision).m_CommitHash.IsEmpty()) { possibletempfile.Format(_T("%s%s-rev%s.git%3.3x.tmp%s"), temppath, (LPCTSTR)path.GetFileOrDirectoryName(), (LPCTSTR)((GitRev&)revision).m_CommitHash.ToString().Left(7), i, (LPCTSTR)path.GetFileExtension()); } else { possibletempfile.Format(_T("%s%s.git%3.3x.tmp%s"), temppath, (LPCTSTR)path.GetFileOrDirectoryName(), i, (LPCTSTR)path.GetFileExtension()); } tempfile.SetFromWin(possibletempfile); i++; } while (PathFileExists(tempfile.GetWinPath())); } //now create the temp file, so that subsequent calls to GetTempFile() return //different filenames. CAutoFile hFile = CreateFile(tempfile.GetWinPath(), GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL); delete [] temppath; delete [] tempF; if (bRemoveAtEnd) m_TempFileList.AddPath(tempfile); return tempfile; }
CTGitPath CTempFiles::ConstructTempPath(const CTGitPath& path) { DWORD len = ::GetTempPath(0, nullptr); auto temppath = std::make_unique<TCHAR[]>(len + 1); auto tempF = std::make_unique<TCHAR[]>(len + 50); ::GetTempPath (len+1, temppath.get()); CTGitPath tempfile; CString possibletempfile; if (path.IsEmpty()) { ::GetTempFileName(temppath.get(), L"tsm", 0, tempF.get()); tempfile = CTGitPath (tempF.get()); } else { int i=0; do { // use the UI path, which does unescaping for urls CString filename = path.GetUIFileOrDirectoryName(); // remove illegal chars which could be present in urls filename.Remove('?'); filename.Remove('*'); filename.Remove('<'); filename.Remove('>'); filename.Remove('|'); filename.Remove('"'); // the inner loop assures that the resulting path is < MAX_PATH // if that's not possible without reducing the 'filename' to less than 5 chars, use a path // that's longer than MAX_PATH (in that case, we can't really do much to avoid longer paths) do { possibletempfile.Format(L"%s%s.tsm%3.3x.tmp%s", temppath.get(), (LPCTSTR)filename, i, (LPCTSTR)path.GetFileExtension()); tempfile.SetFromWin(possibletempfile); filename = filename.Left(filename.GetLength()-1); } while ( (filename.GetLength() > 4) && (tempfile.GetWinPathString().GetLength() >= MAX_PATH)); i++; } while (PathFileExists(tempfile.GetWinPath())); } // caller has to actually grab the file path return tempfile; }
CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTGitPath& path, bool bRecursive, bool bFetch /* = true */) { CString strCacheKey; bool bRequestForSelf = false; if(path.IsEquivalentToWithoutCase(m_directoryPath)) { bRequestForSelf = true; } //OutputDebugStringA("GetStatusForMember: ");OutputDebugStringW(path.GetWinPathString());OutputDebugStringA("\r\n"); // In all most circumstances, we ask for the status of a member of this directory. ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf); CString sProjectRoot; const BOOL bIsVersionedPath = path.HasAdminDir(&sProjectRoot); //If is not version control path if( !bIsVersionedPath) { ATLTRACE(_T("%s is not underversion control\n"), path.GetWinPath()); return CStatusCacheEntry(); } // We've not got this item in the cache - let's add it // We never bother asking SVN for the status of just one file, always for its containing directory if (g_GitAdminDir.IsAdminDirPath(path.GetWinPathString())) { // We're being asked for the status of an .git directory // It's not worth asking for this return CStatusCacheEntry(); } if(bFetch) { return GetStatusFromGit(path, sProjectRoot); } else { return GetStatusFromCache(path, bRecursive); } }
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; }
void CFolderCrawler::AddDirectoryForUpdate(const CTGitPath& path) { /* Index file changing*/ if( GitStatus::IsExistIndexLockFile((CString&)path.GetWinPathString())) return; if (!CGitStatusCache::Instance().IsPathGood(path)) return; { ATLTRACE(_T("AddDirectoryForUpdate %s\n"),path.GetWinPath()); AutoLocker lock(m_critSec); m_foldersToUpdate.Push(path); //ATLASSERT(path.IsDirectory() || !path.Exists()); // set this flag while we are sync'ed // with the worker thread m_bItemsAddedSinceLastCrawl = true; } //if (SetHoldoff()) SetEvent(m_hWakeEvent); }
bool CDirectoryWatcher::AddPath(const CTGitPath& path, bool bCloseInfoMap) { if (!CGitStatusCache::Instance().IsPathAllowed(path)) return false; if ((!blockedPath.IsEmpty())&&(blockedPath.IsAncestorOf(path))) { if (GetTickCount() < blockTickCount) { ATLTRACE(_T("Path %s prevented from being watched\n"), path.GetWinPath()); return false; } } if (path.GetWinPathString().Find(L":\\RECYCLER\\") >= 0) return false; if (path.GetWinPathString().Find(L":\\$Recycle.Bin\\") >= 0) return false; AutoLocker lock(m_critSec); for (int i=0; i<watchedPaths.GetCount(); ++i) { if (watchedPaths[i].IsAncestorOf(path)) return false; // already watched (recursively) } // now check if with the new path we might have a new root CTGitPath newroot; for (int i=0; i<watchedPaths.GetCount(); ++i) { const CString& watched = watchedPaths[i].GetWinPathString(); const CString& sPath = path.GetWinPathString(); int minlen = min(sPath.GetLength(), watched.GetLength()); int len = 0; for (len = 0; len < minlen; ++len) { if (watched.GetAt(len) != sPath.GetAt(len)) { if ((len > 1)&&(len < minlen)) { if (sPath.GetAt(len)=='\\') { newroot = CTGitPath(sPath.Left(len)); } else if (watched.GetAt(len)=='\\') { newroot = CTGitPath(watched.Left(len)); } } break; } } if (len == minlen) { if (sPath.GetLength() == minlen) { if (watched.GetLength() > minlen) { if (watched.GetAt(len)=='\\') { newroot = path; } else if (sPath.GetLength() == 3 && sPath[1] == ':') { newroot = path; } } } else { if (sPath.GetLength() > minlen) { if (sPath.GetAt(len)=='\\') { newroot = CTGitPath(watched); } else if (watched.GetLength() == 3 && watched[1] == ':') { newroot = CTGitPath(watched); } } } } } if (!newroot.IsEmpty()) { ATLTRACE(_T("add path to watch %s\n"), newroot.GetWinPath()); watchedPaths.AddPath(newroot); watchedPaths.RemoveChildren(); if (bCloseInfoMap) ClearInfoMap(); return true; } ATLTRACE(_T("add path to watch %s\n"), path.GetWinPath()); watchedPaths.AddPath(path); if (bCloseInfoMap) ClearInfoMap(); return true; }
bool CloneCommand::Execute() { CTGitPath cloneDirectory; if (!parser.HasKey(_T("hasurlhandler"))) { if (orgCmdLinePath.IsEmpty()) { cloneDirectory.SetFromWin(sOrigCWD, true); DWORD len = ::GetTempPath(0, nullptr); auto tszPath = std::make_unique<TCHAR[]>(len); ::GetTempPath(len, tszPath.get()); if (_tcsncicmp(cloneDirectory.GetWinPath(), tszPath.get(), len-2 /* \\ and \0 */) == 0) { // if the current directory is set to a temp directory, // we don't use that but leave it empty instead. cloneDirectory.Reset(); } } else cloneDirectory = orgCmdLinePath; } CCloneDlg dlg; dlg.m_Directory = cloneDirectory.GetWinPathString(); if (parser.HasKey(_T("url"))) dlg.m_URL = parser.GetVal(_T("url")); if (parser.HasKey(_T("exactpath"))) dlg.m_bExactPath = TRUE; if(dlg.DoModal()==IDOK) { CString recursiveStr; if(dlg.m_bRecursive) recursiveStr = _T(" --recursive"); CString bareStr; if(dlg.m_bBare) bareStr = _T(" --bare"); CString nocheckoutStr; if (dlg.m_bNoCheckout) nocheckoutStr = _T(" --no-checkout"); CString branchStr; if (dlg.m_bBranch) branchStr = _T(" --branch ") + dlg.m_strBranch; CString originStr; if (dlg.m_bOrigin && !dlg.m_bSVN) originStr = _T(" --origin ") + dlg.m_strOrigin; if(dlg.m_bAutoloadPuttyKeyFile) { CAppUtils::LaunchPAgent(&dlg.m_strPuttyKeyFile); } CAppUtils::RemoveTrailSlash(dlg.m_Directory); if (!dlg.m_bSVN) CAppUtils::RemoveTrailSlash(dlg.m_URL); CString dir=dlg.m_Directory; CString url=dlg.m_URL; // is this a windows format UNC path, ie starts with \\? if (url.Find(_T("\\\\")) == 0) { // yes, change all \ to / // this should not be necessary but msysgit does not support the use \ here yet int atSign = url.Find(_T('@')); if (atSign > 0) { CString path = url.Mid(atSign); path.Replace(_T('\\'), _T('/')); url = url.Mid(0, atSign) + path; } else url.Replace( _T('\\'), _T('/')); } CString depth; if (dlg.m_bDepth) { depth.Format(_T(" --depth %d"),dlg.m_nDepth); } CString cmd; cmd.Format(_T("git.exe clone --progress%s%s%s%s%s -v%s \"%s\" \"%s\""), (LPCTSTR)nocheckoutStr, (LPCTSTR)recursiveStr, (LPCTSTR)bareStr, (LPCTSTR)branchStr, (LPCTSTR)originStr, (LPCTSTR)depth, (LPCTSTR)url, (LPCTSTR)dir); bool retry = false; auto postCmdCallback = [&](DWORD status, PostCmdList& postCmdList) { if (status) { postCmdList.emplace_back(IDI_REFRESH, IDS_MSGBOX_RETRY, [&]{ retry = true; }); return; } // After cloning, change current directory to the cloned directory g_Git.m_CurrentDir = dlg.m_Directory; if (dlg.m_bAutoloadPuttyKeyFile) // do this here, since it might be needed for actions performed in Log StorePuttyKey(dlg.m_Directory, dlg.m_bOrigin && !dlg.m_strOrigin.IsEmpty() ? dlg.m_strOrigin : _T("origin"), dlg.m_strPuttyKeyFile); postCmdList.emplace_back(IDI_LOG, IDS_MENULOG, [&] { CString cmd = _T("/command:log"); cmd += _T(" /path:\"") + dlg.m_Directory + _T("\""); CAppUtils::RunTortoiseGitProc(cmd); }); postCmdList.emplace_back(IDI_EXPLORER, IDS_STATUSLIST_CONTEXT_EXPLORE, [&]{ CAppUtils::ExploreTo(hWndExplorer, dlg.m_Directory); }); }; // Handle Git SVN-clone if(dlg.m_bSVN) { //g_Git.m_CurrentDir=dlg.m_Directory; cmd.Format(_T("git.exe svn clone \"%s\" \"%s\""), (LPCTSTR)url, (LPCTSTR)dlg.m_Directory); if (dlg.m_bOrigin) { CString str; if (dlg.m_strOrigin.IsEmpty()) str = _T(" --prefix \"\""); else str.Format(_T(" --prefix \"%s/\""), (LPCTSTR)dlg.m_strOrigin); cmd += str; } if(dlg.m_bSVNTrunk) cmd+=_T(" -T ")+dlg.m_strSVNTrunk; if(dlg.m_bSVNBranch) cmd+=_T(" -b ")+dlg.m_strSVNBranchs; if(dlg.m_bSVNTags) cmd+=_T(" -t ")+dlg.m_strSVNTags; if(dlg.m_bSVNFrom) { CString str; str.Format(_T("%d:HEAD"),dlg.m_nSVNFrom); cmd+=_T(" -r ")+str; } if(dlg.m_bSVNUserName) { cmd+= _T(" --username "); cmd+=dlg.m_strUserName; } } else { if (g_Git.UsingLibGit2(CGit::GIT_CMD_CLONE)) { while (true) { retry = false; CGitProgressDlg GitDlg; CTGitPathList list; g_Git.m_CurrentDir = GetExistingDirectoryForClone(dlg.m_Directory); list.AddPath(CTGitPath(dir)); CloneProgressCommand cloneProgressCommand; GitDlg.SetCommand(&cloneProgressCommand); cloneProgressCommand.m_PostCmdCallback = postCmdCallback; cloneProgressCommand.SetUrl(url); cloneProgressCommand.SetPathList(list); cloneProgressCommand.SetIsBare(dlg.m_bBare == TRUE); if (dlg.m_bBranch) cloneProgressCommand.SetRefSpec(dlg.m_strBranch); if (dlg.m_bOrigin) cloneProgressCommand.SetRemote(dlg.m_strOrigin); cloneProgressCommand.SetNoCheckout(dlg.m_bNoCheckout == TRUE); GitDlg.DoModal(); if (!retry) return !GitDlg.DidErrorsOccur(); } } } while (true) { retry = false; g_Git.m_CurrentDir = GetExistingDirectoryForClone(dlg.m_Directory); CProgressDlg progress; progress.m_GitCmd=cmd; progress.m_PostCmdCallback = postCmdCallback; INT_PTR ret = progress.DoModal(); if (!retry) return ret == IDOK; } } return FALSE; }
void CFolderCrawler::BlockPath(const CTGitPath& path, DWORD ticks) { AutoLocker lock(m_critSec); CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": block path %s from being crawled\n", path.GetWinPath()); m_blockedPath = path; if (ticks == 0) m_blockReleasesAt = GetTickCount64() + 10000; else m_blockReleasesAt = GetTickCount64() + ticks; }
BOOL CTortoiseProcApp::InitInstance() { CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": InitInstance\n")); CheckUpgrade(); CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows)); CMFCButton::EnableWindowsTheming(); CHistoryCombo::m_nGitIconIndex = SYS_IMAGE_LIST().AddIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_GITCONFIG), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE)); Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::GdiplusStartup(&m_gdiplusToken,&gdiplusStartupInput,NULL); //set the resource dll for the required language CRegDWORD loc = CRegDWORD(_T("Software\\TortoiseGit\\LanguageID"), 1033); long langId = loc; { CString langStr; langStr.Format(_T("%ld"), langId); CCrashReport::Instance().AddUserInfoToReport(L"LanguageID", langStr); } CString langDll; CStringA langpath = CStringA(CPathUtils::GetAppParentDirectory()); langpath += "Languages"; do { langDll.Format(_T("%sLanguages\\TortoiseProc%ld.dll"), (LPCTSTR)CPathUtils::GetAppParentDirectory(), langId); CString sVer = _T(STRPRODUCTVER); CString sFileVer = CPathUtils::GetVersionFromFile(langDll); if (sFileVer == sVer) { HINSTANCE hInst = LoadLibrary(langDll); if (hInst != NULL) { CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": Load Language DLL %s\n"), langDll); AfxSetResourceHandle(hInst); break; } } { DWORD lid = SUBLANGID(langId); lid--; if (lid > 0) { langId = MAKELANGID(PRIMARYLANGID(langId), lid); } else langId = 0; } } while (langId != 0); TCHAR buf[6] = { 0 }; _tcscpy_s(buf, _T("en")); langId = loc; // MFC uses a help file with the same name as the application by default, // which means we have to change that default to our language specific help files CString sHelppath = CPathUtils::GetAppDirectory() + _T("TortoiseGit_en.chm"); free((void*)m_pszHelpFilePath); m_pszHelpFilePath=_tcsdup(sHelppath); sHelppath = CPathUtils::GetAppParentDirectory() + _T("Languages\\TortoiseGit_en.chm"); do { CString sLang = _T("_"); if (GetLocaleInfo(MAKELCID(langId, SORT_DEFAULT), LOCALE_SISO639LANGNAME, buf, _countof(buf))) { sLang += buf; sHelppath.Replace(_T("_en"), sLang); if (PathFileExists(sHelppath)) { free((void*)m_pszHelpFilePath); m_pszHelpFilePath=_tcsdup(sHelppath); break; } } sHelppath.Replace(sLang, _T("_en")); if (GetLocaleInfo(MAKELCID(langId, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, _countof(buf))) { sLang += _T("_"); sLang += buf; sHelppath.Replace(_T("_en"), sLang); if (PathFileExists(sHelppath)) { free((void*)m_pszHelpFilePath); m_pszHelpFilePath=_tcsdup(sHelppath); break; } } sHelppath.Replace(sLang, _T("_en")); DWORD lid = SUBLANGID(langId); lid--; if (lid > 0) { langId = MAKELANGID(PRIMARYLANGID(langId), lid); } else langId = 0; } while (langId); CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": Set Help Filename %s\n"), m_pszHelpFilePath); setlocale(LC_ALL, ""); if (!g_Git.CheckMsysGitDir()) { UINT ret = CMessageBox::Show(NULL, IDS_PROC_NOMSYSGIT, IDS_APPNAME, 3, IDI_HAND, IDS_PROC_SETMSYSGITPATH, IDS_PROC_GOTOMSYSGITWEBSITE, IDS_ABORTBUTTON); if(ret == 2) { ShellExecute(NULL, NULL, _T("http://msysgit.github.io/"), NULL, NULL, SW_SHOW); } else if(ret == 1) { // open settings dialog CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROC_SETTINGS_TITLE)), new CSetMainPage(), this->GetMainWnd()).DoModal(); } return FALSE; } if (CAppUtils::GetMsysgitVersion() < 0x01070a00) { int ret = CMessageBox::ShowCheck(NULL, IDS_PROC_OLDMSYSGIT, IDS_APPNAME, 1, IDI_EXCLAMATION, IDS_PROC_GOTOMSYSGITWEBSITE, IDS_ABORTBUTTON, IDS_IGNOREBUTTON, _T("OldMsysgitVersionWarning"), IDS_PROC_NOTSHOWAGAINIGNORE); if (ret == 1) { CMessageBox::RemoveRegistryKey(_T("OldMsysgitVersionWarning")); // only store answer if it is "Ignore" ShellExecute(NULL, NULL, _T("http://msysgit.github.io/"), NULL, NULL, SW_SHOW); return FALSE; } else if (ret == 2) { CMessageBox::RemoveRegistryKey(_T("OldMsysgitVersionWarning")); // only store answer if it is "Ignore" return FALSE; } } { CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": Registering Crash Report ...\n")); CCrashReport::Instance().AddUserInfoToReport(L"msysGitDir", CGit::ms_LastMsysGitDir); CString versionString; versionString.Format(_T("%d"), CGit::ms_LastMsysGitVersion); CCrashReport::Instance().AddUserInfoToReport(L"msysGitVersion", versionString); } CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": Initializing UI components ...\n")); // InitCommonControls() is required on Windows XP if an application // manifest specifies use of ComCtl32.dll version 6 or later to enable // visual styles. Otherwise, any window creation will fail. INITCOMMONCONTROLSEX used = { sizeof(INITCOMMONCONTROLSEX), ICC_ANIMATE_CLASS | ICC_BAR_CLASSES | ICC_COOL_CLASSES | ICC_DATE_CLASSES | ICC_HOTKEY_CLASS | ICC_INTERNET_CLASSES | ICC_LISTVIEW_CLASSES | ICC_NATIVEFNTCTL_CLASS | ICC_PAGESCROLLER_CLASS | ICC_PROGRESS_CLASS | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES | ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES | ICC_WIN95_CLASSES }; InitCommonControlsEx(&used); AfxOleInit(); AfxEnableControlContainer(); AfxInitRichEdit5(); CWinAppEx::InitInstance(); SetRegistryKey(_T("TortoiseGit")); AfxGetApp()->m_pszProfileName = _tcsdup(_T("TortoiseProc")); // w/o this ResizableLib will store data under TortoiseGitProc which is not compatible with older versions CCmdLineParser parser(AfxGetApp()->m_lpCmdLine); hWndExplorer = NULL; CString sVal = parser.GetVal(_T("hwnd")); if (!sVal.IsEmpty()) hWndExplorer = (HWND)_wcstoui64(sVal, nullptr, 16); while (GetParent(hWndExplorer)!=NULL) hWndExplorer = GetParent(hWndExplorer); if (!IsWindow(hWndExplorer)) { hWndExplorer = NULL; } // if HKCU\Software\TortoiseGit\Debug is not 0, show our command line // in a message box if (CRegDWORD(_T("Software\\TortoiseGit\\Debug"), FALSE)==TRUE) AfxMessageBox(AfxGetApp()->m_lpCmdLine, MB_OK | MB_ICONINFORMATION); if (parser.HasKey(_T("urlhandler"))) { CString url = parser.GetVal(_T("urlhandler")); if (url.Find(_T("tgit://clone/")) == 0) { url = url.Mid(13); // 21 = "tgit://clone/".GetLength() } else if (url.Find(_T("github-windows://openRepo/")) == 0) { url = url.Mid(26); // 26 = "github-windows://openRepo/".GetLength() int questioMark = url.Find('?'); if (questioMark > 0) url = url.Left(questioMark); } else if (url.Find(_T("smartgit://cloneRepo/")) == 0) { url = url.Mid(21); // 21 = "smartgit://cloneRepo/".GetLength() } else { CMessageBox::Show(NULL, IDS_ERR_INVALIDPATH, IDS_APPNAME, MB_ICONERROR); return FALSE; } CString newCmd; newCmd.Format(_T("/command:clone /url:\"%s\" /hasurlhandler"), url); parser = CCmdLineParser(newCmd); } if ( parser.HasKey(_T("path")) && parser.HasKey(_T("pathfile"))) { CMessageBox::Show(NULL, IDS_ERR_INVALIDPATH, IDS_APPNAME, MB_ICONERROR); return FALSE; } CTGitPath cmdLinePath; CTGitPathList pathList; if (g_sGroupingUUID.IsEmpty()) g_sGroupingUUID = parser.GetVal(L"groupuuid"); if ( parser.HasKey(_T("pathfile")) ) { CString sPathfileArgument = CPathUtils::GetLongPathname(parser.GetVal(_T("pathfile"))); cmdLinePath.SetFromUnknown(sPathfileArgument); if (pathList.LoadFromFile(cmdLinePath)==false) return FALSE; // no path specified! if ( parser.HasKey(_T("deletepathfile")) ) { // We can delete the temporary path file, now that we've loaded it ::DeleteFile(cmdLinePath.GetWinPath()); } // This was a path to a temporary file - it's got no meaning now, and // anybody who uses it again is in for a problem... cmdLinePath.Reset(); } else { CString sPathArgument = CPathUtils::GetLongPathname(parser.GetVal(_T("path"))); if (parser.HasKey(_T("expaths"))) { // an /expaths param means we're started via the buttons in our Win7 library // and that means the value of /expaths is the current directory, and // the selected paths are then added as additional parameters but without a key, only a value // because of the "strange treatment of quotation marks and backslashes by CommandLineToArgvW" // we have to escape the backslashes first. Since we're only dealing with paths here, that's // a save bet. // Without this, a command line like: // /command:commit /expaths:"D:\" "D:\Utils" // would fail because the "D:\" is treated as the backslash being the escape char for the quotation // mark and we'd end up with: // argv[1] = /command:commit // argv[2] = /expaths:D:" D:\Utils // See here for more details: http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx CString cmdLine = GetCommandLineW(); cmdLine.Replace(L"\\", L"\\\\"); int nArgs = 0; LPWSTR *szArglist = CommandLineToArgvW(cmdLine, &nArgs); if (szArglist) { // argument 0 is the process path, so start with 1 for (int i = 1; i < nArgs; ++i) { if (szArglist[i][0] != '/') { if (!sPathArgument.IsEmpty()) sPathArgument += '*'; sPathArgument += szArglist[i]; } } sPathArgument.Replace(L"\\\\", L"\\"); } LocalFree(szArglist); } if (sPathArgument.IsEmpty() && parser.HasKey(L"path")) { CMessageBox::Show(hWndExplorer, IDS_ERR_INVALIDPATH, IDS_APPNAME, MB_ICONERROR); return FALSE; } int asterisk = sPathArgument.Find('*'); cmdLinePath.SetFromUnknown(asterisk >= 0 ? sPathArgument.Left(asterisk) : sPathArgument); pathList.LoadFromAsteriskSeparatedString(sPathArgument); } if (pathList.IsEmpty()) { pathList.AddPath(CTGitPath::CTGitPath(g_Git.m_CurrentDir)); } // Set CWD to temporary dir, and restore it later { DWORD len = GetCurrentDirectory(0, NULL); if (len) { std::unique_ptr<TCHAR[]> originalCurrentDirectory(new TCHAR[len]); if (GetCurrentDirectory(len, originalCurrentDirectory.get())) { sOrigCWD = originalCurrentDirectory.get(); sOrigCWD = CPathUtils::GetLongPathname(sOrigCWD); } } TCHAR pathbuf[MAX_PATH] = {0}; GetTortoiseGitTempPath(MAX_PATH, pathbuf); SetCurrentDirectory(pathbuf); } CheckForNewerVersion(); CAutoGeneralHandle TGitMutex = ::CreateMutex(NULL, FALSE, _T("TortoiseGitProc.exe")); if (!g_Git.SetCurrentDir(cmdLinePath.GetWinPathString(), parser.HasKey(_T("submodule")) == TRUE)) { for (int i = 0; i < pathList.GetCount(); ++i) if(g_Git.SetCurrentDir(pathList[i].GetWinPath())) break; } if(!g_Git.m_CurrentDir.IsEmpty()) { sOrigCWD = g_Git.m_CurrentDir; SetCurrentDirectory(g_Git.m_CurrentDir); } if (g_sGroupingUUID.IsEmpty()) { CRegStdDWORD groupSetting = CRegStdDWORD(_T("Software\\TortoiseGit\\GroupTaskbarIconsPerRepo"), 3); switch (DWORD(groupSetting)) { case 1: case 2: // implemented differently to TortoiseSVN atm break; case 3: case 4: { CString wcroot; if (g_GitAdminDir.HasAdminDir(g_Git.m_CurrentDir, true, &wcroot)) { git_oid oid; CStringA wcRootA(wcroot + CPathUtils::GetAppDirectory()); if (!git_odb_hash(&oid, wcRootA, wcRootA.GetLength(), GIT_OBJ_BLOB)) { CStringA hash; git_oid_tostr(hash.GetBufferSetLength(GIT_OID_HEXSZ + 1), GIT_OID_HEXSZ + 1, &oid); hash.ReleaseBuffer(); g_sGroupingUUID = hash; } ProjectProperties pp; pp.ReadProps(); CString icon = pp.sIcon; icon.Replace('/', '\\'); if (icon.IsEmpty()) g_bGroupingRemoveIcon = true; g_sGroupingIcon = icon; } } } } CString sAppID = GetTaskIDPerUUID(g_sGroupingUUID).c_str(); InitializeJumpList(sAppID); EnsureGitLibrary(false); { CString err; try { // requires CWD to be set CGit::m_LogEncode = CAppUtils::GetLogOutputEncode(); // make sure all config files are read in order to check that none contains an error g_Git.GetConfigValue(_T("doesnot.exist")); } catch (char* msg) { err = CString(msg); } if (!err.IsEmpty()) { UINT choice = CMessageBox::Show(hWndExplorer, err, _T("TortoiseGit"), 1, IDI_ERROR, CString(MAKEINTRESOURCE(IDS_PROC_EDITLOCALGITCONFIG)), CString(MAKEINTRESOURCE(IDS_PROC_EDITGLOBALGITCONFIG)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))); if (choice == 1) { // open the config file with alternative editor CAppUtils::LaunchAlternativeEditor(g_Git.GetGitLocalConfig()); } else if (choice == 2) { // open the global config file with alternative editor CAppUtils::LaunchAlternativeEditor(g_Git.GetGitGlobalConfig()); } return FALSE; } } // execute the requested command CommandServer server; Command * cmd = server.GetCommand(parser.GetVal(_T("command"))); if (cmd) { cmd->SetExplorerHwnd(hWndExplorer); cmd->SetParser(parser); cmd->SetPaths(pathList, cmdLinePath); retSuccess = cmd->Execute(); delete cmd; } // Look for temporary files left around by TortoiseSVN and // remove them. But only delete 'old' files because some // apps might still be needing the recent ones. { DWORD len = GetTortoiseGitTempPath(0, NULL); std::unique_ptr<TCHAR[]> path(new TCHAR[len + 100]); len = GetTortoiseGitTempPath (len + 100, path.get()); if (len != 0) { CDirFileEnum finder(path.get()); FILETIME systime_; ::GetSystemTimeAsFileTime(&systime_); __int64 systime = (((_int64)systime_.dwHighDateTime)<<32) | ((__int64)systime_.dwLowDateTime); bool isDir; CString filepath; while (finder.NextFile(filepath, &isDir)) { HANDLE hFile = ::CreateFile(filepath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, isDir ? FILE_FLAG_BACKUP_SEMANTICS : NULL, NULL); if (hFile != INVALID_HANDLE_VALUE) { FILETIME createtime_; if (::GetFileTime(hFile, &createtime_, NULL, NULL)) { ::CloseHandle(hFile); __int64 createtime = (((_int64)createtime_.dwHighDateTime)<<32) | ((__int64)createtime_.dwLowDateTime); if ((createtime + 864000000000) < systime) //only delete files older than a day { ::SetFileAttributes(filepath, FILE_ATTRIBUTE_NORMAL); if (isDir) ::RemoveDirectory(filepath); else ::DeleteFile(filepath); } } else ::CloseHandle(hFile); } } } } // Since the dialog has been closed, return FALSE so that we exit the // application, rather than start the application's message pump. return FALSE; }
void CDirectoryWatcher::WorkerThread() { DWORD numBytes; CDirWatchInfo * pdi = NULL; LPOVERLAPPED lpOverlapped; WCHAR buf[READ_DIR_CHANGE_BUFFER_SIZE] = {0}; WCHAR * pFound = NULL; CTGitPath path; while (m_bRunning) { if (watchedPaths.GetCount()) { if (!GetQueuedCompletionStatus(m_hCompPort, &numBytes, (PULONG_PTR) &pdi, &lpOverlapped, INFINITE)) { // Error retrieving changes // Clear the list of watched objects and recreate that list if (!m_bRunning) return; { AutoLocker lock(m_critSec); ClearInfoMap(); } DWORD lasterr = GetLastError(); if ((m_hCompPort != INVALID_HANDLE_VALUE)&&(lasterr!=ERROR_SUCCESS)&&(lasterr!=ERROR_INVALID_HANDLE)) { CloseHandle(m_hCompPort); m_hCompPort = INVALID_HANDLE_VALUE; } // Since we pass m_hCompPort to CreateIoCompletionPort, we // have to set this to NULL to have that API create a new // handle. m_hCompPort = NULL; for (int i=0; i<watchedPaths.GetCount(); ++i) { CTGitPath watchedPath = watchedPaths[i]; HANDLE hDir = CreateFile(watchedPath.GetWinPath(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, //security attributes OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME. FILE_FLAG_OVERLAPPED, NULL); if (hDir == INVALID_HANDLE_VALUE) { // this could happen if a watched folder has been removed/renamed ATLTRACE(_T("CDirectoryWatcher: CreateFile failed. Can't watch directory %s\n"), watchedPaths[i].GetWinPath()); CloseHandle(m_hCompPort); m_hCompPort = INVALID_HANDLE_VALUE; AutoLocker lock(m_critSec); watchedPaths.RemovePath(watchedPath); i--; if (i<0) i=0; break; } DEV_BROADCAST_HANDLE NotificationFilter; SecureZeroMemory(&NotificationFilter, sizeof(NotificationFilter)); NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE); NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE; NotificationFilter.dbch_handle = hDir; NotificationFilter.dbch_hdevnotify = RegisterDeviceNotification(hWnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE); CDirWatchInfo * pDirInfo = new CDirWatchInfo(hDir, watchedPath); pDirInfo->m_hDevNotify = NotificationFilter.dbch_hdevnotify; m_hCompPort = CreateIoCompletionPort(hDir, m_hCompPort, (ULONG_PTR)pDirInfo, 0); if (m_hCompPort == NULL) { ATLTRACE(_T("CDirectoryWatcher: CreateIoCompletionPort failed. Can't watch directory %s\n"), watchedPath.GetWinPath()); AutoLocker lock(m_critSec); ClearInfoMap(); delete pDirInfo; pDirInfo = NULL; watchedPaths.RemovePath(watchedPath); i--; if (i<0) i=0; break; } if (!ReadDirectoryChangesW(pDirInfo->m_hDir, pDirInfo->m_Buffer, READ_DIR_CHANGE_BUFFER_SIZE, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, &numBytes,// not used &pDirInfo->m_Overlapped, NULL)) //no completion routine! { ATLTRACE(_T("CDirectoryWatcher: ReadDirectoryChangesW failed. Can't watch directory %s\n"), watchedPath.GetWinPath()); AutoLocker lock(m_critSec); ClearInfoMap(); delete pDirInfo; pDirInfo = NULL; watchedPaths.RemovePath(watchedPath); i--; if (i<0) i=0; break; } AutoLocker lock(m_critSec); watchInfoMap[pDirInfo->m_hDir] = pDirInfo; ATLTRACE(_T("watching path %s\n"), pDirInfo->m_DirName.GetWinPath()); } } else { if (!m_bRunning) return; // NOTE: the longer this code takes to execute until ReadDirectoryChangesW // is called again, the higher the chance that we miss some // changes in the file system! if (pdi) { if (numBytes == 0) { goto continuewatching; } PFILE_NOTIFY_INFORMATION pnotify = (PFILE_NOTIFY_INFORMATION)pdi->m_Buffer; if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE) goto continuewatching; DWORD nOffset = pnotify->NextEntryOffset; do { nOffset = pnotify->NextEntryOffset; if (pnotify->FileNameLength >= (READ_DIR_CHANGE_BUFFER_SIZE*sizeof(TCHAR))) continue; SecureZeroMemory(buf, READ_DIR_CHANGE_BUFFER_SIZE*sizeof(TCHAR)); _tcsncpy_s(buf, READ_DIR_CHANGE_BUFFER_SIZE, pdi->m_DirPath, READ_DIR_CHANGE_BUFFER_SIZE); errno_t err = _tcsncat_s(buf+pdi->m_DirPath.GetLength(), READ_DIR_CHANGE_BUFFER_SIZE-pdi->m_DirPath.GetLength(), pnotify->FileName, _TRUNCATE); if (err == STRUNCATE) { pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset); continue; } buf[(pnotify->FileNameLength/sizeof(TCHAR))+pdi->m_DirPath.GetLength()] = 0; pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset); if (m_FolderCrawler) { if ((pFound = wcsstr(buf, L"\\tmp"))!=NULL) { pFound += 4; if (((*pFound)=='\\')||((*pFound)=='\0')) { if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE) break; continue; } } if ((pFound = wcsstr(buf, L":\\RECYCLER\\"))!=NULL) { if ((pFound-buf) < 5) { // a notification for the recycle bin - ignore it if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE) break; continue; } } if ((pFound = wcsstr(buf, L":\\$Recycle.Bin\\"))!=NULL) { if ((pFound-buf) < 5) { // a notification for the recycle bin - ignore it if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE) break; continue; } } if ((pFound = wcsstr(buf, L".tmp"))!=NULL) { // assume files with a .tmp extension are not versioned and interesting, // so ignore them. if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE) break; continue; } bool isIndex = false; if ((pFound = wcsstr(buf, L".git"))!=NULL) { // omit repository data change except .git/index.lock- or .git/HEAD.lock-files if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE) break; if ((wcsstr(pFound, L"index.lock") != NULL && wcsstr(pFound, L"HEAD.lock") != NULL) && pnotify->Action == FILE_ACTION_ADDED) { m_FolderCrawler->BlockPath(CTGitPath(buf).GetContainingDirectory().GetContainingDirectory()); // optimize here, and use general BlockPath with priorities continue; } else if ((wcsstr(pFound, L"index.lock") != NULL && wcsstr(pFound, L"HEAD.lock") != NULL) && pnotify->Action == FILE_ACTION_REMOVED) { isIndex = true; m_FolderCrawler->BlockPath(CTGitPath(buf).GetContainingDirectory().GetContainingDirectory(), 1); } else { continue; } } path.SetFromWin(buf); if(!path.HasAdminDir() && !isIndex) continue; ATLTRACE(_T("change notification: %s\n"), buf); m_FolderCrawler->AddPathForUpdate(CTGitPath(buf)); } if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE) break; } while (nOffset); continuewatching: SecureZeroMemory(pdi->m_Buffer, sizeof(pdi->m_Buffer)); SecureZeroMemory(&pdi->m_Overlapped, sizeof(OVERLAPPED)); if (!ReadDirectoryChangesW(pdi->m_hDir, pdi->m_Buffer, READ_DIR_CHANGE_BUFFER_SIZE, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, &numBytes,// not used &pdi->m_Overlapped, NULL)) //no completion routine! { // Since the call to ReadDirectoryChangesW failed, just // wait a while. We don't want to have this thread // running using 100% CPU if something goes completely // wrong. Sleep(200); } } } } // if (watchedPaths.GetCount()) else Sleep(200); } // while (m_bRunning) }
bool CCacheDlg::GetStatusFromRemoteCache(const CTGitPath& Path, bool bRecursive) { if(!EnsurePipeOpen()) { STARTUPINFO startup = { 0 }; PROCESS_INFORMATION process = { 0 }; startup.cb = sizeof(startup); CString sCachePath = L"TGitCache.exe"; if (CreateProcess(sCachePath.GetBuffer(sCachePath.GetLength() + 1), L"", nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startup, &process) == 0) { // It's not appropriate to do a message box here, because there may be hundreds of calls sCachePath.ReleaseBuffer(); ATLTRACE("Failed to start cache\n"); return false; } sCachePath.ReleaseBuffer(); // Wait for the cache to open ULONGLONG endTime = GetTickCount64()+1000; while(!EnsurePipeOpen()) { if((GetTickCount64() - endTime) > 0) { return false; } } } DWORD nBytesRead; TGITCacheRequest request; request.flags = TGITCACHE_FLAGS_NONOTIFICATIONS; if(bRecursive) { request.flags |= TGITCACHE_FLAGS_RECUSIVE_STATUS; } wcsncpy_s(request.path, Path.GetWinPath(), MAX_PATH); SecureZeroMemory(&m_Overlapped, sizeof(OVERLAPPED)); m_Overlapped.hEvent = m_hEvent; // Do the transaction in overlapped mode. // That way, if anything happens which might block this call // we still can get out of it. We NEVER MUST BLOCK THE SHELL! // A blocked shell is a very bad user impression, because users // who don't know why it's blocked might find the only solution // to such a problem is a reboot and therefore they might loose // valuable data. // Sure, it would be better to have no situations where the shell // even can get blocked, but the timeout of 5 seconds is long enough // so that users still recognize that something might be wrong and // report back to us so we can investigate further. TGITCacheResponse ReturnedStatus; BOOL fSuccess = TransactNamedPipe(m_hPipe, &request, sizeof(request), &ReturnedStatus, sizeof(ReturnedStatus), &nBytesRead, &m_Overlapped); if (!fSuccess) { if (GetLastError()!=ERROR_IO_PENDING) { ClosePipe(); return false; } // TransactNamedPipe is working in an overlapped operation. // Wait for it to finish DWORD dwWait = WaitForSingleObject(m_hEvent, INFINITE); if (dwWait == WAIT_OBJECT_0) { fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE); return TRUE; } else fSuccess = FALSE; } ClosePipe(); return false; }
bool CloneCommand::Execute() { CTGitPath cloneDirectory; if (!parser.HasKey(_T("hasurlhandler"))) { if (orgCmdLinePath.IsEmpty()) { cloneDirectory.SetFromWin(sOrigCWD, true); DWORD len = ::GetTempPath(0, NULL); std::unique_ptr<TCHAR[]> tszPath(new TCHAR[len]); ::GetTempPath(len, tszPath.get()); if (_tcsncicmp(cloneDirectory.GetWinPath(), tszPath.get(), len-2 /* \\ and \0 */) == 0) { // if the current directory is set to a temp directory, // we don't use that but leave it empty instead. cloneDirectory.Reset(); } } else cloneDirectory = orgCmdLinePath; } CCloneDlg dlg; dlg.m_Directory = cloneDirectory.GetWinPathString(); if (parser.HasKey(_T("url"))) dlg.m_URL = parser.GetVal(_T("url")); if (parser.HasKey(_T("exactpath"))) dlg.m_bExactPath = TRUE; if(dlg.DoModal()==IDOK) { CString recursiveStr; if(dlg.m_bRecursive) recursiveStr = _T("--recursive"); else recursiveStr = _T(""); CString bareStr; if(dlg.m_bBare) bareStr = _T("--bare"); else bareStr = _T(""); CString nocheckoutStr; if (dlg.m_bNoCheckout) nocheckoutStr = _T("--no-checkout"); CString branchStr; if (dlg.m_bBranch) branchStr = _T("--branch ") + dlg.m_strBranch; CString originStr; if (dlg.m_bOrigin) originStr = _T("--origin ") + dlg.m_strOrigin; if(dlg.m_bAutoloadPuttyKeyFile) { CAppUtils::LaunchPAgent(&dlg.m_strPuttyKeyFile); } CAppUtils::RemoveTrailSlash(dlg.m_Directory); if (!dlg.m_bSVN) CAppUtils::RemoveTrailSlash(dlg.m_URL); CString dir=dlg.m_Directory; CString url=dlg.m_URL; // is this a windows format UNC path, ie starts with \\? if (url.Find(_T("\\\\")) == 0) { // yes, change all \ to / // this should not be necessary but msysgit does not support the use \ here yet int atSign = url.Find(_T('@')); if (atSign > 0) { CString path = url.Mid(atSign); path.Replace(_T('\\'), _T('/')); url = url.Mid(0, atSign) + path; } else url.Replace( _T('\\'), _T('/')); } CString depth; if (dlg.m_bDepth) { depth.Format(_T(" --depth %d"),dlg.m_nDepth); } g_Git.m_CurrentDir = GetExistingDirectoryForClone(dlg.m_Directory); CString cmd; CString progressarg; int ver = CAppUtils::GetMsysgitVersion(); if(ver >= 0x01070002) //above 1.7.0.2 progressarg = _T("--progress"); cmd.Format(_T("git.exe clone %s %s %s %s %s %s -v %s \"%s\" \"%s\""), nocheckoutStr, recursiveStr, bareStr, branchStr, originStr, progressarg, depth, url, dir); // Handle Git SVN-clone if(dlg.m_bSVN) { //g_Git.m_CurrentDir=dlg.m_Directory; cmd.Format(_T("git.exe svn clone \"%s\" \"%s\""), url,dlg.m_Directory); if(dlg.m_bSVNTrunk) cmd+=_T(" -T ")+dlg.m_strSVNTrunk; if(dlg.m_bSVNBranch) cmd+=_T(" -b ")+dlg.m_strSVNBranchs; if(dlg.m_bSVNTags) cmd+=_T(" -t ")+dlg.m_strSVNTags; if(dlg.m_bSVNFrom) { CString str; str.Format(_T("%d:HEAD"),dlg.m_nSVNFrom); cmd+=_T(" -r ")+str; } if(dlg.m_bSVNUserName) { cmd+= _T(" --username "); cmd+=dlg.m_strUserName; } } else { if (g_Git.UsingLibGit2(CGit::GIT_CMD_CLONE)) { CGitProgressDlg GitDlg; CTGitPathList list; g_Git.m_CurrentDir = dir; list.AddPath(CTGitPath(dir)); GitDlg.SetCommand(CGitProgressList::GitProgress_Clone); GitDlg.SetUrl(url); GitDlg.SetPathList(list); GitDlg.SetIsBare(!!dlg.m_bBare); GitDlg.SetRefSpec(dlg.m_bBranch ? dlg.m_strBranch : CString()); GitDlg.SetRemote(dlg.m_bOrigin ? dlg.m_strOrigin : CString()); GitDlg.SetNoCheckout(!!dlg.m_bNoCheckout); GitDlg.DoModal(); return !GitDlg.DidErrorsOccur(); } } CProgressDlg progress; progress.m_GitCmd=cmd; progress.m_PostCmdList.Add(CString(MAKEINTRESOURCE(IDS_MENULOG))); progress.m_PostCmdList.Add(CString(MAKEINTRESOURCE(IDS_STATUSLIST_CONTEXT_EXPLORE))); INT_PTR ret = progress.DoModal(); if (dlg.m_bSVN) ::DeleteFile(g_Git.m_CurrentDir + _T("\\sys$command")); if( progress.m_GitStatus == 0) { if(dlg.m_bAutoloadPuttyKeyFile) { g_Git.m_CurrentDir = dlg.m_Directory; SetCurrentDirectory(g_Git.m_CurrentDir); if(g_Git.SetConfigValue(_T("remote.origin.puttykeyfile"), dlg.m_strPuttyKeyFile, CONFIG_LOCAL, CP_UTF8)) { CMessageBox::Show(NULL,_T("Fail set config remote.origin.puttykeyfile"),_T("TortoiseGit"),MB_OK|MB_ICONERROR); return FALSE; } } if (ret == IDC_PROGRESS_BUTTON1) { CString cmd = _T("/command:log"); cmd += _T(" /path:\"") + dlg.m_Directory + _T("\""); CAppUtils::RunTortoiseGitProc(cmd); return TRUE; } if (ret == IDC_PROGRESS_BUTTON1 + 1) { ShellExecute(nullptr, _T("explore"), dlg.m_Directory, nullptr, nullptr, SW_SHOW); return TRUE; } } if(ret == IDOK) return TRUE; } return FALSE; }
bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTGitPath& Path, TGITCacheResponse* pReturnedStatus, bool bRecursive) { if(!EnsurePipeOpen()) { // We've failed to open the pipe - try and start the cache // but only if the last try to start the cache was a certain time // ago. If we just try over and over again without a small pause // in between, the explorer is rendered unusable! // Failing to start the cache can have different reasons: missing exe, // missing registry key, corrupt exe, ... if (((long)GetTickCount() - m_lastTimeout) < 0) return false; // if we're in protected mode, don't try to start the cache: since we're // here, we know we can't access it anyway and starting a new process will // trigger a warning dialog in IE7+ on Vista - we don't want that. if (GetProcessIntegrityLevel() < SECURITY_MANDATORY_MEDIUM_RID) return false; if (!RunTGitCacheProcess()) return false; // Wait for the cache to open long endTime = (long)GetTickCount()+1000; while(!EnsurePipeOpen()) { if(((long)GetTickCount() - endTime) > 0) { m_lastTimeout = (long)GetTickCount()+10000; return false; } } m_lastTimeout = (long)GetTickCount()+10000; } AutoLocker lock(m_critSec); DWORD nBytesRead; TGITCacheRequest request; request.flags = TGITCACHE_FLAGS_NONOTIFICATIONS; if(bRecursive) { request.flags |= TGITCACHE_FLAGS_RECUSIVE_STATUS; } wcsncpy_s(request.path, Path.GetWinPath(), _countof(request.path) - 1); SecureZeroMemory(&m_Overlapped, sizeof(OVERLAPPED)); m_Overlapped.hEvent = m_hEvent; // Do the transaction in overlapped mode. // That way, if anything happens which might block this call // we still can get out of it. We NEVER MUST BLOCK THE SHELL! // A blocked shell is a very bad user impression, because users // who don't know why it's blocked might find the only solution // to such a problem is a reboot and therefore they might loose // valuable data. // One particular situation where the shell could hang is when // the cache crashes and our crash report dialog comes up. // Sure, it would be better to have no situations where the shell // even can get blocked, but the timeout of 10 seconds is long enough // so that users still recognize that something might be wrong and // report back to us so we can investigate further. BOOL fSuccess = TransactNamedPipe(m_hPipe, &request, sizeof(request), pReturnedStatus, sizeof(*pReturnedStatus), &nBytesRead, &m_Overlapped); if (!fSuccess) { if (GetLastError()!=ERROR_IO_PENDING) { //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n"); ClosePipe(); return false; } // TransactNamedPipe is working in an overlapped operation. // Wait for it to finish DWORD dwWait = WaitForSingleObject(m_hEvent, 10000); if (dwWait == WAIT_OBJECT_0) { fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE); } else { // the cache didn't respond! fSuccess = FALSE; } } if (fSuccess) { return true; } ClosePipe(); return false; }
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; }
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(); }
bool CPathWatcher::AddPath(const CTGitPath& path) { AutoLocker lock(m_critSec); for (int i=0; i<watchedPaths.GetCount(); ++i) { if (watchedPaths[i].IsAncestorOf(path)) return false; // already watched (recursively) } // now check if with the new path we might have a new root CTGitPath newroot; for (int i=0; i<watchedPaths.GetCount(); ++i) { const CString& watched = watchedPaths[i].GetWinPathString(); const CString& sPath = path.GetWinPathString(); int minlen = min(sPath.GetLength(), watched.GetLength()); int len = 0; for (len = 0; len < minlen; ++len) { if (watched.GetAt(len) != sPath.GetAt(len)) { if ((len > 1)&&(len < minlen)) { if (sPath.GetAt(len)=='\\') { newroot = CTGitPath(sPath.Left(len)); } else if (watched.GetAt(len)=='\\') { newroot = CTGitPath(watched.Left(len)); } } break; } } if (len == minlen) { if (sPath.GetLength() == minlen) { if (watched.GetLength() > minlen) { if (watched.GetAt(len)=='\\') { newroot = path; } else if (sPath.GetLength() == 3 && sPath[1] == ':') { newroot = path; } } } else { if (sPath.GetLength() > minlen) { if (sPath.GetAt(len)=='\\') { newroot = CTGitPath(watched); } else if (watched.GetLength() == 3 && watched[1] == ':') { newroot = CTGitPath(watched); } } } } } if (!newroot.IsEmpty()) { ATLTRACE(_T("add path to watch %s\n"), newroot.GetWinPath()); watchedPaths.AddPath(newroot); watchedPaths.RemoveChildren(); m_hCompPort = INVALID_HANDLE_VALUE; return true; } ATLTRACE(_T("add path to watch %s\n"), path.GetWinPath()); watchedPaths.AddPath(path); m_hCompPort = INVALID_HANDLE_VALUE; return true; }
BOOL ProjectProperties::ReadProps(CTGitPath path) { CString sPropVal; GetStringProps(this->sLabel,BUGTRAQPROPNAME_LABEL); GetStringProps(this->sMessage,BUGTRAQPROPNAME_MESSAGE); GetStringProps(this->sUrl,BUGTRAQPROPNAME_URL); GetBOOLProps(this->bWarnIfNoIssue,BUGTRAQPROPNAME_WARNIFNOISSUE); GetBOOLProps(this->bNumber,BUGTRAQPROPNAME_NUMBER); GetBOOLProps(this->bAppend,BUGTRAQPROPNAME_APPEND); GetStringProps(sPropVal,BUGTRAQPROPNAME_LOGREGEX,false); sCheckRe = sPropVal; if (sCheckRe.Find('\n')>=0) { sBugIDRe = sCheckRe.Mid(sCheckRe.Find('\n')).Trim(); sCheckRe = sCheckRe.Left(sCheckRe.Find('\n')).Trim(); } if (!sCheckRe.IsEmpty()) { sCheckRe = sCheckRe.Trim(); } return TRUE; #if 0 BOOL bFoundBugtraqLabel = FALSE; BOOL bFoundBugtraqMessage = FALSE; BOOL bFoundBugtraqNumber = FALSE; BOOL bFoundBugtraqLogRe = FALSE; BOOL bFoundBugtraqURL = FALSE; BOOL bFoundBugtraqWarnIssue = FALSE; BOOL bFoundBugtraqAppend = FALSE; BOOL bFoundLogWidth = FALSE; BOOL bFoundLogTemplate = FALSE; BOOL bFoundMinLogSize = FALSE; BOOL bFoundMinLockMsgSize = FALSE; BOOL bFoundFileListEnglish = FALSE; BOOL bFoundProjectLanguage = FALSE; BOOL bFoundUserFileProps = FALSE; BOOL bFoundUserDirProps = FALSE; BOOL bFoundWebViewRev = FALSE; BOOL bFoundWebViewPathRev = FALSE; BOOL bFoundAutoProps = FALSE; BOOL bFoundLogSummary = FALSE; if (!path.IsDirectory()) path = path.GetContainingDirectory(); for (;;) { GitProperties props(path, GitRev::REV_WC, false); for (int i=0; i<props.GetCount(); ++i) { CString sPropName = props.GetItemName(i).c_str(); CString sPropVal = CUnicodeUtils::GetUnicode(((char *)props.GetItemValue(i).c_str())); if ((!bFoundBugtraqLabel)&&(sPropName.Compare(BUGTRAQPROPNAME_LABEL)==0)) { sLabel = sPropVal; bFoundBugtraqLabel = TRUE; } if ((!bFoundBugtraqMessage)&&(sPropName.Compare(BUGTRAQPROPNAME_MESSAGE)==0)) { sMessage = sPropVal; bFoundBugtraqMessage = TRUE; } if ((!bFoundBugtraqNumber)&&(sPropName.Compare(BUGTRAQPROPNAME_NUMBER)==0)) { CString val; val = sPropVal; val = val.Trim(_T(" \n\r\t")); if ((val.CompareNoCase(_T("false"))==0)||(val.CompareNoCase(_T("no"))==0)) bNumber = FALSE; else bNumber = TRUE; bFoundBugtraqNumber = TRUE; } if ((!bFoundBugtraqLogRe)&&(sPropName.Compare(BUGTRAQPROPNAME_LOGREGEX)==0)) { sCheckRe = sPropVal; if (sCheckRe.Find('\n')>=0) { sBugIDRe = sCheckRe.Mid(sCheckRe.Find('\n')).Trim(); sCheckRe = sCheckRe.Left(sCheckRe.Find('\n')).Trim(); } if (!sCheckRe.IsEmpty()) { sCheckRe = sCheckRe.Trim(); } bFoundBugtraqLogRe = TRUE; } if ((!bFoundBugtraqURL)&&(sPropName.Compare(BUGTRAQPROPNAME_URL)==0)) { sUrl = sPropVal; bFoundBugtraqURL = TRUE; } if ((!bFoundBugtraqWarnIssue)&&(sPropName.Compare(BUGTRAQPROPNAME_WARNIFNOISSUE)==0)) { CString val; val = sPropVal; val = val.Trim(_T(" \n\r\t")); if ((val.CompareNoCase(_T("true"))==0)||(val.CompareNoCase(_T("yes"))==0)) bWarnIfNoIssue = TRUE; else bWarnIfNoIssue = FALSE; bFoundBugtraqWarnIssue = TRUE; } if ((!bFoundBugtraqAppend)&&(sPropName.Compare(BUGTRAQPROPNAME_APPEND)==0)) { CString val; val = sPropVal; val = val.Trim(_T(" \n\r\t")); if ((val.CompareNoCase(_T("true"))==0)||(val.CompareNoCase(_T("yes"))==0)) bAppend = TRUE; else bAppend = FALSE; bFoundBugtraqAppend = TRUE; } if ((!bFoundLogWidth)&&(sPropName.Compare(PROJECTPROPNAME_LOGWIDTHLINE)==0)) { CString val; val = sPropVal; if (!val.IsEmpty()) { nLogWidthMarker = _ttoi(val); } bFoundLogWidth = TRUE; } if ((!bFoundLogTemplate)&&(sPropName.Compare(PROJECTPROPNAME_LOGTEMPLATE)==0)) { sLogTemplate = sPropVal; sLogTemplate.Remove('\r'); sLogTemplate.Replace(_T("\n"), _T("\r\n")); bFoundLogTemplate = TRUE; } if ((!bFoundMinLogSize)&&(sPropName.Compare(PROJECTPROPNAME_LOGMINSIZE)==0)) { CString val; val = sPropVal; if (!val.IsEmpty()) { nMinLogSize = _ttoi(val); } bFoundMinLogSize = TRUE; } if ((!bFoundMinLockMsgSize)&&(sPropName.Compare(PROJECTPROPNAME_LOCKMSGMINSIZE)==0)) { CString val; val = sPropVal; if (!val.IsEmpty()) { nMinLockMsgSize = _ttoi(val); } bFoundMinLockMsgSize = TRUE; } if ((!bFoundFileListEnglish)&&(sPropName.Compare(PROJECTPROPNAME_LOGFILELISTLANG)==0)) { CString val; val = sPropVal; val = val.Trim(_T(" \n\r\t")); if ((val.CompareNoCase(_T("false"))==0)||(val.CompareNoCase(_T("no"))==0)) bFileListInEnglish = TRUE; else bFileListInEnglish = FALSE; bFoundFileListEnglish = TRUE; } if ((!bFoundProjectLanguage)&&(sPropName.Compare(PROJECTPROPNAME_PROJECTLANGUAGE)==0)) { CString val; val = sPropVal; if (!val.IsEmpty()) { LPTSTR strEnd; lProjectLanguage = _tcstol(val, &strEnd, 0); } bFoundProjectLanguage = TRUE; } if ((!bFoundUserFileProps)&&(sPropName.Compare(PROJECTPROPNAME_USERFILEPROPERTY)==0)) { sFPPath = sPropVal; sFPPath.Replace(_T("\r\n"), _T("\n")); bFoundUserFileProps = TRUE; } if ((!bFoundUserDirProps)&&(sPropName.Compare(PROJECTPROPNAME_USERDIRPROPERTY)==0)) { sDPPath = sPropVal; sDPPath.Replace(_T("\r\n"), _T("\n")); bFoundUserDirProps = TRUE; } if ((!bFoundAutoProps)&&(sPropName.Compare(PROJECTPROPNAME_AUTOPROPS)==0)) { sAutoProps = sPropVal; sAutoProps.Replace(_T("\r\n"), _T("\n")); bFoundAutoProps = TRUE; } if ((!bFoundWebViewRev)&&(sPropName.Compare(PROJECTPROPNAME_WEBVIEWER_REV)==0)) { sWebViewerRev = sPropVal; bFoundWebViewRev = TRUE; } if ((!bFoundWebViewPathRev)&&(sPropName.Compare(PROJECTPROPNAME_WEBVIEWER_PATHREV)==0)) { sWebViewerPathRev = sPropVal; bFoundWebViewPathRev = TRUE; } if ((!bFoundLogSummary)&&(sPropName.Compare(PROJECTPROPNAME_LOGSUMMARY)==0)) { sLogSummaryRe = sPropVal; bFoundLogSummary = TRUE; } } if (PathIsRoot(path.GetWinPath())) return FALSE; propsPath = path; path = path.GetContainingDirectory(); if ((!path.HasAdminDir())||(path.IsEmpty())) { if (bFoundBugtraqLabel | bFoundBugtraqMessage | bFoundBugtraqNumber | bFoundBugtraqURL | bFoundBugtraqWarnIssue | bFoundLogWidth | bFoundLogTemplate | bFoundBugtraqLogRe | bFoundMinLockMsgSize | bFoundUserFileProps | bFoundUserDirProps | bFoundAutoProps | bFoundWebViewRev | bFoundWebViewPathRev | bFoundLogSummary) { return TRUE; } propsPath.Reset(); return FALSE; } } #endif return FALSE; //never reached }
bool CRemoteCacheLink::GetStatusFromRemoteCache(const CTGitPath& Path, TGITCacheResponse* pReturnedStatus, bool bRecursive) { if(!EnsurePipeOpen()) { // We've failed to open the pipe - try and start the cache // but only if the last try to start the cache was a certain time // ago. If we just try over and over again without a small pause // in between, the explorer is rendered unusable! // Failing to start the cache can have different reasons: missing exe, // missing registry key, corrupt exe, ... if (((long)GetTickCount() - m_lastTimeout) < 0) return false; STARTUPINFO startup; PROCESS_INFORMATION process; memset(&startup, 0, sizeof(startup)); startup.cb = sizeof(startup); memset(&process, 0, sizeof(process)); CString sCachePath = CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TGitCache.exe"); #ifndef _WIN64 typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if (NULL != fnIsWow64Process) { BOOL bIsWow64 = false; if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { bIsWow64 = false; } if (bIsWow64) { CRegString tgitinstalled64 = CRegString(_T("Software\\TortoiseGit\\CachePath"), _T(""), false, HKEY_LOCAL_MACHINE, KEY_WOW64_64KEY); if (!CString(tgitinstalled64).IsEmpty()) sCachePath = tgitinstalled64; } } if (!CCreateProcessHelper::CreateProcessDetached(sCachePath, NULL)) { ATLTRACE("Failed to start x64 cache\n"); CString sCachePath = CPathUtils::GetAppDirectory(g_hmodThisDll) + _T("TGitCache.exe"); if (!CCreateProcessHelper::CreateProcessDetached(sCachePath, NULL)) { // It's not appropriate to do a message box here, because there may be hundreds of calls ATLTRACE("Failed to start cache\n"); return false; } } #else if (!CCreateProcessHelper::CreateProcessDetached(sCachePath, NULL)) { // It's not appropriate to do a message box here, because there may be hundreds of calls ATLTRACE("Failed to start cache\n"); return false; } #endif CloseHandle(process.hThread); CloseHandle(process.hProcess); sCachePath.ReleaseBuffer(); // Wait for the cache to open long endTime = (long)GetTickCount()+1000; while(!EnsurePipeOpen()) { if(((long)GetTickCount() - endTime) > 0) { m_lastTimeout = (long)GetTickCount()+10000; return false; } } } AutoLocker lock(m_critSec); DWORD nBytesRead; TGITCacheRequest request; request.flags = TGITCACHE_FLAGS_NONOTIFICATIONS; if(bRecursive) { request.flags |= TGITCACHE_FLAGS_RECUSIVE_STATUS; } wcsncpy_s(request.path, MAX_PATH+1, Path.GetWinPath(), MAX_PATH); SecureZeroMemory(&m_Overlapped, sizeof(OVERLAPPED)); m_Overlapped.hEvent = m_hEvent; // Do the transaction in overlapped mode. // That way, if anything happens which might block this call // we still can get out of it. We NEVER MUST BLOCK THE SHELL! // A blocked shell is a very bad user impression, because users // who don't know why it's blocked might find the only solution // to such a problem is a reboot and therefore they might lose // valuable data. // One particular situation where the shell could hang is when // the cache crashes and our crash report dialog comes up. // Sure, it would be better to have no situations where the shell // even can get blocked, but the timeout of 10 seconds is long enough // so that users still recognize that something might be wrong and // report back to us so we can investigate further. BOOL fSuccess = TransactNamedPipe(m_hPipe, &request, sizeof(request), pReturnedStatus, sizeof(*pReturnedStatus), &nBytesRead, &m_Overlapped); if (!fSuccess) { if (GetLastError()!=ERROR_IO_PENDING) { //OutputDebugStringA("TortoiseShell: TransactNamedPipe failed\n"); ClosePipe(); return false; } // TransactNamedPipe is working in an overlapped operation. // Wait for it to finish DWORD dwWait = WaitForSingleObject(m_hEvent, 10000); if (dwWait == WAIT_OBJECT_0) { fSuccess = GetOverlappedResult(m_hPipe, &m_Overlapped, &nBytesRead, FALSE); } else { // the cache didn't respond! fSuccess = FALSE; } } if (fSuccess) { return true; } ClosePipe(); return false; }
void CShellUpdater::WorkerThread() { HANDLE hWaitHandles[2]; hWaitHandles[0] = m_hTerminationEvent; hWaitHandles[1] = m_hWakeEvent; for(;;) { 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. ~CShellUpdater() is being executed) if(waitResult == WAIT_OBJECT_0 || waitResult == WAIT_ABANDONED_0 || waitResult == WAIT_ABANDONED_0+1) { // Termination event break; } // wait some time before we notify the shell Sleep(50); for(;;) { CTGitPath workingPath; if (!m_bRunning) return; Sleep(0); { AutoLocker lock(m_critSec); if(m_pathsToUpdate.empty()) { // Nothing left to do break; } if(m_bItemsAddedSinceLastUpdate) { m_pathsToUpdate.erase(std::unique(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), &CTGitPath::PredLeftEquivalentToRight), m_pathsToUpdate.end()); m_bItemsAddedSinceLastUpdate = false; } workingPath = m_pathsToUpdate.front(); m_pathsToUpdate.pop_front(); } if (workingPath.IsEmpty()) continue; ATLTRACE(_T("Update notifications for: %s\n"), workingPath.GetWinPath()); if (workingPath.IsDirectory()) { // 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()) CGitStatusCache::Instance().AddPathToWatch(workingPath); } // first send a notification about a sub folder change, so explorer doesn't discard // the folder notification. Since we only know for sure that the subversion admin // dir is present, we send a notification for that folder. CString admindir = workingPath.GetWinPathString() + _T("\\") + g_GitAdminDir.GetAdminDirName(); if(::PathFileExists(admindir)) SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, (LPCTSTR)admindir, NULL); SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL); // Sending an UPDATEDIR notification somehow overwrites/deletes the UPDATEITEM message. And without // that message, the folder overlays in the current view don't get updated without hitting F5. // Drawback is, without UPDATEDIR, the left tree view isn't always updated... SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL); } else SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL); } } _endthread(); }
void CDirectoryWatcher::WorkerThread() { DWORD numBytes; CDirWatchInfo * pdi = NULL; LPOVERLAPPED lpOverlapped; WCHAR buf[READ_DIR_CHANGE_BUFFER_SIZE] = {0}; WCHAR * pFound = NULL; while (m_bRunning) { CleanupWatchInfo(); if (watchedPaths.GetCount()) { // Any incoming notifications? pdi = NULL; numBytes = 0; InterlockedExchange(&m_bCleaned, FALSE); if ((!m_hCompPort) || !GetQueuedCompletionStatus(m_hCompPort, &numBytes, (PULONG_PTR) &pdi, &lpOverlapped, 600000 /*10 minutes*/)) { // No. Still trying? if (!m_bRunning) return; ATLTRACE(_T(": restarting watcher\n")); m_hCompPort.CloseHandle(); // We must sync the whole section because other threads may // receive "AddPath" calls that will delete the completion // port *while* we are adding references to it . AutoLocker lock(m_critSec); // Clear the list of watched objects and recreate that list. // This will also delete the old completion port ClearInfoMap(); CleanupWatchInfo(); for (int i=0; i<watchedPaths.GetCount(); ++i) { CTGitPath watchedPath = watchedPaths[i]; CAutoFile hDir = CreateFile(watchedPath.GetWinPath(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, //security attributes OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME. FILE_FLAG_OVERLAPPED, NULL); if (!hDir) { // this could happen if a watched folder has been removed/renamed ATLTRACE(_T("CDirectoryWatcher: CreateFile failed. Can't watch directory %s\n"), watchedPaths[i].GetWinPath()); watchedPaths.RemovePath(watchedPath); break; } DEV_BROADCAST_HANDLE NotificationFilter; SecureZeroMemory(&NotificationFilter, sizeof(NotificationFilter)); NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE); NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE; NotificationFilter.dbch_handle = hDir; // RegisterDeviceNotification sends a message to the UI thread: // make sure we *can* send it and that the UI thread isn't waiting on a lock int numPaths = watchedPaths.GetCount(); size_t numWatch = watchInfoMap.size(); lock.Unlock(); NotificationFilter.dbch_hdevnotify = RegisterDeviceNotification(hWnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE); lock.Lock(); // since we released the lock to prevent a deadlock with the UI thread, // it could happen that new paths were added to watch, or another thread // could have cleared our info map. // if that happened, we have to restart watching all paths again. if ((numPaths != watchedPaths.GetCount()) || (numWatch != watchInfoMap.size())) { ClearInfoMap(); CleanupWatchInfo(); Sleep(200); break; } CDirWatchInfo * pDirInfo = new CDirWatchInfo(hDir, watchedPath); hDir.Detach(); // the new CDirWatchInfo object owns the handle now pDirInfo->m_hDevNotify = NotificationFilter.dbch_hdevnotify; HANDLE port = CreateIoCompletionPort(pDirInfo->m_hDir, m_hCompPort, (ULONG_PTR)pDirInfo, 0); if (port == NULL) { ATLTRACE(_T("CDirectoryWatcher: CreateIoCompletionPort failed. Can't watch directory %s\n"), watchedPath.GetWinPath()); // we must close the directory handle to allow ClearInfoMap() // to close the completion port properly pDirInfo->CloseDirectoryHandle(); ClearInfoMap(); CleanupWatchInfo(); delete pDirInfo; pDirInfo = NULL; watchedPaths.RemovePath(watchedPath); break; } m_hCompPort = port; if (!ReadDirectoryChangesW(pDirInfo->m_hDir, pDirInfo->m_Buffer, READ_DIR_CHANGE_BUFFER_SIZE, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, &numBytes,// not used &pDirInfo->m_Overlapped, NULL)) //no completion routine! { ATLTRACE(_T("CDirectoryWatcher: ReadDirectoryChangesW failed. Can't watch directory %s\n"), watchedPath.GetWinPath()); // we must close the directory handle to allow ClearInfoMap() // to close the completion port properly pDirInfo->CloseDirectoryHandle(); ClearInfoMap(); CleanupWatchInfo(); delete pDirInfo; pDirInfo = NULL; watchedPaths.RemovePath(watchedPath); break; } ATLTRACE(_T("watching path %s\n"), pDirInfo->m_DirName.GetWinPath()); watchInfoMap[pDirInfo->m_hDir] = pDirInfo; } } else { if (!m_bRunning) return; if (watchInfoMap.empty()) continue; // NOTE: the longer this code takes to execute until ReadDirectoryChangesW // is called again, the higher the chance that we miss some // changes in the file system! if (pdi) { BOOL bRet = false; std::list<CTGitPath> notifyPaths; { AutoLocker lock(m_critSec); // in case the CDirectoryWatcher objects have been cleaned, // the m_bCleaned variable will be set to true here. If the // objects haven't been cleared, we can access them here. if (InterlockedExchange(&m_bCleaned, FALSE)) continue; if ( (!pdi->m_hDir) || watchInfoMap.empty() || (watchInfoMap.find(pdi->m_hDir) == watchInfoMap.end())) { continue; } PFILE_NOTIFY_INFORMATION pnotify = (PFILE_NOTIFY_INFORMATION)pdi->m_Buffer; DWORD nOffset = 0; do { pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset); if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE) break; nOffset = pnotify->NextEntryOffset; if (pnotify->FileNameLength >= (READ_DIR_CHANGE_BUFFER_SIZE*sizeof(TCHAR))) continue; SecureZeroMemory(buf, READ_DIR_CHANGE_BUFFER_SIZE*sizeof(TCHAR)); _tcsncpy_s(buf, pdi->m_DirPath, _countof(buf) - 1); errno_t err = _tcsncat_s(buf + pdi->m_DirPath.GetLength(), READ_DIR_CHANGE_BUFFER_SIZE-pdi->m_DirPath.GetLength(), pnotify->FileName, min(READ_DIR_CHANGE_BUFFER_SIZE-pdi->m_DirPath.GetLength(), pnotify->FileNameLength/sizeof(TCHAR))); if (err == STRUNCATE) { continue; } buf[(pnotify->FileNameLength/sizeof(TCHAR))+pdi->m_DirPath.GetLength()] = 0; if (m_FolderCrawler) { if ((pFound = wcsstr(buf, L"\\tmp")) != NULL) { pFound += 4; if (((*pFound)=='\\')||((*pFound)=='\0')) { continue; } } if ((pFound = wcsstr(buf, L":\\RECYCLER\\")) != NULL) { if ((pFound-buf) < 5) { // a notification for the recycle bin - ignore it continue; } } if ((pFound = wcsstr(buf, L":\\$Recycle.Bin\\")) != NULL) { if ((pFound-buf) < 5) { // a notification for the recycle bin - ignore it continue; } } if (wcsstr(buf, L".tmp") != NULL) { // assume files with a .tmp extension are not versioned and interesting, // so ignore them. continue; } CTGitPath path; bool isIndex = false; if ((pFound = wcsstr(buf, L".git")) != NULL) { // omit repository data change except .git/index.lock- or .git/HEAD.lock-files if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE) break; path = g_AdminDirMap.GetWorkingCopy(CTGitPath(buf).GetContainingDirectory().GetWinPathString()); if ((wcsstr(pFound, L"index.lock") != NULL || wcsstr(pFound, L"HEAD.lock") != NULL) && pnotify->Action == FILE_ACTION_ADDED) { CGitStatusCache::Instance().BlockPath(path); continue; } else if (((wcsstr(pFound, L"index.lock") != NULL || wcsstr(pFound, L"HEAD.lock") != NULL) && pnotify->Action == FILE_ACTION_REMOVED) || (((wcsstr(pFound, L"index") != NULL && wcsstr(pFound, L"index.lock") == NULL) || (wcsstr(pFound, L"HEAD") != NULL && wcsstr(pFound, L"HEAD.lock") != NULL)) && pnotify->Action == FILE_ACTION_MODIFIED) || ((wcsstr(pFound, L"index.lock") == NULL || wcsstr(pFound, L"HEAD.lock") != NULL) && pnotify->Action == FILE_ACTION_RENAMED_NEW_NAME)) { isIndex = true; CGitStatusCache::Instance().BlockPath(path, 1); } else { continue; } } else path.SetFromUnknown(buf); if(!path.HasAdminDir() && !isIndex) continue; ATLTRACE(_T("change notification: %s\n"), buf); notifyPaths.push_back(path); } } while ((nOffset > 0)&&(nOffset < READ_DIR_CHANGE_BUFFER_SIZE)); // setup next notification cycle SecureZeroMemory (pdi->m_Buffer, sizeof(pdi->m_Buffer)); SecureZeroMemory (&pdi->m_Overlapped, sizeof(OVERLAPPED)); bRet = ReadDirectoryChangesW(pdi->m_hDir, pdi->m_Buffer, READ_DIR_CHANGE_BUFFER_SIZE, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, &numBytes,// not used &pdi->m_Overlapped, NULL); //no completion routine! } if (!notifyPaths.empty()) { for (std::list<CTGitPath>::const_iterator nit = notifyPaths.begin(); nit != notifyPaths.end(); ++nit) { m_FolderCrawler->AddPathForUpdate(*nit); } } // any clean-up to do? CleanupWatchInfo(); if (!bRet) { // Since the call to ReadDirectoryChangesW failed, just // wait a while. We don't want to have this thread // running using 100% CPU if something goes completely // wrong. Sleep(200); } } } }// if (watchedPaths.GetCount()) else Sleep(200); }// while (m_bRunning) }
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 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; }
int CCachedDirectory::EnumFiles(const CTGitPath &path , bool IsFull) { CString sProjectRoot; path.HasAdminDir(&sProjectRoot); CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": EnumFiles %s\n"), path.GetWinPath()); ATLASSERT( !m_directoryPath.IsEmpty() ); CString sSubPath; CString s = path.GetWinPath(); if (s.GetLength() > sProjectRoot.GetLength()) { // skip initial slash if necessary if(s[sProjectRoot.GetLength()] == _T('\\')) sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() -1); else sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() ); } // strip "\" at the end, otherwise cache lookups for drives do not work correctly sProjectRoot.TrimRight(_T("\\")); GitStatus *pStatus = &CGitStatusCache::Instance().m_GitStatus; UNREFERENCED_PARAMETER(pStatus); git_wc_status_kind status = git_wc_status_none; if (!path.IsDirectory()) { bool assumeValid = false; bool skipWorktree = false; pStatus->GetFileStatus(sProjectRoot, sSubPath, &status, IsFull, false, true, GetStatusCallback, this, &assumeValid, &skipWorktree); if (status < m_mostImportantFileStatus) RefreshMostImportant(); } else { bool isSelf = path == m_directoryPath; if (isSelf) { AutoLocker lock(m_critSec); // clear subdirectory status cache m_childDirectories.clear(); // build new files status cache m_entryCache_tmp.clear(); } m_mostImportantFileStatus = git_wc_status_none; pStatus->EnumDirStatus(sProjectRoot, sSubPath, &status, IsFull, false, true, GetStatusCallback,this); m_mostImportantFileStatus = GitStatus::GetMoreImportant(m_mostImportantFileStatus, status); if (isSelf) { AutoLocker lock(m_critSec); // use a tmp files status cache so that we can still use the old cached values // for deciding whether we have to issue a shell notify m_entryCache = m_entryCache_tmp; m_entryCache_tmp.clear(); } // need to set/construct m_ownStatus (only unversioned and normal are valid values) m_ownStatus = git_wc_status_unversioned; m_ownStatus.SetKind(git_node_dir); if (m_mostImportantFileStatus > git_wc_status_unversioned) { git_wc_status2_t status2; status2.text_status = status2.prop_status = git_wc_status_normal; m_ownStatus.SetStatus(&status2); } else { if (::PathFileExists(m_directoryPath.GetWinPathString() + _T("\\.git"))) { git_wc_status2_t status2; status2.text_status = status2.prop_status = git_wc_status_normal; m_ownStatus.SetStatus(&status2); } else { git_wc_status2_t status2; status2.text_status = status2.prop_status = CalculateRecursiveStatus(); m_ownStatus.SetStatus(&status2); } } } return 0; }
/* 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; }
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; }
CStatusCacheEntry CCachedDirectory::GetStatusFromCache(const CTGitPath& path, bool bRecursive) { if(path.IsDirectory()) { // We don't have directory status in our cache // Ask the directory if it knows its own status CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path); if( dirEntry) { if (dirEntry->IsOwnStatusValid()) return dirEntry->GetOwnStatus(bRecursive); else { /* cache have outof date, need crawl again*/ /*AutoLocker lock(dirEntry->m_critSec); ChildDirStatus::const_iterator it; for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it) { CGitStatusCache::Instance().AddFolderForCrawling(it->first); }*/ CGitStatusCache::Instance().AddFolderForCrawling(path); /*Return old status during crawling*/ return dirEntry->GetOwnStatus(bRecursive); } } else { CGitStatusCache::Instance().AddFolderForCrawling(path); } return CStatusCacheEntry(); } else { //All file ignored if under ignore directory if (m_ownStatus.GetEffectiveStatus() == git_wc_status_ignored) return CStatusCacheEntry(git_wc_status_ignored); if (m_ownStatus.GetEffectiveStatus() == git_wc_status_unversioned) return CStatusCacheEntry(git_wc_status_unversioned); // Look up a file in our own cache AutoLocker lock(m_critSec); CString strCacheKey = GetCacheKey(path); CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey); if(itMap != m_entryCache.end()) { // We've hit the cache - check for timeout if(!itMap->second.HasExpired((long)GetTickCount())) { if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime())) { if ((itMap->second.GetEffectiveStatus()!=git_wc_status_missing)||(!PathFileExists(path.GetWinPath()))) { // Note: the filetime matches after a modified has been committed too. // So in that case, we would return a wrong status (e.g. 'modified' instead // of 'normal') here. return itMap->second; } } } } CGitStatusCache::Instance().AddFolderForCrawling(path.GetContainingDirectory()); return CStatusCacheEntry(); } }
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()) { CString menuText; popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF); popup.AppendMenuIcon(ID_GNUDIFFCOMPARE, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF); popup.AppendMenu(MF_SEPARATOR, NULL); menuText.Format(IDS_FILEDIFF_POPREVERTTOREV, m_rev1.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength())); popup.AppendMenuIcon(ID_REVERT1, menuText, IDI_REVERT); menuText.Format(IDS_FILEDIFF_POPREVERTTOREV, m_rev2.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength())); popup.AppendMenuIcon(ID_REVERT2, menuText, IDI_REVERT); popup.AppendMenu(MF_SEPARATOR, NULL); popup.AppendMenuIcon(ID_LOG, IDS_FILEDIFF_LOG, IDI_LOG); popup.AppendMenuIcon(ID_BLAME, IDS_FILEDIFF_POPBLAME, IDI_BLAME); 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_GNUDIFFCOMPARE: { POSITION pos = m_cFileList.GetFirstSelectedItemPosition(); while (pos) { CTGitPath *fd2 = m_arFilteredList[m_cFileList.GetNextSelectedItem(pos)]; CTGitPath *fd1 = fd2; if (fd2->m_Action & CTGitPath::LOGACTIONS_REPLACED) fd1 = new CTGitPath(fd2->GetGitOldPathString()); CAppUtils::StartShowUnifiedDiff(m_hWnd, *fd2, m_rev2.m_CommitHash.ToString(), *fd1, m_rev1.m_CommitHash.ToString()); } } break; case ID_REVERT1: RevertSelectedItemToVersion(m_rev1.m_CommitHash.ToString()); break; case ID_REVERT2: RevertSelectedItemToVersion(m_rev2.m_CommitHash.ToString()); 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::RunTortoiseGitProc(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->GetContainingDirectory().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(IDS_STATUSLIST_CHECKOUTFILEFAILED, fd->GetGitPathString(), m_rev1.m_CommitHash.ToString(), filename); if (CMessageBox::Show(NULL, out, _T("TortoiseGit"), 2, IDI_WARNING, CString(MAKEINTRESOURCE(IDS_IGNOREBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2) return; } } } } } break; } } }
bool PasteCopyCommand::Execute() { CString sDroppath = parser.GetVal(_T("droptarget")); CTGitPath dropPath(sDroppath); ProjectProperties props; props.ReadProps(dropPath); if (dropPath.IsAdminDir()) return FALSE; if(!dropPath.HasAdminDir(&g_Git.m_CurrentDir)) return FALSE; //SVN svn; //SVNStatus status; unsigned long count = 0; CString sNewName; orgPathList.RemoveAdminPaths(); CSysProgressDlg progress; progress.SetTitle(IDS_PROC_COPYING); progress.SetAnimation(IDR_MOVEANI); progress.SetTime(true); progress.ShowModeless(CWnd::FromHandle(hwndExplorer)); for(int nPath = 0; nPath < orgPathList.GetCount(); nPath++) { const CTGitPath& sourcePath = orgPathList[nPath]; CTGitPath fullDropPath = dropPath; if (sNewName.IsEmpty()) fullDropPath.AppendPathString(sourcePath.GetFileOrDirectoryName()); else fullDropPath.AppendPathString(sNewName); // Check for a drop-on-to-ourselves if (sourcePath.IsEquivalentTo(fullDropPath)) { // Offer a rename progress.Stop(); CRenameDlg dlg; dlg.m_windowtitle.Format(IDS_PROC_NEWNAMECOPY, (LPCTSTR)sourcePath.GetUIFileOrDirectoryName()); if (dlg.DoModal() != IDOK) { return FALSE; } // rebuild the progress dialog progress.EnsureValid(); progress.SetTitle(IDS_PROC_COPYING); progress.SetAnimation(IDR_MOVEANI); progress.SetTime(true); progress.SetProgress(count, orgPathList.GetCount()); progress.ShowModeless(CWnd::FromHandle(hwndExplorer)); // Rebuild the destination path, with the new name fullDropPath.SetFromUnknown(sDroppath); fullDropPath.AppendPathString(dlg.m_name); } //svn_wc_status_kind s = status.GetAllStatus(sourcePath); //if ((s == svn_wc_status_none)||(s == svn_wc_status_unversioned)||(s == svn_wc_status_ignored)) { // source file is unversioned: move the file to the target, then add it CopyFile(sourcePath.GetWinPath(), fullDropPath.GetWinPath(), FALSE); CString cmd,output; cmd.Format(_T("git.exe add \"%s\""),fullDropPath.GetWinPath()); if( g_Git.Run(cmd,&output,CP_ACP)) { TRACE(_T("%s\n"), (LPCTSTR)output); CMessageBox::Show(hwndExplorer, output, _T("TortoiseGit"), MB_ICONERROR); return FALSE; //get out of here } else CShellUpdater::Instance().AddPathForUpdate(fullDropPath); } //else //{ // if (!svn.Copy(CTSVNPathList(sourcePath), fullDropPath, SVNRev::REV_WC, SVNRev())) // { // TRACE(_T("%s\n"), (LPCTSTR)svn.GetLastErrorMessage()); // CMessageBox::Show(hwndExplorer, svn.GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR); // return FALSE; //get out of here // } // else // CShellUpdater::Instance().AddPathForUpdate(fullDropPath); //} count++; if (progress.IsValid()) { progress.FormatPathLine(1, IDS_PROC_COPYINGPROG, sourcePath.GetWinPath()); progress.FormatPathLine(2, IDS_PROC_CPYMVPROG2, fullDropPath.GetWinPath()); progress.SetProgress(count, orgPathList.GetCount()); } if ((progress.IsValid())&&(progress.HasUserCancelled())) { CMessageBox::Show(hwndExplorer, IDS_SVN_USERCANCELLED, IDS_APPNAME, MB_ICONINFORMATION); return false; } } return true; }
CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTGitPath& path, bool bRecursive, bool bFetch /* = true */) { CString sProjectRoot; bool bIsVersionedPath; bool bRequestForSelf = false; if(path.IsEquivalentToWithoutCase(m_directoryPath)) { bRequestForSelf = true; AutoLocker lock(m_critSec); // HasAdminDir might modify m_directoryPath, so we need to do it synchronized bIsVersionedPath = m_directoryPath.HasAdminDir(&sProjectRoot); } else bIsVersionedPath = path.HasAdminDir(&sProjectRoot); // In all most circumstances, we ask for the status of a member of this directory. ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf); //If is not version control path if( !bIsVersionedPath) { CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": %s is not underversion control\n"), path.GetWinPath()); return CStatusCacheEntry(); } // We've not got this item in the cache - let's add it // We never bother asking SVN for the status of just one file, always for its containing directory if (GitAdminDir::IsAdminDirPath(path.GetWinPathString())) { // We're being asked for the status of an .git directory // It's not worth asking for this return CStatusCacheEntry(); } if(bFetch) { return GetStatusFromGit(path, sProjectRoot); } else { return GetStatusFromCache(path, bRecursive); } }