1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // Copyright (C) 2003-2008 - TortoiseSVN
\r
5 // This program is free software; you can redistribute it and/or
\r
6 // modify it under the terms of the GNU General Public License
\r
7 // as published by the Free Software Foundation; either version 2
\r
8 // of the License, or (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, write to the Free Software Foundation,
\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
20 #include "ShellExt.h"
\r
22 #include "PreserveChdir.h"
\r
23 //#include "SVNProperties.h"
\r
24 #include "UnicodeUtils.h"
\r
25 #include "GitStatus.h"
\r
26 #include "PathUtils.h"
\r
27 #include "..\TGitCache\CacheInterface.h"
\r
30 const static int ColumnFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT;
\r
32 // Defines that revision numbers occupy at most MAX_REV_STRING_LEN characters.
\r
33 // There are Perforce repositories out there that have several 100,000 revs.
\r
34 // So, don't be too restrictive by limiting this to 6 digits + 1 separator,
\r
37 // Because shorter strings will be extended to have exactly MAX_REV_STRING_LEN
\r
38 // characters, large numbers will produce large strings. These, in turn, will
\r
39 // affect column auto sizing. This setting is a reasonable compromise.
\r
41 // Max rev correctly sorted: 99,999,999 for MAX_REV_STRING_LEN == 10
\r
43 #define MAX_REV_STRING_LEN 10
\r
45 // IColumnProvider members
\r
46 STDMETHODIMP CShellExt::GetColumnInfo(DWORD dwIndex, SHCOLUMNINFO *psci)
\r
48 PreserveChdir preserveChdir;
\r
52 ShellCache::CacheType cachetype = g_ShellCache.GetCacheType();
\r
57 case 0: // SVN Status
\r
58 if (cachetype == ShellCache::none)
\r
60 psci->scid.fmtid = CLSID_Tortoisegit_UPTODATE;
\r
61 psci->scid.pid = dwIndex;
\r
63 psci->fmt = LVCFMT_LEFT;
\r
65 psci->csFlags = ColumnFlags;
\r
67 MAKESTRING(IDS_COLTITLESTATUS);
\r
68 lstrcpynW(psci->wszTitle, stringtablebuffer, MAX_COLUMN_NAME_LEN);
\r
69 MAKESTRING(IDS_COLDESCSTATUS);
\r
70 lstrcpynW(psci->wszDescription, stringtablebuffer, MAX_COLUMN_DESC_LEN);
\r
72 case 1: // SVN Revision
\r
73 if (cachetype == ShellCache::none)
\r
75 psci->scid.fmtid = CLSID_Tortoisegit_UPTODATE;
\r
76 psci->scid.pid = dwIndex;
\r
78 psci->fmt = LVCFMT_RIGHT;
\r
80 psci->csFlags = SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT;
\r
82 MAKESTRING(IDS_COLTITLEREV);
\r
83 lstrcpynW(psci->wszTitle, stringtablebuffer, MAX_COLUMN_NAME_LEN);
\r
84 MAKESTRING(IDS_COLDESCREV);
\r
85 lstrcpynW(psci->wszDescription, stringtablebuffer, MAX_COLUMN_DESC_LEN);
\r
88 if (cachetype == ShellCache::none)
\r
90 psci->scid.fmtid = CLSID_Tortoisegit_UPTODATE;
\r
91 psci->scid.pid = dwIndex;
\r
93 psci->fmt = LVCFMT_LEFT;
\r
95 psci->csFlags = ColumnFlags;
\r
97 MAKESTRING(IDS_COLTITLEURL);
\r
98 lstrcpynW(psci->wszTitle, stringtablebuffer, MAX_COLUMN_NAME_LEN);
\r
99 MAKESTRING(IDS_COLDESCURL);
\r
100 lstrcpynW(psci->wszDescription, stringtablebuffer, MAX_COLUMN_DESC_LEN);
\r
102 case 3: // SVN Short Url
\r
103 if (cachetype == ShellCache::none)
\r
105 psci->scid.fmtid = CLSID_Tortoisegit_UPTODATE;
\r
106 psci->scid.pid = dwIndex;
\r
107 psci->vt = VT_BSTR;
\r
108 psci->fmt = LVCFMT_LEFT;
\r
110 psci->csFlags = ColumnFlags;
\r
112 MAKESTRING(IDS_COLTITLESHORTURL);
\r
113 lstrcpynW(psci->wszTitle, stringtablebuffer, MAX_COLUMN_NAME_LEN);
\r
114 MAKESTRING(IDS_COLDESCSHORTURL);
\r
115 lstrcpynW(psci->wszDescription, stringtablebuffer, MAX_COLUMN_DESC_LEN);
\r
118 if (cachetype == ShellCache::none)
\r
120 psci->scid.fmtid = FMTID_SummaryInformation; // predefined FMTID
\r
121 psci->scid.pid = PIDSI_AUTHOR; // Predefined - author
\r
122 psci->vt = VT_LPSTR; // We'll return the data as a string
\r
123 psci->fmt = LVCFMT_LEFT; // Text will be left-aligned in the column
\r
124 psci->csFlags = SHCOLSTATE_TYPE_STR; // Data should be sorted as strings
\r
125 psci->cChars = 32; // Default col width in chars
\r
127 case 5: // SVN mime-type
\r
128 if (cachetype == ShellCache::none)
\r
130 psci->scid.fmtid = CLSID_Tortoisegit_UPTODATE;
\r
131 psci->scid.pid = dwIndex;
\r
132 psci->vt = VT_BSTR;
\r
133 psci->fmt = LVCFMT_LEFT;
\r
135 psci->csFlags = ColumnFlags;
\r
137 MAKESTRING(IDS_COLTITLEMIMETYPE);
\r
138 lstrcpynW(psci->wszTitle, stringtablebuffer, MAX_COLUMN_NAME_LEN);
\r
139 MAKESTRING(IDS_COLDESCMIMETYPE);
\r
140 lstrcpynW(psci->wszDescription, stringtablebuffer, MAX_COLUMN_DESC_LEN);
\r
142 case 6: // SVN Lock Owner
\r
143 if (cachetype == ShellCache::none)
\r
145 psci->scid.fmtid = CLSID_Tortoisegit_UPTODATE;
\r
146 psci->scid.pid = dwIndex;
\r
147 psci->vt = VT_BSTR;
\r
148 psci->fmt = LVCFMT_LEFT;
\r
150 psci->csFlags = ColumnFlags;
\r
152 MAKESTRING(IDS_COLTITLEOWNER);
\r
153 lstrcpynW(psci->wszTitle, stringtablebuffer, MAX_COLUMN_NAME_LEN);
\r
154 MAKESTRING(IDS_COLDESCOWNER);
\r
155 lstrcpynW(psci->wszDescription, stringtablebuffer, MAX_COLUMN_DESC_LEN);
\r
157 case 7: // SVN eol-style
\r
158 if (cachetype == ShellCache::none)
\r
160 psci->scid.fmtid = CLSID_Tortoisegit_UPTODATE;
\r
161 psci->scid.pid = dwIndex;
\r
162 psci->vt = VT_BSTR;
\r
163 psci->fmt = LVCFMT_LEFT;
\r
165 psci->csFlags = ColumnFlags;
\r
167 MAKESTRING(IDS_COLTITLEEOLSTYLE);
\r
168 lstrcpynW(psci->wszTitle, stringtablebuffer, MAX_COLUMN_NAME_LEN);
\r
169 MAKESTRING(IDS_COLDESCEOLSTYLE);
\r
170 lstrcpynW(psci->wszDescription, stringtablebuffer, MAX_COLUMN_DESC_LEN);
\r
172 case 8: // SVN Author
\r
173 if (cachetype == ShellCache::none)
\r
175 psci->scid.fmtid = CLSID_Tortoisegit_UPTODATE;
\r
176 psci->scid.pid = dwIndex;
\r
177 psci->vt = VT_BSTR;
\r
178 psci->fmt = LVCFMT_LEFT;
\r
180 psci->csFlags = ColumnFlags;
\r
182 MAKESTRING(IDS_COLTITLEAUTHOR);
\r
183 lstrcpynW(psci->wszTitle, stringtablebuffer, MAX_COLUMN_NAME_LEN);
\r
184 MAKESTRING(IDS_COLDESCAUTHOR);
\r
185 lstrcpynW(psci->wszDescription, stringtablebuffer, MAX_COLUMN_DESC_LEN);
\r
192 STDMETHODIMP CShellExt::GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData)
\r
194 PreserveChdir preserveChdir;
\r
195 if (!g_ShellCache.IsPathAllowed((TCHAR *)pscd->wszFile))
\r
200 ShellCache::CacheType cachetype = g_ShellCache.GetCacheType();
\r
201 if (pscid->fmtid == CLSID_Tortoisegit_UPTODATE && pscid->pid < 8)
\r
204 const TCHAR * path = (TCHAR *)pscd->wszFile;
\r
206 // reserve for the path + trailing \0
\r
208 TCHAR buf[MAX_STATUS_STRING_LENGTH+1];
\r
209 SecureZeroMemory(buf, MAX_STATUS_STRING_LENGTH);
\r
210 switch (pscid->pid)
\r
212 case 0: // SVN Status
\r
213 GetColumnStatus(path, pscd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
\r
214 GitStatus::GetStatusString(g_hResInst, filestatus, buf, sizeof(buf)/sizeof(TCHAR), (WORD)CRegStdWORD(_T("Software\\TortoiseGit\\LanguageID"), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)));
\r
217 case 1: // SVN Revision
\r
219 GetColumnStatus(path, pscd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
\r
220 if (columnrev >= 0)
\r
222 V_VT(pvarData) = VT_I4;
\r
223 V_I4(pvarData) = columnrev;
\r
229 GetColumnStatus(path, pscd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
\r
232 case 3: // SVN Short Url
\r
233 GetColumnStatus(path, pscd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
\r
234 szInfo = itemshorturl;
\r
236 case 5: // SVN mime-type
\r
238 if (cachetype == ShellCache::none)
\r
240 if (g_ShellCache.IsPathAllowed(path))
\r
242 SVNProperties props = SVNProperties(CTSVNPath(path), false);
\r
243 for (int i=0; i<props.GetCount(); i++)
\r
245 if (props.GetItemName(i).compare(_T("svn:mime-type"))==0)
\r
247 szInfo = MultibyteToWide((char *)props.GetItemValue(i).c_str());
\r
253 case 6: // SVN Lock Owner
\r
254 GetColumnStatus(path, pscd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
\r
257 case 7: // SVN eol-style
\r
259 if (cachetype == ShellCache::none)
\r
261 if (g_ShellCache.IsPathAllowed(path))
\r
263 SVNProperties props = SVNProperties(CTSVNPath(path), false);
\r
264 for (int i=0; i<props.GetCount(); i++)
\r
266 if (props.GetItemName(i).compare(_T("svn:eol-style"))==0)
\r
268 szInfo = MultibyteToWide((char *)props.GetItemValue(i).c_str());
\r
277 const WCHAR * wsInfo = szInfo.c_str();
\r
278 V_VT(pvarData) = VT_BSTR;
\r
279 V_BSTR(pvarData) = SysAllocString(wsInfo);
\r
282 if ((pscid->fmtid == FMTID_SummaryInformation)||(pscid->pid == 8))
\r
285 const TCHAR * path = pscd->wszFile;
\r
287 if (cachetype == ShellCache::none)
\r
289 switch (pscid->pid)
\r
291 case PIDSI_AUTHOR: // author
\r
293 GetColumnStatus(path, pscd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
\r
294 szInfo = columnauthor;
\r
299 wide_string wsInfo = szInfo;
\r
300 V_VT(pvarData) = VT_BSTR;
\r
301 V_BSTR(pvarData) = SysAllocString(wsInfo.c_str());
\r
308 STDMETHODIMP CShellExt::Initialize(LPCSHCOLUMNINIT psci)
\r
310 // psci->wszFolder (WCHAR) holds the path to the folder to be displayed
\r
311 // Should check to see if its a "SVN" folder and if not return E_FAIL
\r
313 PreserveChdir preserveChdir;
\r
314 if (g_ShellCache.IsColumnsEveryWhere())
\r
316 std::wstring path = psci->wszFolder;
\r
319 if (! g_ShellCache.HasSVNAdminDir(path.c_str(), TRUE))
\r
322 columnfilepath = _T("");
\r
326 void CShellExt::GetColumnStatus(const TCHAR * path, BOOL bIsDir)
\r
329 PreserveChdir preserveChdir;
\r
330 if (_tcscmp(path, columnfilepath.c_str())==0)
\r
333 columnfilepath = path;
\r
334 const FileStatusCacheEntry * status = NULL;
\r
335 TSVNCacheResponse itemStatus;
\r
336 ShellCache::CacheType t = ShellCache::exe;
\r
337 AutoLocker lock(g_csGlobalCOMGuard);
\r
338 t = g_ShellCache.GetCacheType();
\r
342 case ShellCache::exe:
\r
344 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
\r
345 if(m_remoteCacheLink.GetStatusFromRemoteCache(CTGitPath(path), &itemStatus, true))
\r
347 filestatus = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
\r
351 filestatus = git_wc_status_none;
\r
352 columnauthor.clear();
\r
353 columnrev = GIT_INVALID_REVNUM;
\r
355 itemshorturl.clear();
\r
361 case ShellCache::dll:
\r
362 case ShellCache::dllFull:
\r
364 status = m_CachedStatus.GetFullStatus(CTGitPath(path), bIsDir, TRUE);
\r
365 filestatus = status->status;
\r
369 case ShellCache::none:
\r
371 if (g_ShellCache.HasSVNAdminDir(path, bIsDir))
\r
372 filestatus = git_wc_status_normal;
\r
374 filestatus = git_wc_status_none;
\r
375 columnauthor.clear();
\r
376 columnrev = GIT_INVALID_REVNUM;
\r
378 itemshorturl.clear();
\r
385 if (t == ShellCache::exe)
\r
387 columnauthor = UTF8ToWide(itemStatus.m_author);
\r
388 columnrev = itemStatus.m_entry.cmt_rev;
\r
389 itemurl = UTF8ToWide(itemStatus.m_url);
\r
390 owner = UTF8ToWide(itemStatus.m_owner);
\r
396 columnauthor = UTF8ToWide(status->author);
\r
397 columnrev = status->rev;
\r
398 itemurl = UTF8ToWide(status->url);
\r
399 owner = UTF8ToWide(status->owner);
\r
402 TCHAR urlpath[INTERNET_MAX_URL_LENGTH+1];
\r
404 URL_COMPONENTS urlComponents;
\r
405 memset(&urlComponents, 0, sizeof(URL_COMPONENTS));
\r
406 urlComponents.dwStructSize = sizeof(URL_COMPONENTS);
\r
407 urlComponents.dwUrlPathLength = INTERNET_MAX_URL_LENGTH;
\r
408 urlComponents.lpszUrlPath = urlpath;
\r
409 if (InternetCrackUrl(itemurl.c_str(), 0, ICU_DECODE, &urlComponents))
\r
411 // since the short url is shown as an additional column where the
\r
412 // file/foldername is shown too, we strip that name from the url
\r
413 // to make the url even shorter.
\r
414 TCHAR * ptr = _tcsrchr(urlComponents.lpszUrlPath, '/');
\r
416 ptr = _tcsrchr(urlComponents.lpszUrlPath, '\\');
\r
420 // to shorten the url even more, we check for 'trunk', 'branches' and 'tags'
\r
421 // and simply assume that these are the folders attached to the repository
\r
422 // root. If we find those, we strip the whole path before those folders too.
\r
423 // Note: this will strip too much if such a folder is *below* the repository
\r
424 // root - but it's called 'short url' and we're free to shorten it the way we
\r
426 /*ptr = _tcsstr(urlComponents.lpszUrlPath, _T("/trunk"));
\r
428 ptr = _tcsstr(urlComponents.lpszUrlPath, _T("\\trunk"));
\r
429 if ((ptr == NULL)||((*(ptr+6) != 0)&&(*(ptr+6) != '/')&&(*(ptr+6) != '\\')))
\r
431 ptr = _tcsstr(urlComponents.lpszUrlPath, _T("/branches"));
\r
433 ptr = _tcsstr(urlComponents.lpszUrlPath, _T("\\branches"));
\r
434 if ((ptr == NULL)||((*(ptr+9) != 0)&&(*(ptr+9) != '/')&&(*(ptr+9) != '\\')))
\r
436 ptr = _tcsstr(urlComponents.lpszUrlPath, _T("/tags"));
\r
438 ptr = _tcsstr(urlComponents.lpszUrlPath, _T("\\tags"));
\r
439 if ((ptr)&&(*(ptr+5) != 0)&&(*(ptr+5) != '/')&&(*(ptr+5) != '\\'))
\r
444 itemshorturl = ptr;
\r
446 itemshorturl = urlComponents.lpszUrlPath;
\r
449 itemshorturl = _T(" ");
\r
452 itemshorturl = _T(" ");
\r
456 char url[INTERNET_MAX_URL_LENGTH];
\r
457 strcpy_s(url, INTERNET_MAX_URL_LENGTH, status->url);
\r
458 CPathUtils::Unescape(url);
\r
459 itemurl = UTF8ToWide(url);
\r
461 else if (t == ShellCache::exe)
\r
463 char url[INTERNET_MAX_URL_LENGTH];
\r
464 strcpy_s(url, INTERNET_MAX_URL_LENGTH, itemStatus.m_url);
\r
465 CPathUtils::Unescape(url);
\r
466 itemurl = UTF8ToWide(url);
\r