int CTGitPath::GetAdminDirMask() const { int status = 0; CString topdir; if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir)) { return status; } // ITEMIS_INGIT will be revoked if necessary in TortoiseShell/ContextMenu.cpp status |= ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER; if (IsDirectory()) { status |= ITEMIS_FOLDERINGIT; if (IsWCRoot()) { status |= ITEMIS_WCROOT; CString topProjectDir; if (GitAdminDir::HasAdminDir(GetWinPathString(), false, &topProjectDir)) { if (PathFileExists(topProjectDir + _T("\\.gitmodules"))) { CAutoConfig config(true); git_config_add_file_ondisk(config, CGit::GetGitPathStringA(topProjectDir + _T("\\.gitmodules")), GIT_CONFIG_LEVEL_APP, FALSE); CString relativePath = GetWinPathString().Mid(topProjectDir.GetLength()); relativePath.Replace(_T("\\"), _T("/")); relativePath.Trim(_T("/")); CStringA submodulePath = CUnicodeUtils::GetUTF8(relativePath); if (git_config_foreach_match(config, "submodule\\..*\\.path", [](const git_config_entry *entry, void *data) { return entry->value == *(CStringA *)data ? GIT_EUSER : 0; }, &submodulePath) == GIT_EUSER) status |= ITEMIS_SUBMODULE; } } } } CString dotGitPath; GitAdminDir::GetAdminDirPath(topdir, dotGitPath); if (PathFileExists(dotGitPath + _T("BISECT_START"))) status |= ITEMIS_BISECT; if (PathFileExists(dotGitPath + _T("MERGE_HEAD"))) status |= ITEMIS_MERGEACTIVE; if (PathFileExists(dotGitPath + _T("refs\\stash"))) status |= ITEMIS_STASH; if (PathFileExists(dotGitPath + _T("svn"))) status |= ITEMIS_GITSVN; if (PathFileExists(topdir + _T("\\.gitmodules"))) status |= ITEMIS_SUBMODULECONTAINER; return status; }
CTGitPath CTGitPath::GetSubPath(const CTGitPath &root) { CTGitPath path; if(GetWinPathString().Left(root.GetWinPathString().GetLength()) == root.GetWinPathString()) { CString str=GetWinPathString(); path.SetFromWin(str.Right(str.GetLength()-root.GetWinPathString().GetLength()-1)); } return path; }
CTGitPath CTGitPathList::GetCommonRoot() const { if (IsEmpty()) return CTGitPath(); if (GetCount() == 1) return m_paths[0]; // first entry is common root for itself // (add trailing '\\' to detect partial matches of the last path element) CString root = m_paths[0].GetWinPathString() + _T('\\'); int rootLength = root.GetLength(); // determine common path string prefix for (auto it = m_paths.cbegin() + 1; it != m_paths.cend(); ++it) { CString path = it->GetWinPathString() + _T('\\'); int newLength = CStringUtils::GetMatchingLength(root, path); if (newLength != rootLength) { root.Delete(newLength, rootLength); rootLength = newLength; } } // remove the last (partial) path element if (rootLength > 0) root.Delete(root.ReverseFind(_T('\\')), rootLength); // done return CTGitPath(root); }
const CString& CTGitPath::GetUIPathString() const { if (m_sUIPath.IsEmpty()) { m_sUIPath = GetWinPathString(); } return m_sUIPath; }
bool CTGitPath::HasRebaseApply() const { CString topdir; if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir)) { return false; } CString dotGitPath; GitAdminDir::GetAdminDirPath(topdir, dotGitPath); return !!PathFileExists(dotGitPath + _T("rebase-apply")); }
bool CTGitPath::IsMergeActive() const { CString topdir; if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir)) { return false; } CString dotGitPath; GitAdminDir::GetAdminDirPath(topdir, dotGitPath); return !!PathFileExists(dotGitPath + _T("MERGE_HEAD")); }
bool CTGitPath::IsBisectActive() const { CString topdir; if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir)) { return false; } CString dotGitPath; GitAdminDir::GetAdminDirPath(topdir, dotGitPath); return !!PathFileExists(dotGitPath + _T("BISECT_START")); }
bool CTGitPath::HasGitSVNDir() const { CString topdir; if (!GitAdminDir::HasAdminDir(GetWinPathString(), &topdir)) { return false; } CString dotGitPath; GitAdminDir::GetAdminDirPath(topdir, dotGitPath); return !!PathFileExists(dotGitPath + _T("svn")); }
const CString& CTSVNPath::GetUIPathString() const { if (m_sUIPath.IsEmpty()) { #if defined(_MFC_VER) //BUGBUG HORRIBLE!!! - CPathUtils::IsEscaped doesn't need to be MFC-only if (IsUrl()) { m_sUIPath = CPathUtils::PathUnescape(GetSVNPathString()); m_sUIPath.Replace(L"file:////", L"file://"); } else #endif { m_sUIPath = GetWinPathString(); } } return m_sUIPath; }
bool CTGitPath::HasStashDir() const { CString topdir; if(!GitAdminDir::HasAdminDir(GetWinPathString(),&topdir)) { return false; } CString dotGitPath; GitAdminDir::GetAdminDirPath(topdir, dotGitPath); if (!!PathFileExists(dotGitPath + _T("refs\\stash"))) return true; CAutoFile hfile = CreateFile(dotGitPath + _T("packed-refs"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (!hfile) return false; DWORD filesize = ::GetFileSize(hfile, nullptr); if (filesize == 0) return false; DWORD size = 0; auto buff = std::make_unique<char[]>(filesize + 1); ReadFile(hfile, buff.get(), filesize, &size, nullptr); buff.get()[filesize] = '\0'; if (size != filesize) return false; for (DWORD i = 0; i < filesize;) { if (buff[i] == '#' || buff[i] == '^') { while (buff[i] != '\n') { ++i; if (i == filesize) break; } ++i; } if (i >= filesize) break; while (buff[i] != ' ') { ++i; if (i == filesize) break; } ++i; if (i >= filesize) break; if (i <= filesize - 10 && (buff[i + 10] == '\n' || buff[i + 10] == '\0') && !strncmp("refs/stash", buff.get() + i, 10)) return true; while (buff[i] != '\n') { ++i; if (i == filesize) break; } while (buff[i] == '\n') { ++i; if (i == filesize) break; } } return false; }
////////////////////////////////////////////////////////////////////////// // IDataObject ////////////////////////////////////////////////////////////////////////// STDMETHODIMP GitDataObject::GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { if (!pformatetcIn) return E_INVALIDARG; if (!pmedium) return E_POINTER; pmedium->hGlobal = nullptr; if ((pformatetcIn->tymed & TYMED_ISTREAM) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILECONTENTS)) { // supports the IStream format. // The lindex param is the index of the file to return CString filepath; IStream* pIStream = nullptr; // Note: we don't get called for directories since those are simply created and don't // need to be fetched. // Note2: It would be really nice if we could get a stream from the subversion library // from where we could read the file contents. But the Subversion lib is not implemented // to *read* from a remote stream but so that the library *writes* to a stream we pass. // Since we can't get such a read stream, we have to fetch the file in whole first to // a temp location and then pass the shell an IStream for that temp file. if (m_revision.IsEmpty()) { if ((pformatetcIn->lindex >= 0) && (pformatetcIn->lindex < (LONG)m_allPaths.size())) filepath = g_Git.CombinePath(m_allPaths[pformatetcIn->lindex]); } else { filepath = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString(); if ((pformatetcIn->lindex >= 0) && (pformatetcIn->lindex < (LONG)m_allPaths.size())) { if (g_Git.GetOneFile(m_revision.ToString(), m_allPaths[pformatetcIn->lindex], filepath)) { DeleteFile(filepath); return STG_E_ACCESSDENIED; } } } HRESULT res = SHCreateStreamOnFileEx(filepath, STGM_READ, FILE_ATTRIBUTE_NORMAL, FALSE, nullptr, &pIStream); if (res == S_OK) { // http://blogs.msdn.com/b/oldnewthing/archive/2014/09/18/10558763.aspx LARGE_INTEGER liZero = { 0, 0 }; pIStream->Seek(liZero, STREAM_SEEK_END, nullptr); pmedium->pstm = pIStream; pmedium->tymed = TYMED_ISTREAM; return S_OK; } return res; } else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILEDESCRIPTOR)) { for (int i = 0; i < m_gitPaths.GetCount(); ++i) { if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory()) continue; m_allPaths.push_back(m_gitPaths[i]); } size_t dataSize = sizeof(FILEGROUPDESCRIPTOR) + ((max(1, m_allPaths.size()) - 1) * sizeof(FILEDESCRIPTOR)); HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, dataSize); FILEGROUPDESCRIPTOR* files = (FILEGROUPDESCRIPTOR*)GlobalLock(data); files->cItems = static_cast<UINT>(m_allPaths.size()); int index = 0; for (auto it = m_allPaths.cbegin(); it != m_allPaths.cend(); ++it) { CString temp(m_iStripLength > 0 ? it->GetWinPathString().Mid(m_iStripLength + 1) : (m_iStripLength == 0 ? it->GetWinPathString() : it->GetUIFileOrDirectoryName())); if (temp.GetLength() < MAX_PATH) wcscpy_s(files->fgd[index].cFileName, (LPCTSTR)temp); else { files->cItems--; continue; } files->fgd[index].dwFlags = FD_ATTRIBUTES | FD_PROGRESSUI | FD_FILESIZE | FD_LINKUI; files->fgd[index].dwFileAttributes = FILE_ATTRIBUTE_NORMAL; // Always set the file size to 0 even if we 'know' the file size (infodata.size64). // Because for text files, the file size is too low due to the EOLs getting converted // to CRLF (from LF as stored in the repository). And a too low file size set here // prevents the shell from reading the full file later - it only reads the stream up // to the number of bytes specified here. Which means we would end up with a truncated // text file (binary files are still ok though). files->fgd[index].nFileSizeLow = 0; files->fgd[index].nFileSizeHigh = 0; ++index; } GlobalUnlock(data); pmedium->hGlobal = data; pmedium->tymed = TYMED_HGLOBAL; return S_OK; } // handling CF_PREFERREDDROPEFFECT is necessary to tell the shell that it should *not* ask for the // CF_FILEDESCRIPTOR until the drop actually occurs. If we don't handle CF_PREFERREDDROPEFFECT, the shell // will ask for the file descriptor for every object (file/folder) the mouse pointer hovers over and is // a potential drop target. else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->cfFormat == CF_PREFERREDDROPEFFECT)) { HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, sizeof(DWORD)); DWORD* effect = (DWORD*)GlobalLock(data); (*effect) = DROPEFFECT_COPY; GlobalUnlock(data); pmedium->hGlobal = data; pmedium->tymed = TYMED_HGLOBAL; return S_OK; } else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_TEXT)) { // caller wants text // create the string from the path list CString text; if (!m_gitPaths.IsEmpty()) { // create a single string where the URLs are separated by newlines for (int i = 0; i < m_gitPaths.GetCount(); ++i) { text += m_gitPaths[i].GetWinPathString(); text += L"\r\n"; } } CStringA texta = CUnicodeUtils::GetUTF8(text); pmedium->tymed = TYMED_HGLOBAL; pmedium->hGlobal = GlobalAlloc(GHND, (texta.GetLength() + 1) * sizeof(char)); if (pmedium->hGlobal) { char* pMem = (char*)GlobalLock(pmedium->hGlobal); strcpy_s(pMem, texta.GetLength() + 1, (LPCSTR)texta); GlobalUnlock(pmedium->hGlobal); } pmedium->pUnkForRelease = nullptr; return S_OK; } else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && ((pformatetcIn->cfFormat == CF_UNICODETEXT) || (pformatetcIn->cfFormat == CF_INETURL) || (pformatetcIn->cfFormat == CF_SHELLURL))) { // caller wants Unicode text // create the string from the path list CString text; if (!m_gitPaths.IsEmpty()) { // create a single string where the URLs are separated by newlines for (int i = 0; i < m_gitPaths.GetCount(); ++i) { if (pformatetcIn->cfFormat == CF_UNICODETEXT) text += m_gitPaths[i].GetWinPathString(); else text += g_Git.CombinePath(m_gitPaths[i]); text += L"\r\n"; } } pmedium->tymed = TYMED_HGLOBAL; pmedium->hGlobal = GlobalAlloc(GHND, (text.GetLength() + 1) * sizeof(TCHAR)); if (pmedium->hGlobal) { TCHAR* pMem = (TCHAR*)GlobalLock(pmedium->hGlobal); wcscpy_s(pMem, text.GetLength() + 1, (LPCTSTR)text); GlobalUnlock(pmedium->hGlobal); } pmedium->pUnkForRelease = nullptr; return S_OK; } else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_HDROP)) { int nLength = 0; for (int i = 0; i < m_gitPaths.GetCount(); ++i) { if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory()) continue; nLength += g_Git.CombinePath(m_gitPaths[i]).GetLength(); nLength += 1; // '\0' separator } int nBufferSize = sizeof(DROPFILES) + (nLength + 1) * sizeof(TCHAR); auto pBuffer = std::make_unique<char[]>(nBufferSize); SecureZeroMemory(pBuffer.get(), nBufferSize); DROPFILES* df = (DROPFILES*)pBuffer.get(); df->pFiles = sizeof(DROPFILES); df->fWide = 1; TCHAR* pFilenames = (TCHAR*)(pBuffer.get() + sizeof(DROPFILES)); TCHAR* pCurrentFilename = pFilenames; for (int i = 0; i < m_gitPaths.GetCount(); ++i) { if (m_gitPaths[i].m_Action & (CTGitPath::LOGACTIONS_MISSING | CTGitPath::LOGACTIONS_DELETED) || m_gitPaths[i].IsDirectory()) continue; CString str = g_Git.CombinePath(m_gitPaths[i]); wcscpy_s(pCurrentFilename, str.GetLength() + 1, (LPCWSTR)str); pCurrentFilename += str.GetLength(); *pCurrentFilename = '\0'; // separator between file names pCurrentFilename++; } *pCurrentFilename = '\0'; // terminate array pmedium->tymed = TYMED_HGLOBAL; pmedium->hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, nBufferSize); if (pmedium->hGlobal) { LPVOID pMem = ::GlobalLock(pmedium->hGlobal); if (pMem) memcpy(pMem, pBuffer.get(), nBufferSize); GlobalUnlock(pmedium->hGlobal); } pmedium->pUnkForRelease = nullptr; return S_OK; } else if ((pformatetcIn->tymed & TYMED_HGLOBAL) && (pformatetcIn->dwAspect == DVASPECT_CONTENT) && (pformatetcIn->cfFormat == CF_FILE_ATTRIBUTES_ARRAY)) { int nBufferSize = sizeof(FILE_ATTRIBUTES_ARRAY) + m_gitPaths.GetCount() * sizeof(DWORD); auto pBuffer = std::make_unique<char[]>(nBufferSize); SecureZeroMemory(pBuffer.get(), nBufferSize); FILE_ATTRIBUTES_ARRAY* cf = (FILE_ATTRIBUTES_ARRAY*)pBuffer.get(); cf->cItems = m_gitPaths.GetCount(); cf->dwProductFileAttributes = DWORD_MAX; cf->dwSumFileAttributes = 0; for (int i = 0; i < m_gitPaths.GetCount(); ++i) { DWORD fileattribs = FILE_ATTRIBUTE_NORMAL; cf->rgdwFileAttributes[i] = fileattribs; cf->dwProductFileAttributes &= fileattribs; cf->dwSumFileAttributes |= fileattribs; } pmedium->tymed = TYMED_HGLOBAL; pmedium->hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, nBufferSize); if (pmedium->hGlobal) { LPVOID pMem = ::GlobalLock(pmedium->hGlobal); if (pMem) memcpy(pMem, pBuffer.get(), nBufferSize); GlobalUnlock(pmedium->hGlobal); } pmedium->pUnkForRelease = nullptr; return S_OK; } for (size_t i = 0; i < m_vecFormatEtc.size(); ++i) { if ((pformatetcIn->tymed == m_vecFormatEtc[i]->tymed) && (pformatetcIn->dwAspect == m_vecFormatEtc[i]->dwAspect) && (pformatetcIn->cfFormat == m_vecFormatEtc[i]->cfFormat)) { CopyMedium(pmedium, m_vecStgMedium[i], m_vecFormatEtc[i]); return S_OK; } } return DV_E_FORMATETC; }