From 7ab99eccc9dcef68cab287d7da59bebfeadcc290 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Sat, 8 Nov 2008 09:39:52 +0800 Subject: [PATCH] Context Menu Showed --- TortoiseShell/ContextMenu.cpp | 202 +++--- TortoiseShell/GitStatus.h | 10 +- TortoiseShell/TGitPath.cpp | 1538 ++++++++++++++++++++++++++++++++++++++++- TortoiseShell/TGitPath.h | 3 +- TortoiseShell/TortoiseSVN.cpp | 2 +- TortoiseShell/resource.rc | 36 +- 6 files changed, 1672 insertions(+), 119 deletions(-) diff --git a/TortoiseShell/ContextMenu.cpp b/TortoiseShell/ContextMenu.cpp index e895dfa..6f8267e 100644 --- a/TortoiseShell/ContextMenu.cpp +++ b/TortoiseShell/ContextMenu.cpp @@ -49,14 +49,14 @@ CShellExt::MenuInfo CShellExt::menuInfo[] = { ShellMenuPrevDiff, MENUPREVDIFF, IDI_DIFF, IDS_MENUPREVDIFF, IDS_MENUDESCPREVDIFF, ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_FOLDER, 0, 0, 0, 0, 0, 0 }, - { ShellMenuUrlDiff, MENUURLDIFF, IDI_DIFF, IDS_MENUURLDIFF, IDS_MENUDESCURLDIFF, - ITEMIS_INSVN|ITEMIS_ONLYONE|ITEMIS_EXTENDED, 0, ITEMIS_FOLDERINSVN|ITEMIS_EXTENDED|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 }, +// { ShellMenuUrlDiff, MENUURLDIFF, IDI_DIFF, IDS_MENUURLDIFF, IDS_MENUDESCURLDIFF, +// ITEMIS_INSVN|ITEMIS_ONLYONE|ITEMIS_EXTENDED, 0, ITEMIS_FOLDERINSVN|ITEMIS_EXTENDED|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 }, { ShellMenuLog, MENULOG, IDI_LOG, IDS_MENULOG, IDS_MENUDESCLOG, ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, 0, 0 }, - { ShellMenuRepoBrowse, MENUREPOBROWSE, IDI_REPOBROWSE, IDS_MENUREPOBROWSE, IDS_MENUDESCREPOBROWSE, - ITEMIS_ONLYONE, 0, ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 }, +// { ShellMenuRepoBrowse, MENUREPOBROWSE, IDI_REPOBROWSE, IDS_MENUREPOBROWSE, IDS_MENUDESCREPOBROWSE, +// ITEMIS_ONLYONE, 0, ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 }, { ShellMenuShowChanged, MENUSHOWCHANGED, IDI_SHOWCHANGED, IDS_MENUSHOWCHANGED, IDS_MENUDESCSHOWCHANGED, ITEMIS_INSVN|ITEMIS_ONLYONE, 0, ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0}, @@ -72,8 +72,8 @@ CShellExt::MenuInfo CShellExt::menuInfo[] = { ShellMenuResolve, MENURESOLVE, IDI_RESOLVE, IDS_MENURESOLVE, IDS_MENUDESCRESOLVE, ITEMIS_INSVN|ITEMIS_CONFLICTED, 0, ITEMIS_INSVN|ITEMIS_FOLDER, 0, ITEMIS_FOLDERINSVN, 0, 0, 0 }, - { ShellMenuUpdateExt, MENUUPDATEEXT, IDI_UPDATE, IDS_MENUUPDATEEXT, IDS_MENUDESCUPDATEEXT, - ITEMIS_INSVN, ITEMIS_ADDED, ITEMIS_FOLDERINSVN, ITEMIS_ADDED, 0, 0, 0, 0 }, +// { ShellMenuUpdateExt, MENUUPDATEEXT, IDI_UPDATE, IDS_MENUUPDATEEXT, IDS_MENUDESCUPDATEEXT, +// ITEMIS_INSVN, ITEMIS_ADDED, ITEMIS_FOLDERINSVN, ITEMIS_ADDED, 0, 0, 0, 0 }, { ShellMenuRename, MENURENAME, IDI_RENAME, IDS_MENURENAME, IDS_MENUDESCRENAME, ITEMIS_INSVN|ITEMIS_ONLYONE|ITEMIS_INVERSIONEDFOLDER, 0, 0, 0, 0, 0, 0, 0 }, @@ -96,22 +96,22 @@ CShellExt::MenuInfo CShellExt::menuInfo[] = { ShellMenuCleanup, MENUCLEANUP, IDI_CLEANUP, IDS_MENUCLEANUP, IDS_MENUDESCCLEANUP, ITEMIS_INSVN|ITEMIS_FOLDER, 0, ITEMIS_FOLDERINSVN|ITEMIS_FOLDER, 0, 0, 0, 0, 0 }, - { ShellMenuLock, MENULOCK, IDI_LOCK, IDS_MENU_LOCK, IDS_MENUDESC_LOCK, - ITEMIS_INSVN, ITEMIS_LOCKED|ITEMIS_ADDED, ITEMIS_FOLDERINSVN, ITEMIS_LOCKED|ITEMIS_ADDED, 0, 0, 0, 0 }, +// { ShellMenuLock, MENULOCK, IDI_LOCK, IDS_MENU_LOCK, IDS_MENUDESC_LOCK, +// ITEMIS_INSVN, ITEMIS_LOCKED|ITEMIS_ADDED, ITEMIS_FOLDERINSVN, ITEMIS_LOCKED|ITEMIS_ADDED, 0, 0, 0, 0 }, - { ShellMenuUnlock, MENUUNLOCK, IDI_UNLOCK, IDS_MENU_UNLOCK, IDS_MENUDESC_UNLOCK, - ITEMIS_INSVN|ITEMIS_LOCKED, 0, ITEMIS_FOLDER|ITEMIS_INSVN, 0, ITEMIS_FOLDERINSVN, 0, 0, 0 }, +// { ShellMenuUnlock, MENUUNLOCK, IDI_UNLOCK, IDS_MENU_UNLOCK, IDS_MENUDESC_UNLOCK, +// ITEMIS_INSVN|ITEMIS_LOCKED, 0, ITEMIS_FOLDER|ITEMIS_INSVN, 0, ITEMIS_FOLDERINSVN, 0, 0, 0 }, - { ShellMenuUnlockForce, MENUUNLOCK, IDI_UNLOCK, IDS_MENU_UNLOCKFORCE, IDS_MENUDESC_UNLOCKFORCE, - ITEMIS_INSVN|ITEMIS_LOCKED, 0, ITEMIS_FOLDER|ITEMIS_INSVN|ITEMIS_EXTENDED, 0, 0, 0, 0, 0 }, +// { ShellMenuUnlockForce, MENUUNLOCK, IDI_UNLOCK, IDS_MENU_UNLOCKFORCE, IDS_MENUDESC_UNLOCKFORCE, +// ITEMIS_INSVN|ITEMIS_LOCKED, 0, ITEMIS_FOLDER|ITEMIS_INSVN|ITEMIS_EXTENDED, 0, 0, 0, 0, 0 }, { ShellSeparator, 0, 0, 0, 0, 0, 0, 0, 0}, - { ShellMenuCopy, MENUCOPY, IDI_COPY, IDS_MENUBRANCH, IDS_MENUDESCCOPY, - ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 }, +// { ShellMenuCopy, MENUCOPY, IDI_COPY, IDS_MENUBRANCH, IDS_MENUDESCCOPY, +// ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 }, - { ShellMenuSwitch, MENUSWITCH, IDI_SWITCH, IDS_MENUSWITCH, IDS_MENUDESCSWITCH, - ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 }, +// { ShellMenuSwitch, MENUSWITCH, IDI_SWITCH, IDS_MENUSWITCH, IDS_MENUDESCSWITCH, +// ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 }, { ShellMenuMerge, MENUMERGE, IDI_MERGE, IDS_MENUMERGE, IDS_MENUDESCMERGE, ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_ADDED, ITEMIS_FOLDER|ITEMIS_FOLDERINSVN|ITEMIS_ONLYONE, 0, 0, 0, 0, 0 }, @@ -135,17 +135,17 @@ CShellExt::MenuInfo CShellExt::menuInfo[] = { ShellMenuAddAsReplacement, MENUADD, IDI_ADD, IDS_MENUADDASREPLACEMENT, IDS_MENUADDASREPLACEMENT, ITEMIS_DELETED|ITEMIS_ONLYONE, ITEMIS_FOLDER, 0, 0, 0, 0, 0, 0 }, - { ShellMenuImport, MENUIMPORT, IDI_IMPORT, IDS_MENUIMPORT, IDS_MENUDESCIMPORT, - ITEMIS_FOLDER, ITEMIS_INSVN, 0, 0, 0, 0, 0, 0 }, +// { ShellMenuImport, MENUIMPORT, IDI_IMPORT, IDS_MENUIMPORT, IDS_MENUDESCIMPORT, +// ITEMIS_FOLDER, ITEMIS_INSVN, 0, 0, 0, 0, 0, 0 }, - { ShellMenuBlame, MENUBLAME, IDI_BLAME, IDS_MENUBLAME, IDS_MENUDESCBLAME, - ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_FOLDER|ITEMIS_ADDED, 0, 0, 0, 0, 0, 0 }, +// { ShellMenuBlame, MENUBLAME, IDI_BLAME, IDS_MENUBLAME, IDS_MENUDESCBLAME, +// ITEMIS_INSVN|ITEMIS_ONLYONE, ITEMIS_FOLDER|ITEMIS_ADDED, 0, 0, 0, 0, 0, 0 }, - { ShellMenuIgnoreSub, MENUIGNORE, IDI_IGNORE, IDS_MENUIGNORE, IDS_MENUDESCIGNORE, - ITEMIS_INVERSIONEDFOLDER, ITEMIS_IGNORED|ITEMIS_INSVN, 0, 0, 0, 0, 0, 0 }, +// { ShellMenuIgnoreSub, MENUIGNORE, IDI_IGNORE, IDS_MENUIGNORE, IDS_MENUDESCIGNORE, +// ITEMIS_INVERSIONEDFOLDER, ITEMIS_IGNORED|ITEMIS_INSVN, 0, 0, 0, 0, 0, 0 }, - { ShellMenuUnIgnoreSub, MENUIGNORE, IDI_IGNORE, IDS_MENUUNIGNORE, IDS_MENUDESCUNIGNORE, - ITEMIS_IGNORED, 0, 0, 0, 0, 0, 0, 0 }, +// { ShellMenuUnIgnoreSub, MENUIGNORE, IDI_IGNORE, IDS_MENUUNIGNORE, IDS_MENUDESCUNIGNORE, +// ITEMIS_IGNORED, 0, 0, 0, 0, 0, 0, 0 }, { ShellSeparator, 0, 0, 0, 0, 0, 0, 0, 0}, @@ -193,7 +193,7 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY /* hRegKey */) { -#if 0 + ATLTRACE("Shell :: Initialize\n"); PreserveChdir preserveChdir; files_.clear(); @@ -219,6 +219,7 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, { if (m_State == FileStateDropHandler) { + FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM stg = { TYMED_HGLOBAL }; if ( FAILED( pDataObj->GetData ( &etc, &stg ))) @@ -272,27 +273,27 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, try { GitStatus stat; - //stat.GetStatus(CTGitPath(str.c_str()), false, true, true); + stat.GetStatus(CTGitPath(str.c_str()), false, true, true); if (stat.status) { statuspath = str; status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status); fetchedstatus = status; - if ((stat.status->entry)&&(stat.status->entry->lock_token)) - itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0; - if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir)) - { - itemStates |= ITEMIS_FOLDER; - if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none)) - itemStates |= ITEMIS_FOLDERINGit; - } - if ((stat.status->entry)&&(stat.status->entry->present_props)) - { - if (strstr(stat.status->entry->present_props, "svn:needs-lock")) - itemStates |= ITEMIS_NEEDSLOCK; - } - if ((stat.status->entry)&&(stat.status->entry->uuid)) - uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid); + //if ((stat.status->entry)&&(stat.status->entry->lock_token)) + // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0; + //if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir)) + //{ + // itemStates |= ITEMIS_FOLDER; + // if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none)) + // itemStates |= ITEMIS_FOLDERINGit; + //} + //if ((stat.status->entry)&&(stat.status->entry->present_props)) + //{ + // if (strstr(stat.status->entry->present_props, "svn:needs-lock")) + // itemStates |= ITEMIS_NEEDSLOCK; + //} + //if ((stat.status->entry)&&(stat.status->entry->uuid)) + // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid); } else { @@ -308,7 +309,7 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, ATLTRACE2(_T("Exception in GitStatus::GetAllStatus()\n")); } if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none)) - itemStates |= ITEMIS_INGit; + itemStates |= ITEMIS_INSVN; if (status == git_wc_status_ignored) itemStates |= ITEMIS_IGNORED; if (status == git_wc_status_normal) @@ -324,9 +325,11 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, } // for (int i = 0; i < count; i++) GlobalUnlock ( drop ); ReleaseStgMedium ( &stg ); + } // if (m_State == FileStateDropHandler) else { + //Enumerate PIDLs which the user has selected CIDA* cida = (CIDA*)GlobalLock(medium.hGlobal); ItemIDList parent( GetPIDLFolder (cida)); @@ -369,23 +372,23 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, { status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status); fetchedstatus = status; - if ((stat.status->entry)&&(stat.status->entry->lock_token)) - itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0; - if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir)) - { - itemStates |= ITEMIS_FOLDER; - if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none)) - itemStates |= ITEMIS_FOLDERINGit; - } - if ((stat.status->entry)&&(stat.status->entry->conflict_wrk)) - itemStates |= ITEMIS_CONFLICTED; - if ((stat.status->entry)&&(stat.status->entry->present_props)) - { - if (strstr(stat.status->entry->present_props, "svn:needs-lock")) - itemStates |= ITEMIS_NEEDSLOCK; - } - if ((stat.status->entry)&&(stat.status->entry->uuid)) - uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid); + //if ((stat.status->entry)&&(stat.status->entry->lock_token)) + // itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0; + //if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir)) + //{ + // itemStates |= ITEMIS_FOLDER; + // if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none)) + // itemStates |= ITEMIS_FOLDERINGit; + //} + //if ((stat.status->entry)&&(stat.status->entry->conflict_wrk)) + // itemStates |= ITEMIS_CONFLICTED; + //if ((stat.status->entry)&&(stat.status->entry->present_props)) + //{ + // if (strstr(stat.status->entry->present_props, "svn:needs-lock")) + // itemStates |= ITEMIS_NEEDSLOCK; + //} + //if ((stat.status->entry)&&(stat.status->entry->uuid)) + // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid); } else { @@ -406,25 +409,25 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, } } if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none)) - itemStates |= ITEMIS_INGit; + itemStates |= ITEMIS_INSVN; if (status == git_wc_status_ignored) { itemStates |= ITEMIS_IGNORED; // the item is ignored. Get the svn:ignored properties so we can (maybe) later // offer a 'remove from ignored list' entry - GitProperties props(strpath.GetContainingDirectory(), false); - ignoredprops.empty(); - for (int p=0; ptext_status, stat.status->prop_status); - if ((stat.status->entry)&&(stat.status->entry->lock_token)) - itemStatesFolder |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0; - if ((stat.status->entry)&&(stat.status->entry->present_props)) - { - if (strstr(stat.status->entry->present_props, "svn:needs-lock")) - itemStatesFolder |= ITEMIS_NEEDSLOCK; - } - if ((stat.status->entry)&&(stat.status->entry->uuid)) - uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid); +// if ((stat.status->entry)&&(stat.status->entry->lock_token)) +// itemStatesFolder |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0; +// if ((stat.status->entry)&&(stat.status->entry->present_props)) +// { +// if (strstr(stat.status->entry->present_props, "svn:needs-lock")) +// itemStatesFolder |= ITEMIS_NEEDSLOCK; +// } +// if ((stat.status->entry)&&(stat.status->entry->uuid)) +// uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid); if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none)) - itemStatesFolder |= ITEMIS_INGit; + itemStatesFolder |= ITEMIS_INSVN; if (status == git_wc_status_normal) itemStatesFolder |= ITEMIS_NORMAL; if (status == git_wc_status_conflicted) @@ -522,7 +527,7 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, } if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none)) { - itemStatesFolder |= ITEMIS_FOLDERINGit; + itemStatesFolder |= ITEMIS_FOLDERINSVN; } if (status == git_wc_status_ignored) itemStatesFolder |= ITEMIS_IGNORED; @@ -531,11 +536,13 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, itemStates |= ITEMIS_ONLYONE; if (m_State != FileStateDropHandler) itemStates |= itemStatesFolder; + } if (files_.size() == 2) itemStates |= ITEMIS_TWO; if ((files_.size() == 1)&&(g_ShellCache.IsContextPathAllowed(files_.front().c_str()))) { + itemStates |= ITEMIS_ONLYONE; if (m_State != FileStateDropHandler) { @@ -554,15 +561,15 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, if (stat.status) { status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status); - if ((stat.status->entry)&&(stat.status->entry->lock_token)) - itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0; - if ((stat.status->entry)&&(stat.status->entry->present_props)) - { - if (strstr(stat.status->entry->present_props, "svn:needs-lock")) - itemStates |= ITEMIS_NEEDSLOCK; - } - if ((stat.status->entry)&&(stat.status->entry->uuid)) - uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid); +// if ((stat.status->entry)&&(stat.status->entry->lock_token)) +// itemStates |= (stat.status->entry->lock_token[0] != 0) ? ITEMIS_LOCKED : 0; +// if ((stat.status->entry)&&(stat.status->entry->present_props)) +// { +// if (strstr(stat.status->entry->present_props, "svn:needs-lock")) +// itemStates |= ITEMIS_NEEDSLOCK; +// } +// if ((stat.status->entry)&&(stat.status->entry->uuid)) +// uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid); } } catch ( ... ) @@ -575,7 +582,7 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, status = fetchedstatus; } if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none)) - itemStates |= ITEMIS_FOLDERINGit; + itemStates |= ITEMIS_FOLDERINSVN; if (status == git_wc_status_ignored) itemStates |= ITEMIS_IGNORED; itemStates |= ITEMIS_FOLDER; @@ -585,8 +592,9 @@ STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder, itemStates |= ITEMIS_DELETED; } } + } -#endif + return NOERROR; } diff --git a/TortoiseShell/GitStatus.h b/TortoiseShell/GitStatus.h index 440fc99..7a8cc05 100644 --- a/TortoiseShell/GitStatus.h +++ b/TortoiseShell/GitStatus.h @@ -47,9 +47,17 @@ typedef enum typedef CString git_revnum_t; -typedef int git_wc_status2_t; typedef int git_error_t; +typedef struct git_wc_status2_t +{ + /** The status of the entries text. */ + git_wc_status_kind text_status; + + /** The status of the entries properties. */ + git_wc_status_kind prop_status; +}git_wc_status2; + #define MAX_STATUS_STRING_LENGTH 256 /** diff --git a/TortoiseShell/TGitPath.cpp b/TortoiseShell/TGitPath.cpp index 8c51170..55b69c5 100644 --- a/TortoiseShell/TGitPath.cpp +++ b/TortoiseShell/TGitPath.cpp @@ -1,10 +1,1546 @@ +// TortoiseGit - a Windows shell extension for easy version control + +// Copyright (C) 2003-2008 - TortoiseGit + +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software Foundation, +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// #include "StdAfx.h" #include "TGitPath.h" +#include "UnicodeUtils.h" +#include "GitAdminDir.h" +#include "PathUtils.h" +#include + +#if defined(_MFC_VER) +#include "MessageBox.h" +#include "AppUtils.h" +#endif -CTGitPath::CTGitPath(void) +using namespace std; + + +CTGitPath::CTGitPath(void) : + m_bDirectoryKnown(false), + m_bIsDirectory(false), + m_bIsURL(false), + m_bURLKnown(false), + m_bHasAdminDirKnown(false), + m_bHasAdminDir(false), + m_bIsValidOnWindowsKnown(false), + m_bIsReadOnly(false), + m_bIsAdminDirKnown(false), + m_bIsAdminDir(false), + m_bExists(false), + m_bExistsKnown(false), + m_bLastWriteTimeKnown(0), + m_lastWriteTime(0), + m_customData(NULL), + m_bIsSpecialDirectoryKnown(false), + m_bIsSpecialDirectory(false) { } CTGitPath::~CTGitPath(void) { } +// Create a TGitPath object from an unknown path type (same as using SetFromUnknown) +CTGitPath::CTGitPath(const CString& sUnknownPath) : + m_bDirectoryKnown(false), + m_bIsDirectory(false), + m_bIsURL(false), + m_bURLKnown(false), + m_bHasAdminDirKnown(false), + m_bHasAdminDir(false), + m_bIsValidOnWindowsKnown(false), + m_bIsReadOnly(false), + m_bIsAdminDirKnown(false), + m_bIsAdminDir(false), + m_bExists(false), + m_bExistsKnown(false), + m_bLastWriteTimeKnown(0), + m_lastWriteTime(0), + m_customData(NULL), + m_bIsSpecialDirectoryKnown(false), + m_bIsSpecialDirectory(false) +{ + SetFromUnknown(sUnknownPath); +} + +void CTGitPath::SetFromGit(const char* pPath) +{ + Reset(); + if (pPath == NULL) + return; + int len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, NULL, 0); + if (len) + { + len = MultiByteToWideChar(CP_UTF8, 0, pPath, -1, m_sFwdslashPath.GetBuffer(len+1), len+1); + m_sFwdslashPath.ReleaseBuffer(len-1); + } + SanitizeRootPath(m_sFwdslashPath, true); +} + +void CTGitPath::SetFromGit(const char* pPath, bool bIsDirectory) +{ + SetFromGit(pPath); + m_bDirectoryKnown = true; + m_bIsDirectory = bIsDirectory; +} + +void CTGitPath::SetFromGit(const CString& sPath) +{ + Reset(); + m_sFwdslashPath = sPath; + SanitizeRootPath(m_sFwdslashPath, true); +} + +void CTGitPath::SetFromWin(LPCTSTR pPath) +{ + Reset(); + m_sBackslashPath = pPath; + SanitizeRootPath(m_sBackslashPath, false); + ATLASSERT(m_sBackslashPath.Find('/')<0); +} +void CTGitPath::SetFromWin(const CString& sPath) +{ + Reset(); + m_sBackslashPath = sPath; + SanitizeRootPath(m_sBackslashPath, false); +} +void CTGitPath::SetFromWin(const CString& sPath, bool bIsDirectory) +{ + Reset(); + m_sBackslashPath = sPath; + m_bIsDirectory = bIsDirectory; + m_bDirectoryKnown = true; + SanitizeRootPath(m_sBackslashPath, false); +} +void CTGitPath::SetFromUnknown(const CString& sPath) +{ + Reset(); + // Just set whichever path we think is most likely to be used + SetFwdslashPath(sPath); +} + +LPCTSTR CTGitPath::GetWinPath() const +{ + if(IsEmpty()) + { + return _T(""); + } + if(m_sBackslashPath.IsEmpty()) + { + SetBackslashPath(m_sFwdslashPath); + } + return m_sBackslashPath; +} +// This is a temporary function, to be used during the migration to +// the path class. Ultimately, functions consuming paths should take a CTGitPath&, not a CString +const CString& CTGitPath::GetWinPathString() const +{ + if(m_sBackslashPath.IsEmpty()) + { + SetBackslashPath(m_sFwdslashPath); + } + return m_sBackslashPath; +} + +const CString& CTGitPath::GetGitPathString() const +{ + if(m_sFwdslashPath.IsEmpty()) + { + SetFwdslashPath(m_sBackslashPath); + } + return m_sFwdslashPath; +} + +#if 0 +const char* CTGitPath::GetGitApiPath(apr_pool_t *pool) const +{ + // This funny-looking 'if' is to avoid a subtle problem with empty paths, whereby + // each call to GetGitApiPath returns a different pointer value. + // If you made multiple calls to GetGitApiPath on the same string, only the last + // one would give you a valid pointer to an empty string, because each + // call would invalidate the previous call's return. + if(IsEmpty()) + { + return ""; + } + if(m_sFwdslashPath.IsEmpty()) + { + SetFwdslashPath(m_sBackslashPath); + } + if(m_sUTF8FwdslashPath.IsEmpty()) + { + SetUTF8FwdslashPath(m_sFwdslashPath); + } + if (svn_path_is_url(m_sUTF8FwdslashPath)) + { + m_sUTF8FwdslashPathEscaped = CPathUtils::PathEscape(m_sUTF8FwdslashPath); + m_sUTF8FwdslashPathEscaped.Replace("file:////", "file:///\\"); + m_sUTF8FwdslashPathEscaped = svn_path_canonicalize(m_sUTF8FwdslashPathEscaped, pool); + return m_sUTF8FwdslashPathEscaped; + } + m_sUTF8FwdslashPath = svn_path_canonicalize(m_sUTF8FwdslashPath, pool); + + return m_sUTF8FwdslashPath; +} +#endif + +const CString& CTGitPath::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(GetGitPathString()); + m_sUIPath.Replace(_T("file:////"), _T("file:///\\")); + + } + else +#endif + { + m_sUIPath = GetWinPathString(); + } + } + return m_sUIPath; +} + +void CTGitPath::SetFwdslashPath(const CString& sPath) const +{ + m_sFwdslashPath = sPath; + m_sFwdslashPath.Replace('\\', '/'); + + // We don't leave a trailing / + m_sFwdslashPath.TrimRight('/'); + + SanitizeRootPath(m_sFwdslashPath, true); + + m_sFwdslashPath.Replace(_T("file:////"), _T("file:///\\")); + + m_sUTF8FwdslashPath.Empty(); +} + +void CTGitPath::SetBackslashPath(const CString& sPath) const +{ + m_sBackslashPath = sPath; + m_sBackslashPath.Replace('/', '\\'); + m_sBackslashPath.TrimRight('\\'); + SanitizeRootPath(m_sBackslashPath, false); +} + +void CTGitPath::SetUTF8FwdslashPath(const CString& sPath) const +{ + m_sUTF8FwdslashPath = CUnicodeUtils::GetUTF8(sPath); +} + +void CTGitPath::SanitizeRootPath(CString& sPath, bool bIsForwardPath) const +{ + // Make sure to add the trailing slash to root paths such as 'C:' + if (sPath.GetLength() == 2 && sPath[1] == ':') + { + sPath += (bIsForwardPath) ? _T("/") : _T("\\"); + } +} + +bool CTGitPath::IsUrl() const +{ +#if 0 + if (!m_bURLKnown) + { + EnsureFwdslashPathSet(); + if(m_sUTF8FwdslashPath.IsEmpty()) + { + SetUTF8FwdslashPath(m_sFwdslashPath); + } + m_bIsURL = !!svn_path_is_url(m_sUTF8FwdslashPath); + m_bURLKnown = true; + } + return m_bIsURL; +#endif + return false; +} + +bool CTGitPath::IsDirectory() const +{ + if(!m_bDirectoryKnown) + { + UpdateAttributes(); + } + return m_bIsDirectory; +} + +bool CTGitPath::Exists() const +{ + if (!m_bExistsKnown) + { + UpdateAttributes(); + } + return m_bExists; +} + +bool CTGitPath::Delete(bool bTrash) const +{ + EnsureBackslashPathSet(); + ::SetFileAttributes(m_sBackslashPath, FILE_ATTRIBUTE_NORMAL); + bool bRet = false; + if (Exists()) + { + if ((bTrash)||(IsDirectory())) + { + TCHAR * buf = new TCHAR[m_sBackslashPath.GetLength()+2]; + _tcscpy_s(buf, m_sBackslashPath.GetLength()+2, m_sBackslashPath); + buf[m_sBackslashPath.GetLength()] = 0; + buf[m_sBackslashPath.GetLength()+1] = 0; + SHFILEOPSTRUCT shop = {0}; + shop.wFunc = FO_DELETE; + shop.pFrom = buf; + shop.fFlags = FOF_NOCONFIRMATION|FOF_NOERRORUI|FOF_SILENT; + if (bTrash) + shop.fFlags |= FOF_ALLOWUNDO; + bRet = (SHFileOperation(&shop) == 0); + delete [] buf; + } + else + { + bRet = !!::DeleteFile(m_sBackslashPath); + } + } + m_bExists = false; + m_bExistsKnown = true; + return bRet; +} + +__int64 CTGitPath::GetLastWriteTime() const +{ + if(!m_bLastWriteTimeKnown) + { + UpdateAttributes(); + } + return m_lastWriteTime; +} + +bool CTGitPath::IsReadOnly() const +{ + if(!m_bLastWriteTimeKnown) + { + UpdateAttributes(); + } + return m_bIsReadOnly; +} + +void CTGitPath::UpdateAttributes() const +{ + EnsureBackslashPathSet(); + WIN32_FILE_ATTRIBUTE_DATA attribs; + if(GetFileAttributesEx(m_sBackslashPath, GetFileExInfoStandard, &attribs)) + { + m_bIsDirectory = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + m_lastWriteTime = *(__int64*)&attribs.ftLastWriteTime; + m_bIsReadOnly = !!(attribs.dwFileAttributes & FILE_ATTRIBUTE_READONLY); + m_bExists = true; + } + else + { + DWORD err = GetLastError(); + if ((err == ERROR_FILE_NOT_FOUND)||(err == ERROR_PATH_NOT_FOUND)||(err == ERROR_INVALID_NAME)) + { + m_bIsDirectory = false; + m_lastWriteTime = 0; + m_bExists = false; + } + else + { + m_bIsDirectory = false; + m_lastWriteTime = 0; + m_bExists = true; + return; + } + } + m_bDirectoryKnown = true; + m_bLastWriteTimeKnown = true; + m_bExistsKnown = true; +} + + +void CTGitPath::EnsureBackslashPathSet() const +{ + if(m_sBackslashPath.IsEmpty()) + { + SetBackslashPath(m_sFwdslashPath); + ATLASSERT(IsEmpty() || !m_sBackslashPath.IsEmpty()); + } +} +void CTGitPath::EnsureFwdslashPathSet() const +{ + if(m_sFwdslashPath.IsEmpty()) + { + SetFwdslashPath(m_sBackslashPath); + ATLASSERT(IsEmpty() || !m_sFwdslashPath.IsEmpty()); + } +} + + +// Reset all the caches +void CTGitPath::Reset() +{ + m_bDirectoryKnown = false; + m_bURLKnown = false; + m_bLastWriteTimeKnown = false; + m_bHasAdminDirKnown = false; + m_bIsValidOnWindowsKnown = false; + m_bIsAdminDirKnown = false; + m_bExistsKnown = false; + m_bIsSpecialDirectoryKnown = false; + m_bIsSpecialDirectory = false; + + m_sBackslashPath.Empty(); + m_sFwdslashPath.Empty(); + m_sUTF8FwdslashPath.Empty(); + ATLASSERT(IsEmpty()); +} + +CTGitPath CTGitPath::GetDirectory() const +{ + if ((IsDirectory())||(!Exists())) + { + return *this; + } + return GetContainingDirectory(); +} + +CTGitPath CTGitPath::GetContainingDirectory() const +{ + EnsureBackslashPathSet(); + + CString sDirName = m_sBackslashPath.Left(m_sBackslashPath.ReverseFind('\\')); + if(sDirName.GetLength() == 2 && sDirName[1] == ':') + { + // This is a root directory, which needs a trailing slash + sDirName += '\\'; + if(sDirName == m_sBackslashPath) + { + // We were clearly provided with a root path to start with - we should return nothing now + sDirName.Empty(); + } + } + if(sDirName.GetLength() == 1 && sDirName[0] == '\\') + { + // We have an UNC path and we already are the root + sDirName.Empty(); + } + CTGitPath retVal; + retVal.SetFromWin(sDirName); + return retVal; +} + +CString CTGitPath::GetRootPathString() const +{ + EnsureBackslashPathSet(); + CString workingPath = m_sBackslashPath; + LPTSTR pPath = workingPath.GetBuffer(MAX_PATH); // MAX_PATH ok here. + ATLVERIFY(::PathStripToRoot(pPath)); + workingPath.ReleaseBuffer(); + return workingPath; +} + + +CString CTGitPath::GetFilename() const +{ + ATLASSERT(!IsDirectory()); + return GetFileOrDirectoryName(); +} + +CString CTGitPath::GetFileOrDirectoryName() const +{ + EnsureBackslashPathSet(); + return m_sBackslashPath.Mid(m_sBackslashPath.ReverseFind('\\')+1); +} + +CString CTGitPath::GetUIFileOrDirectoryName() const +{ + GetUIPathString(); + return m_sUIPath.Mid(m_sUIPath.ReverseFind('\\')+1); +} + +CString CTGitPath::GetFileExtension() const +{ + if(!IsDirectory()) + { + EnsureBackslashPathSet(); + int dotPos = m_sBackslashPath.ReverseFind('.'); + int slashPos = m_sBackslashPath.ReverseFind('\\'); + if (dotPos > slashPos) + return m_sBackslashPath.Mid(dotPos); + } + return CString(); +} + + +bool CTGitPath::ArePathStringsEqual(const CString& sP1, const CString& sP2) +{ + int length = sP1.GetLength(); + if(length != sP2.GetLength()) + { + // Different lengths + return false; + } + // We work from the end of the strings, because path differences + // are more likely to occur at the far end of a string + LPCTSTR pP1Start = sP1; + LPCTSTR pP1 = pP1Start+(length-1); + LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1); + while(length-- > 0) + { + if(_totlower(*pP1--) != _totlower(*pP2--)) + { + return false; + } + } + return true; +} + +bool CTGitPath::ArePathStringsEqualWithCase(const CString& sP1, const CString& sP2) +{ + int length = sP1.GetLength(); + if(length != sP2.GetLength()) + { + // Different lengths + return false; + } + // We work from the end of the strings, because path differences + // are more likely to occur at the far end of a string + LPCTSTR pP1Start = sP1; + LPCTSTR pP1 = pP1Start+(length-1); + LPCTSTR pP2 = ((LPCTSTR)sP2)+(length-1); + while(length-- > 0) + { + if((*pP1--) != (*pP2--)) + { + return false; + } + } + return true; +} + +bool CTGitPath::IsEmpty() const +{ + // Check the backward slash path first, since the chance that this + // one is set is higher. In case of a 'false' return value it's a little + // bit faster. + return m_sBackslashPath.IsEmpty() && m_sFwdslashPath.IsEmpty(); +} + +// Test if both paths refer to the same item +// Ignores case and slash direction +bool CTGitPath::IsEquivalentTo(const CTGitPath& rhs) const +{ + // Try and find a slash direction which avoids having to convert + // both filenames + if(!m_sBackslashPath.IsEmpty()) + { + // *We've* got a \ path - make sure that the RHS also has a \ path + rhs.EnsureBackslashPathSet(); + return ArePathStringsEqualWithCase(m_sBackslashPath, rhs.m_sBackslashPath); + } + else + { + // Assume we've got a fwdslash path and make sure that the RHS has one + rhs.EnsureFwdslashPathSet(); + return ArePathStringsEqualWithCase(m_sFwdslashPath, rhs.m_sFwdslashPath); + } +} + +bool CTGitPath::IsEquivalentToWithoutCase(const CTGitPath& rhs) const +{ + // Try and find a slash direction which avoids having to convert + // both filenames + if(!m_sBackslashPath.IsEmpty()) + { + // *We've* got a \ path - make sure that the RHS also has a \ path + rhs.EnsureBackslashPathSet(); + return ArePathStringsEqual(m_sBackslashPath, rhs.m_sBackslashPath); + } + else + { + // Assume we've got a fwdslash path and make sure that the RHS has one + rhs.EnsureFwdslashPathSet(); + return ArePathStringsEqual(m_sFwdslashPath, rhs.m_sFwdslashPath); + } +} + +bool CTGitPath::IsAncestorOf(const CTGitPath& possibleDescendant) const +{ + possibleDescendant.EnsureBackslashPathSet(); + EnsureBackslashPathSet(); + + bool bPathStringsEqual = ArePathStringsEqual(m_sBackslashPath, possibleDescendant.m_sBackslashPath.Left(m_sBackslashPath.GetLength())); + if (m_sBackslashPath.GetLength() >= possibleDescendant.GetWinPathString().GetLength()) + { + return bPathStringsEqual; + } + + return (bPathStringsEqual && + ((possibleDescendant.m_sBackslashPath[m_sBackslashPath.GetLength()] == '\\')|| + (m_sBackslashPath.GetLength()==3 && m_sBackslashPath[1]==':'))); +} + +// Get a string representing the file path, optionally with a base +// section stripped off the front. +CString CTGitPath::GetDisplayString(const CTGitPath* pOptionalBasePath /* = NULL*/) const +{ + EnsureFwdslashPathSet(); + if(pOptionalBasePath != NULL) + { + // Find the length of the base-path without having to do an 'ensure' on it + int baseLength = max(pOptionalBasePath->m_sBackslashPath.GetLength(), pOptionalBasePath->m_sFwdslashPath.GetLength()); + + // Now, chop that baseLength of the front of the path + return m_sFwdslashPath.Mid(baseLength).TrimLeft('/'); + } + return m_sFwdslashPath; +} + +int CTGitPath::Compare(const CTGitPath& left, const CTGitPath& right) +{ + left.EnsureBackslashPathSet(); + right.EnsureBackslashPathSet(); + return left.m_sBackslashPath.CompareNoCase(right.m_sBackslashPath); +} + +bool operator<(const CTGitPath& left, const CTGitPath& right) +{ + return CTGitPath::Compare(left, right) < 0; +} + +bool CTGitPath::PredLeftEquivalentToRight(const CTGitPath& left, const CTGitPath& right) +{ + return left.IsEquivalentTo(right); +} + +bool CTGitPath::PredLeftSameWCPathAsRight(const CTGitPath& left, const CTGitPath& right) +{ + if (left.IsAdminDir() && right.IsAdminDir()) + { + CTGitPath l = left; + CTGitPath r = right; + do + { + l = l.GetContainingDirectory(); + } while(l.HasAdminDir()); + do + { + r = r.GetContainingDirectory(); + } while(r.HasAdminDir()); + return l.GetContainingDirectory().IsEquivalentTo(r.GetContainingDirectory()); + } + return left.GetDirectory().IsEquivalentTo(right.GetDirectory()); +} + +bool CTGitPath::CheckChild(const CTGitPath &parent, const CTGitPath& child) +{ + return parent.IsAncestorOf(child); +} + +void CTGitPath::AppendRawString(const CString& sAppend) +{ + EnsureFwdslashPathSet(); + CString strCopy = m_sFwdslashPath += sAppend; + SetFromUnknown(strCopy); +} + +void CTGitPath::AppendPathString(const CString& sAppend) +{ + EnsureBackslashPathSet(); + CString cleanAppend(sAppend); + cleanAppend.Replace('/', '\\'); + cleanAppend.TrimLeft('\\'); + m_sBackslashPath.TrimRight('\\'); + CString strCopy = m_sBackslashPath + _T("\\") + cleanAppend; + SetFromWin(strCopy); +} + +bool CTGitPath::HasAdminDir() const +{ + if (m_bHasAdminDirKnown) + return m_bHasAdminDir; + + EnsureBackslashPathSet(); + m_bHasAdminDir = g_GitAdminDir.HasAdminDir(m_sBackslashPath, IsDirectory()); + m_bHasAdminDirKnown = true; + return m_bHasAdminDir; +} + +bool CTGitPath::IsAdminDir() const +{ + if (m_bIsAdminDirKnown) + return m_bIsAdminDir; + + EnsureBackslashPathSet(); + m_bIsAdminDir = g_GitAdminDir.IsAdminDirPath(m_sBackslashPath); + m_bIsAdminDirKnown = true; + return m_bIsAdminDir; +} + +bool CTGitPath::IsValidOnWindows() const +{ + if (m_bIsValidOnWindowsKnown) + return m_bIsValidOnWindows; + + m_bIsValidOnWindows = false; + EnsureBackslashPathSet(); + CString sMatch = m_sBackslashPath + _T("\r\n"); + wstring sPattern; + // the 'file://' URL is just a normal windows path: + if (sMatch.Left(7).CompareNoCase(_T("file:\\\\"))==0) + { + sMatch = sMatch.Mid(7); + sMatch.TrimLeft(_T("\\")); + sPattern = _T("^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$"); + } + else if (IsUrl()) + { + sPattern = _T("^((http|https|svn|svn\\+ssh|file)\\:\\\\+([^\\\\@\\:]+\\:[^\\\\@\\:]+@)?\\\\[^\\\\]+(\\:\\d+)?)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<>\\. ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<>\\. ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$"); + } + else + { + sPattern = _T("^(\\\\\\\\\\?\\\\)?(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?\"\\|<> ](([^\\\\/:\\*\\?\"\\|<>\\. ])|([^\\\\/:\\*\\?\"\\|<>]*[^\\\\/:\\*\\?\"\\|<>\\. ]))?$"); + } + + try + { + tr1::wregex rx(sPattern, tr1::regex_constants::icase | tr1::regex_constants::ECMAScript); + tr1::wsmatch match; + + wstring rmatch = wstring((LPCTSTR)sMatch); + if (tr1::regex_match(rmatch, match, rx)) + { + if (wstring(match[0]).compare(sMatch)==0) + m_bIsValidOnWindows = true; + } + if (m_bIsValidOnWindows) + { + // now check for illegal filenames + tr1::wregex rx2(_T("\\\\(lpt\\d|com\\d|aux|nul|prn|con)(\\\\|$)"), tr1::regex_constants::icase | tr1::regex_constants::ECMAScript); + rmatch = m_sBackslashPath; + if (tr1::regex_search(rmatch, rx2, tr1::regex_constants::match_default)) + m_bIsValidOnWindows = false; + } + } + catch (exception) {} + + m_bIsValidOnWindowsKnown = true; + return m_bIsValidOnWindows; +} + +bool CTGitPath::IsSpecialDirectory() const +{ + if (m_bIsSpecialDirectoryKnown) + return m_bIsSpecialDirectory; + + static LPCTSTR specialDirectories[] + = { _T("trunk"), _T("tags"), _T("branches") }; + + for (int i=0 ; i<(sizeof(specialDirectories) / sizeof(specialDirectories[0])) ; ++i) + { + CString name = GetFileOrDirectoryName(); + if (0 == name.CompareNoCase(specialDirectories[i])) + { + m_bIsSpecialDirectory = true; + break; + } + } + + m_bIsSpecialDirectoryKnown = true; + + return m_bIsSpecialDirectory; +} + +////////////////////////////////////////////////////////////////////////// + +CTGitPathList::CTGitPathList() +{ + +} + +// A constructor which allows a path list to be easily built which one initial entry in +CTGitPathList::CTGitPathList(const CTGitPath& firstEntry) +{ + AddPath(firstEntry); +} + +void CTGitPathList::AddPath(const CTGitPath& newPath) +{ + m_paths.push_back(newPath); + m_commonBaseDirectory.Reset(); +} +int CTGitPathList::GetCount() const +{ + return (int)m_paths.size(); +} +void CTGitPathList::Clear() +{ + m_paths.clear(); + m_commonBaseDirectory.Reset(); +} + +const CTGitPath& CTGitPathList::operator[](INT_PTR index) const +{ + ATLASSERT(index >= 0 && index < (INT_PTR)m_paths.size()); + return m_paths[index]; +} + +bool CTGitPathList::AreAllPathsFiles() const +{ + // Look through the vector for any directories - if we find them, return false + return std::find_if(m_paths.begin(), m_paths.end(), std::mem_fun_ref(&CTGitPath::IsDirectory)) == m_paths.end(); +} + + +#if defined(_MFC_VER) + +bool CTGitPathList::LoadFromFile(const CTGitPath& filename) +{ + Clear(); + try + { + CString strLine; + CStdioFile file(filename.GetWinPath(), CFile::typeBinary | CFile::modeRead | CFile::shareDenyWrite); + + // for every selected file/folder + CTGitPath path; + while (file.ReadString(strLine)) + { + path.SetFromUnknown(strLine); + AddPath(path); + } + file.Close(); + } + catch (CFileException* pE) + { + TRACE("CFileException loading target file list\n"); + TCHAR error[10000] = {0}; + pE->GetErrorMessage(error, 10000); + CMessageBox::Show(NULL, error, _T("TortoiseGit"), MB_ICONERROR); + pE->Delete(); + return false; + } + return true; +} + +bool CTGitPathList::WriteToFile(const CString& sFilename, bool bANSI /* = false */) const +{ + try + { + if (bANSI) + { + CStdioFile file(sFilename, CFile::typeText | CFile::modeReadWrite | CFile::modeCreate); + PathVector::const_iterator it; + for(it = m_paths.begin(); it != m_paths.end(); ++it) + { + CStringA line = CStringA(it->GetGitPathString()) + '\n'; + file.Write(line, line.GetLength()); + } + file.Close(); + } + else + { + CStdioFile file(sFilename, CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate); + PathVector::const_iterator it; + for(it = m_paths.begin(); it != m_paths.end(); ++it) + { + file.WriteString(it->GetGitPathString()+_T("\n")); + } + file.Close(); + } + } + catch (CFileException* pE) + { + TRACE("CFileException in writing temp file\n"); + pE->Delete(); + return false; + } + return true; +} + + +void CTGitPathList::LoadFromAsteriskSeparatedString(const CString& sPathString) +{ + int pos = 0; + CString temp; + for(;;) + { + temp = sPathString.Tokenize(_T("*"),pos); + if(temp.IsEmpty()) + { + break; + } + AddPath(CTGitPath(CPathUtils::GetLongPathname(temp))); + } +} + +CString CTGitPathList::CreateAsteriskSeparatedString() const +{ + CString sRet; + PathVector::const_iterator it; + for(it = m_paths.begin(); it != m_paths.end(); ++it) + { + if (!sRet.IsEmpty()) + sRet += _T("*"); + sRet += it->GetWinPathString(); + } + return sRet; +} +#endif // _MFC_VER + +bool +CTGitPathList::AreAllPathsFilesInOneDirectory() const +{ + // Check if all the paths are files and in the same directory + PathVector::const_iterator it; + m_commonBaseDirectory.Reset(); + for(it = m_paths.begin(); it != m_paths.end(); ++it) + { + if(it->IsDirectory()) + { + return false; + } + const CTGitPath& baseDirectory = it->GetDirectory(); + if(m_commonBaseDirectory.IsEmpty()) + { + m_commonBaseDirectory = baseDirectory; + } + else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory)) + { + // Different path + m_commonBaseDirectory.Reset(); + return false; + } + } + return true; +} + +CTGitPath CTGitPathList::GetCommonDirectory() const +{ + if (m_commonBaseDirectory.IsEmpty()) + { + PathVector::const_iterator it; + for(it = m_paths.begin(); it != m_paths.end(); ++it) + { + const CTGitPath& baseDirectory = it->GetDirectory(); + if(m_commonBaseDirectory.IsEmpty()) + { + m_commonBaseDirectory = baseDirectory; + } + else if(!m_commonBaseDirectory.IsEquivalentTo(baseDirectory)) + { + // Different path + m_commonBaseDirectory.Reset(); + break; + } + } + } + // since we only checked strings, not paths, + // we have to make sure now that we really return a *path* here + PathVector::const_iterator iter; + for(iter = m_paths.begin(); iter != m_paths.end(); ++iter) + { + if (!m_commonBaseDirectory.IsAncestorOf(*iter)) + { + m_commonBaseDirectory = m_commonBaseDirectory.GetContainingDirectory(); + break; + } + } + return m_commonBaseDirectory; +} + +CTGitPath CTGitPathList::GetCommonRoot() const +{ + PathVector::const_iterator it; + CString sRoot, sTempRoot; + bool bEqual = true; + + if (GetCount() == 1) + return m_paths[0]; + + int backSlashPos = 0; + int searchStartPos = 0; + while (bEqual) + { + for (it = m_paths.begin(); it != m_paths.end(); ++it) + { + if (backSlashPos == 0) + { + backSlashPos = it->GetWinPathString().Find('\\', searchStartPos+1); + if ((backSlashPos < 0)&&(searchStartPos != it->GetWinPathString().GetLength())) + backSlashPos = it->GetWinPathString().GetLength(); + } + else if (it->GetWinPathString().Find('\\', searchStartPos+1) != backSlashPos) + { + if (it->GetWinPathString().Find('\\', searchStartPos+1) < 0) + { + if (it->GetWinPathString().GetLength() != backSlashPos) + { + bEqual = false; + break; + } + } + else + { + bEqual = false; + break; + } + } + if (backSlashPos < 0) + { + bEqual = false; + break; + } + } + if (bEqual == false) + { + if (searchStartPos) + sRoot = m_paths[0].GetWinPathString().Left(searchStartPos+1); + } + else + { + searchStartPos = backSlashPos; + } + backSlashPos = 0; + } + + return CTGitPath(sRoot.TrimRight('\\')); +} + +void CTGitPathList::SortByPathname(bool bReverse /*= false*/) +{ + std::sort(m_paths.begin(), m_paths.end()); + if (bReverse) + std::reverse(m_paths.begin(), m_paths.end()); +} + +void CTGitPathList::DeleteAllFiles(bool bTrash) +{ + PathVector::const_iterator it; + if (bTrash) + { + SortByPathname(); + CString sPaths; + for (it = m_paths.begin(); it != m_paths.end(); ++it) + { + if ((it->Exists())&&(!it->IsDirectory())) + { + ::SetFileAttributes(it->GetWinPath(), FILE_ATTRIBUTE_NORMAL); + sPaths += it->GetWinPath(); + sPaths += '\0'; + } + } + sPaths += '\0'; + sPaths += '\0'; + SHFILEOPSTRUCT shop = {0}; + shop.wFunc = FO_DELETE; + shop.pFrom = (LPCTSTR)sPaths; + shop.fFlags = FOF_ALLOWUNDO|FOF_NOCONFIRMATION|FOF_NOERRORUI|FOF_SILENT; + SHFileOperation(&shop); + } + else + { + for (it = m_paths.begin(); it != m_paths.end(); ++it) + { + if (!it->IsDirectory()) + { + ::SetFileAttributes(it->GetWinPath(), FILE_ATTRIBUTE_NORMAL); + ::DeleteFile(it->GetWinPath()); + } + } + } + Clear(); +} + +void CTGitPathList::RemoveDuplicates() +{ + SortByPathname(); + // Remove the duplicates + // (Unique moves them to the end of the vector, then erase chops them off) + m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::PredLeftEquivalentToRight), m_paths.end()); +} + +void CTGitPathList::RemoveAdminPaths() +{ + PathVector::iterator it; + for(it = m_paths.begin(); it != m_paths.end(); ) + { + if (it->IsAdminDir()) + { + m_paths.erase(it); + it = m_paths.begin(); + } + else + ++it; + } +} + +void CTGitPathList::RemovePath(const CTGitPath& path) +{ + PathVector::iterator it; + for(it = m_paths.begin(); it != m_paths.end(); ++it) + { + if (it->IsEquivalentTo(path)) + { + m_paths.erase(it); + return; + } + } +} + +void CTGitPathList::RemoveChildren() +{ + SortByPathname(); + m_paths.erase(std::unique(m_paths.begin(), m_paths.end(), &CTGitPath::CheckChild), m_paths.end()); +} + +bool CTGitPathList::IsEqual(const CTGitPathList& list) +{ + if (list.GetCount() != GetCount()) + return false; + for (int i=0; iension")); + ATLASSERT(!testPath.IsValidOnWindows()); + testPath.SetFromWin(_T("c:\\com1\\filename")); + ATLASSERT(!testPath.IsValidOnWindows()); + testPath.SetFromWin(_T("c:\\com1")); + ATLASSERT(!testPath.IsValidOnWindows()); + testPath.SetFromWin(_T("c:\\com1\\AuX")); + ATLASSERT(!testPath.IsValidOnWindows()); + + testPath.SetFromWin(_T("\\\\Share\\lpt9\\filename")); + ATLASSERT(!testPath.IsValidOnWindows()); + testPath.SetFromWin(_T("\\\\Share\\prn")); + ATLASSERT(!testPath.IsValidOnWindows()); + testPath.SetFromWin(_T("\\\\Share\\NUL")); + ATLASSERT(!testPath.IsValidOnWindows()); + + // now come some URL tests + testPath.SetFromGit(_T("http://myserver.com/repos/trunk")); + ATLASSERT(testPath.IsValidOnWindows()); + testPath.SetFromGit(_T("https://myserver.com/repos/trunk/file%20with%20spaces")); + ATLASSERT(testPath.IsValidOnWindows()); + testPath.SetFromGit(_T("svn://myserver.com/repos/trunk/file with spaces")); + ATLASSERT(testPath.IsValidOnWindows()); + testPath.SetFromGit(_T("svn+ssh://www.myserver.com/repos/trunk")); + ATLASSERT(testPath.IsValidOnWindows()); + testPath.SetFromGit(_T("http://localhost:90/repos/trunk")); + ATLASSERT(testPath.IsValidOnWindows()); + testPath.SetFromGit(_T("file:///C:/GitRepos/Tester/Proj1/tags/t2")); + ATLASSERT(testPath.IsValidOnWindows()); + // and some negative URL tests + testPath.SetFromGit(_T("httpp://myserver.com/repos/trunk")); + ATLASSERT(!testPath.IsValidOnWindows()); + testPath.SetFromGit(_T("https://myserver.com/rep:os/trunk/file%20with%20spaces")); + ATLASSERT(!testPath.IsValidOnWindows()); + testPath.SetFromGit(_T("svn://myserver.com/rep