ContextMenu.cpp 70.5 KB
Newer Older
李智's avatar
李智 committed
1
// TortoiseGit - a Windows shell extension for easy version control
Frank Li's avatar
Frank Li committed
2

3
// Copyright (C) 2003-2012, 2014-2015 - TortoiseSVN
4
// Copyright (C) 2008-2015 - TortoiseGit
Frank Li's avatar
Frank Li committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

// 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 "ShellExt.h"
#include "ItemIDList.h"
#include "PreserveChdir.h"
#include "UnicodeUtils.h"
李智's avatar
李智 committed
25 26
#include "GitStatus.h"
#include "TGitPath.h"
27
#include "PathUtils.h"
28
#include "CreateProcessHelper.h"
29
#include "FormatMessageWrapper.h"
30
#include "..\TGitCache\CacheInterface.h"
31
#include "resource.h"
Frank Li's avatar
Frank Li committed
32 33 34 35 36 37

#define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
#define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])

int g_shellidlist=RegisterClipboardFormat(CFSTR_SHELLIDLIST);

38
extern MenuInfo menuInfo[];
39
static int g_syncSeq = 0;
Frank Li's avatar
Frank Li committed
40 41 42

STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder,
                                   LPDATAOBJECT pDataObj,
43
                                   HKEY  hRegKey)
Frank Li's avatar
Frank Li committed
44
{
45 46 47 48 49 50 51 52 53
	__try
	{
		return Initialize_Wrap(pIDFolder, pDataObj, hRegKey);
	}
	__except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
	{
	}
	return E_FAIL;
}
李智's avatar
李智 committed
54

55 56 57 58
STDMETHODIMP CShellExt::Initialize_Wrap(LPCITEMIDLIST pIDFolder,
                                        LPDATAOBJECT pDataObj,
                                        HKEY /* hRegKey */)
{
59
	CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: Initialize\n");
Frank Li's avatar
Frank Li committed
60 61
	PreserveChdir preserveChdir;
	files_.clear();
62 63 64
	folder_.clear();
	uuidSource.clear();
	uuidTarget.clear();
Frank Li's avatar
Frank Li committed
65 66 67
	itemStates = 0;
	itemStatesFolder = 0;
	stdstring statuspath;
李智's avatar
李智 committed
68
	git_wc_status_kind fetchedstatus = git_wc_status_none;
Frank Li's avatar
Frank Li committed
69 70 71 72 73
	// get selected files/folders
	if (pDataObj)
	{
		STGMEDIUM medium;
		FORMATETC fmte = {(CLIPFORMAT)g_shellidlist,
74 75 76
			(DVTARGETDEVICE FAR *)NULL,
			DVASPECT_CONTENT,
			-1,
Frank Li's avatar
Frank Li committed
77 78 79 80 81 82 83
			TYMED_HGLOBAL};
		HRESULT hres = pDataObj->GetData(&fmte, &medium);

		if (SUCCEEDED(hres) && medium.hGlobal)
		{
			if (m_State == FileStateDropHandler)
			{
李智's avatar
李智 committed
84

Frank Li's avatar
Frank Li committed
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
				FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
				STGMEDIUM stg = { TYMED_HGLOBAL };
				if ( FAILED( pDataObj->GetData ( &etc, &stg )))
				{
					ReleaseStgMedium ( &medium );
					return E_INVALIDARG;
				}


				HDROP drop = (HDROP)GlobalLock(stg.hGlobal);
				if ( NULL == drop )
				{
					ReleaseStgMedium ( &stg );
					ReleaseStgMedium ( &medium );
					return E_INVALIDARG;
				}

				int count = DragQueryFile(drop, (UINT)-1, NULL, 0);
				if (count == 1)
					itemStates |= ITEMIS_ONLYONE;
				for (int i = 0; i < count; i++)
				{
					// find the path length in chars
					UINT len = DragQueryFile(drop, i, NULL, 0);
					if (len == 0)
						continue;
Sven Strickroth's avatar
Sven Strickroth committed
111 112
					std::unique_ptr<TCHAR[]> szFileName(new TCHAR[len + 1]);
					if (0 == DragQueryFile(drop, i, szFileName.get(), len + 1))
Frank Li's avatar
Frank Li committed
113
						continue;
Sven Strickroth's avatar
Sven Strickroth committed
114 115
					stdstring str = stdstring(szFileName.get());
					if ((!str.empty()) && (g_ShellCache.IsContextPathAllowed(szFileName.get())))
Frank Li's avatar
Frank Li committed
116 117
					{
						{
李智's avatar
李智 committed
118
							CTGitPath strpath;
Frank Li's avatar
Frank Li committed
119 120 121 122 123 124 125
							strpath.SetFromWin(str.c_str());
							itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE : 0;
							itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE : 0;
						}
						files_.push_back(str);
						if (i == 0)
						{
ch3cooli's avatar
ch3cooli committed
126
							//get the git status of the item
李智's avatar
李智 committed
127 128
							git_wc_status_kind status = git_wc_status_none;
							CTGitPath askedpath;
Frank Li's avatar
Frank Li committed
129
							askedpath.SetFromWin(str.c_str());
130 131 132
							CString workTreePath;
							askedpath.HasAdminDir(&workTreePath);
							uuidSource = workTreePath;
Frank Li's avatar
Frank Li committed
133 134
							try
							{
135
								if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
Frank Li's avatar
Frank Li committed
136
								{
137 138
									CTGitPath tpath(str.c_str());
									if (!tpath.HasAdminDir())
139
									{
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
										status = git_wc_status_none;
										continue;
									}
									if (tpath.IsAdminDir())
									{
										status = git_wc_status_none;
										continue;
									}
									TGITCacheResponse itemStatus;
									SecureZeroMemory(&itemStatus, sizeof(itemStatus));
									if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
									{
										fetchedstatus = status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
										if (askedpath.IsDirectory())//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;
										}
159
									}
Frank Li's avatar
Frank Li committed
160 161 162
								}
								else
								{
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
									GitStatus stat;
									stat.GetStatus(CTGitPath(str.c_str()), false, false, true);
									if (stat.status)
									{
										statuspath = str;
										status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
										fetchedstatus = status;
										if ( askedpath.IsDirectory() )//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->uuid))
										//	uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
									}
									else
									{
										// sometimes, git_client_status() returns with an error.
										// in that case, we have to check if the working copy is versioned
										// anyway to show the 'correct' context menu
										if (askedpath.HasAdminDir())
											status = git_wc_status_normal;
									}
Frank Li's avatar
Frank Li committed
187 188 189 190
								}
							}
							catch ( ... )
							{
191
								CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
Frank Li's avatar
Frank Li committed
192
							}
193

194 195
							// TODO: should we really assume any sub-directory to be versioned
							//       or only if it contains versioned files
196
							itemStates |= askedpath.GetAdminDirMask();
197

198
							if ((status == git_wc_status_unversioned) || (status == git_wc_status_ignored) || (status == git_wc_status_none))
199
								itemStates &= ~ITEMIS_INGIT;
200

李智's avatar
李智 committed
201
							if (status == git_wc_status_ignored)
Frank Li's avatar
Frank Li committed
202
								itemStates |= ITEMIS_IGNORED;
李智's avatar
李智 committed
203
							if (status == git_wc_status_normal)
Frank Li's avatar
Frank Li committed
204
								itemStates |= ITEMIS_NORMAL;
李智's avatar
李智 committed
205
							if (status == git_wc_status_conflicted)
Frank Li's avatar
Frank Li committed
206
								itemStates |= ITEMIS_CONFLICTED;
李智's avatar
李智 committed
207
							if (status == git_wc_status_added)
Frank Li's avatar
Frank Li committed
208
								itemStates |= ITEMIS_ADDED;
李智's avatar
李智 committed
209
							if (status == git_wc_status_deleted)
Frank Li's avatar
Frank Li committed
210 211 212 213 214 215
								itemStates |= ITEMIS_DELETED;
						}
					}
				} // for (int i = 0; i < count; i++)
				GlobalUnlock ( drop );
				ReleaseStgMedium ( &stg );
李智's avatar
李智 committed
216

217
			} // if (m_State == FileStateDropHandler)
Frank Li's avatar
Frank Li committed
218 219
			else
			{
李智's avatar
李智 committed
220

Frank Li's avatar
Frank Li committed
221 222 223 224 225 226 227 228 229 230 231 232
				//Enumerate PIDLs which the user has selected
				CIDA* cida = (CIDA*)GlobalLock(medium.hGlobal);
				ItemIDList parent( GetPIDLFolder (cida));

				int count = cida->cidl;
				BOOL statfetched = FALSE;
				for (int i = 0; i < count; ++i)
				{
					ItemIDList child (GetPIDLItem (cida, i), &parent);
					stdstring str = child.toString();
					if ((str.empty() == false)&&(g_ShellCache.IsContextPathAllowed(str.c_str())))
					{
ch3cooli's avatar
ch3cooli committed
233
						//check if our menu is requested for a git admin directory
234
						if (GitAdminDir::IsAdminDirPath(str.c_str()))
Frank Li's avatar
Frank Li committed
235 236 237
							continue;

						files_.push_back(str);
李智's avatar
李智 committed
238
						CTGitPath strpath;
Frank Li's avatar
Frank Li committed
239 240 241 242 243
						strpath.SetFromWin(str.c_str());
						itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".diff"))==0) ? ITEMIS_PATCHFILE : 0;
						itemStates |= (strpath.GetFileExtension().CompareNoCase(_T(".patch"))==0) ? ITEMIS_PATCHFILE : 0;
						if (!statfetched)
						{
ch3cooli's avatar
ch3cooli committed
244
							//get the git status of the item
李智's avatar
李智 committed
245
							git_wc_status_kind status = git_wc_status_none;
Frank Li's avatar
Frank Li committed
246 247 248
							if ((g_ShellCache.IsSimpleContext())&&(strpath.IsDirectory()))
							{
								if (strpath.HasAdminDir())
李智's avatar
李智 committed
249
									status = git_wc_status_normal;
Frank Li's avatar
Frank Li committed
250 251 252 253 254
							}
							else
							{
								try
								{
255
									if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
Frank Li's avatar
Frank Li committed
256
									{
257 258
										CTGitPath tpath(str.c_str());
										if(!tpath.HasAdminDir())
259
										{
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
											status = git_wc_status_none;
											continue;
										}
										if(tpath.IsAdminDir())
										{
											status = git_wc_status_none;
											continue;
										}
										TGITCacheResponse itemStatus;
										SecureZeroMemory(&itemStatus, sizeof(itemStatus));
										if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
										{
											fetchedstatus = status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
											if (strpath.IsDirectory())//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 (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
												itemStates |= ITEMIS_CONFLICTED;
281
										}
282
									}
Frank Li's avatar
Frank Li committed
283 284
									else
									{
285
										GitStatus stat;
Frank Li's avatar
Frank Li committed
286
										if (strpath.HasAdminDir())
287 288 289
											stat.GetStatus(strpath, false, false, true);
										statuspath = str;
										if (stat.status)
Frank Li's avatar
Frank Li committed
290
										{
291
											status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
Frank Li's avatar
Frank Li committed
292
											fetchedstatus = status;
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
											if ( strpath.IsDirectory() )//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;
											}
											// TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
											if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
												itemStates |= ITEMIS_CONFLICTED;
											//if ((stat.status->entry)&&(stat.status->entry->uuid))
											//	uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
										}
										else
										{
											// sometimes, git_client_status() returns with an error.
											// in that case, we have to check if the working copy is versioned
											// anyway to show the 'correct' context menu
											if (strpath.HasAdminDir())
											{
												status = git_wc_status_normal;
												fetchedstatus = status;
											}
Frank Li's avatar
Frank Li committed
315 316 317 318 319 320
										}
									}
									statfetched = TRUE;
								}
								catch ( ... )
								{
321
									CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
Frank Li's avatar
Frank Li committed
322 323
								}
							}
324

325 326
							itemStates |= strpath.GetAdminDirMask();

327
							if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
328
								itemStates &= ~ITEMIS_INGIT;
李智's avatar
李智 committed
329
							if (status == git_wc_status_ignored)
Frank Li's avatar
Frank Li committed
330 331 332
							{
								itemStates |= ITEMIS_IGNORED;
							}
333

李智's avatar
李智 committed
334
							if (status == git_wc_status_normal)
Frank Li's avatar
Frank Li committed
335
								itemStates |= ITEMIS_NORMAL;
李智's avatar
李智 committed
336
							if (status == git_wc_status_conflicted)
Frank Li's avatar
Frank Li committed
337
								itemStates |= ITEMIS_CONFLICTED;
李智's avatar
李智 committed
338
							if (status == git_wc_status_added)
Frank Li's avatar
Frank Li committed
339
								itemStates |= ITEMIS_ADDED;
李智's avatar
李智 committed
340
							if (status == git_wc_status_deleted)
Frank Li's avatar
Frank Li committed
341 342 343 344 345
								itemStates |= ITEMIS_DELETED;
						}
					}
				} // for (int i = 0; i < count; ++i)
				ItemIDList child (GetPIDLItem (cida, 0), &parent);
346
				if (g_ShellCache.HasGITAdminDir(child.toString().c_str(), FALSE))
347
					itemStates |= ITEMIS_INVERSIONEDFOLDER;
348

349
				if (GitAdminDir::IsBareRepo(child.toString().c_str()))
350 351
					itemStates = ITEMIS_BAREREPO;

Frank Li's avatar
Frank Li committed
352 353 354 355
				GlobalUnlock(medium.hGlobal);

				// if the item is a versioned folder, check if there's a patch file
				// in the clipboard to be used in "Apply Patch"
356
				UINT cFormatDiff = RegisterClipboardFormat(_T("TGIT_UNIFIEDDIFF"));
Frank Li's avatar
Frank Li committed
357 358
				if (cFormatDiff)
				{
359
					if (IsClipboardFormatAvailable(cFormatDiff))
Frank Li's avatar
Frank Li committed
360 361
						itemStates |= ITEMIS_PATCHINCLIPBOARD;
				}
362
				if (IsClipboardFormatAvailable(CF_HDROP))
Frank Li's avatar
Frank Li committed
363
					itemStates |= ITEMIS_PATHINCLIPBOARD;
李智's avatar
李智 committed
364

Frank Li's avatar
Frank Li committed
365 366 367 368 369 370 371 372 373 374 375 376 377 378
			}

			ReleaseStgMedium ( &medium );
			if (medium.pUnkForRelease)
			{
				IUnknown* relInterface = (IUnknown*)medium.pUnkForRelease;
				relInterface->Release();
			}
		}
	}

	// get folder background
	if (pIDFolder)
	{
李智's avatar
李智 committed
379

Frank Li's avatar
Frank Li committed
380 381
		ItemIDList list(pIDFolder);
		folder_ = list.toString();
李智's avatar
李智 committed
382
		git_wc_status_kind status = git_wc_status_none;
383
		if (IsClipboardFormatAvailable(CF_HDROP))
Frank Li's avatar
Frank Li committed
384
			itemStatesFolder |= ITEMIS_PATHINCLIPBOARD;
385

386 387 388
		CTGitPath askedpath;
		askedpath.SetFromWin(folder_.c_str());

389
		if (g_ShellCache.IsContextPathAllowed(folder_.c_str()))
Frank Li's avatar
Frank Li committed
390
		{
391
			if (folder_.compare(statuspath)!=0)
Frank Li's avatar
Frank Li committed
392
			{
393 394 395
				CString worktreePath;
				askedpath.HasAdminDir(&worktreePath);
				uuidTarget = worktreePath;
396
				try
Frank Li's avatar
Frank Li committed
397
				{
398
					if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(folder_.c_str()))
399
					{
400 401 402 403 404 405 406 407 408 409 410 411
						CTGitPath tpath(folder_.c_str());
						if(!tpath.HasAdminDir())
							status = git_wc_status_none;
						else if(tpath.IsAdminDir())
							status = git_wc_status_none;
						else
						{
							TGITCacheResponse itemStatus;
							SecureZeroMemory(&itemStatus, sizeof(itemStatus));
							if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
								status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
						}
412 413 414
					}
					else
					{
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
						GitStatus stat;
						stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
						if (stat.status)
						{
							status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
							//					if ((stat.status->entry)&&(stat.status->entry->uuid))
							//						uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);

						}
						else
						{
							// sometimes, git_client_status() returns with an error.
							// in that case, we have to check if the working copy is versioned
							// anyway to show the 'correct' context menu
							if (askedpath.HasAdminDir())
								status = git_wc_status_normal;
						}
432 433 434
					}

					//if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
435
					itemStatesFolder |= askedpath.GetAdminDirMask();
436

437
					if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
438
						itemStates &= ~ITEMIS_INGIT;
439

440 441 442 443 444 445 446 447 448
					if (status == git_wc_status_normal)
						itemStatesFolder |= ITEMIS_NORMAL;
					if (status == git_wc_status_conflicted)
						itemStatesFolder |= ITEMIS_CONFLICTED;
					if (status == git_wc_status_added)
						itemStatesFolder |= ITEMIS_ADDED;
					if (status == git_wc_status_deleted)
						itemStatesFolder |= ITEMIS_DELETED;

Frank Li's avatar
Frank Li committed
449
				}
450
				catch ( ... )
Frank Li's avatar
Frank Li committed
451
				{
452
					CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
Frank Li's avatar
Frank Li committed
453 454
				}
			}
455
			else
Frank Li's avatar
Frank Li committed
456
			{
457
				status = fetchedstatus;
Frank Li's avatar
Frank Li committed
458
			}
459 460 461 462 463 464
			//if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
			itemStatesFolder |= askedpath.GetAdminDirMask();

			if (status == git_wc_status_ignored)
				itemStatesFolder |= ITEMIS_IGNORED;
			itemStatesFolder |= ITEMIS_FOLDER;
465
			if (files_.empty())
466 467 468
				itemStates |= ITEMIS_ONLYONE;
			if (m_State != FileStateDropHandler)
				itemStates |= itemStatesFolder;
469 470
		}
		else
Frank Li's avatar
Frank Li committed
471
		{
472
			folder_.clear();
Frank Li's avatar
Frank Li committed
473 474 475 476 477 478 479
			status = fetchedstatus;
		}
	}
	if (files_.size() == 2)
		itemStates |= ITEMIS_TWO;
	if ((files_.size() == 1)&&(g_ShellCache.IsContextPathAllowed(files_.front().c_str())))
	{
李智's avatar
李智 committed
480

Frank Li's avatar
Frank Li committed
481 482 483 484 485 486
		itemStates |= ITEMIS_ONLYONE;
		if (m_State != FileStateDropHandler)
		{
			if (PathIsDirectory(files_.front().c_str()))
			{
				folder_ = files_.front();
李智's avatar
李智 committed
487
				git_wc_status_kind status = git_wc_status_none;
488 489 490
				CTGitPath askedpath;
				askedpath.SetFromWin(folder_.c_str());

Frank Li's avatar
Frank Li committed
491
				if (folder_.compare(statuspath)!=0)
492
				{
Frank Li's avatar
Frank Li committed
493 494
					try
					{
李智's avatar
李智 committed
495
						GitStatus stat;
496
						stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
Frank Li's avatar
Frank Li committed
497 498
						if (stat.status)
						{
李智's avatar
李智 committed
499
							status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
李智's avatar
李智 committed
500 501
//							if ((stat.status->entry)&&(stat.status->entry->uuid))
//								uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
Frank Li's avatar
Frank Li committed
502 503 504 505
						}
					}
					catch ( ... )
					{
506
						CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
Frank Li's avatar
Frank Li committed
507 508 509 510 511 512
					}
				}
				else
				{
					status = fetchedstatus;
				}
513
				//if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
514
				itemStates |= askedpath.GetAdminDirMask();
515

516
				if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
517
					itemStates &= ~ITEMIS_INGIT;
518

李智's avatar
李智 committed
519
				if (status == git_wc_status_ignored)
Frank Li's avatar
Frank Li committed
520 521
					itemStates |= ITEMIS_IGNORED;
				itemStates |= ITEMIS_FOLDER;
李智's avatar
李智 committed
522
				if (status == git_wc_status_added)
Frank Li's avatar
Frank Li committed
523
					itemStates |= ITEMIS_ADDED;
李智's avatar
李智 committed
524
				if (status == git_wc_status_deleted)
Frank Li's avatar
Frank Li committed
525
					itemStates |= ITEMIS_DELETED;
526

Frank Li's avatar
Frank Li committed
527 528
			}
		}
529

Frank Li's avatar
Frank Li committed
530
	}
531

Sven Strickroth's avatar
Sven Strickroth committed
532
	return S_OK;
Frank Li's avatar
Frank Li committed
533 534
}

535
void CShellExt::InsertGitMenu(BOOL istop, HMENU menu, UINT pos, UINT_PTR id, UINT stringid, UINT icon, UINT idCmdFirst, GitCommands com, UINT /*uFlags*/)
Frank Li's avatar
Frank Li committed
536
{
537
	TCHAR menutextbuffer[512] = {0};
Frank Li's avatar
Frank Li committed
538 539 540 541 542
	TCHAR verbsbuffer[255] = {0};
	MAKESTRING(stringid);

	if (istop)
	{
李智's avatar
李智 committed
543
		//menu entry for the top context menu, so append an "Git " before
Frank Li's avatar
Frank Li committed
544
		//the menu text to indicate where the entry comes from
李智's avatar
李智 committed
545
		_tcscpy_s(menutextbuffer, 255, _T("Git "));
546 547 548 549 550 551 552
		if (!g_ShellCache.HasShellMenuAccelerators())
		{
			// remove the accelerators
			tstring temp = stringtablebuffer;
			temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
			_tcscpy_s(stringtablebuffer, 255, temp.c_str());
		}
Frank Li's avatar
Frank Li committed
553 554
	}
	_tcscat_s(menutextbuffer, 255, stringtablebuffer);
Sven Strickroth's avatar
Sven Strickroth committed
555

556 557 558 559 560 561 562 563 564 565
	// insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
	// so we have an easy and fast way to check the current branch
	// (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
	if (com == ShellMenuCommit)
	{
		// get branch name
		CTGitPath path(folder_.empty() ? files_.front().c_str() : folder_.c_str());
		CString sProjectRoot;
		CString sBranchName;

566 567 568 569
		if (path.GetAdminDirMask() & ITEMIS_SUBMODULE)
		{
			if (istop)
				_tcscpy_s(menutextbuffer, 255, _T("Git "));
570 571
			else
				menutextbuffer[0] = '\0';
572 573
			MAKESTRING(IDS_MENUCOMMITSUBMODULE);
			_tcscat_s(menutextbuffer, 255, stringtablebuffer);
574 575
		}

576
		if (path.HasAdminDir(&sProjectRoot) && !CGit::GetCurrentBranchFromFile(sProjectRoot, sBranchName))
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
		{
			if (sBranchName.GetLength() == 40)
			{
				// if SHA1 only show 4 first bytes
				BOOL bIsSha1 = TRUE;
				for (int i=0; i<40; i++)
					if ( !iswxdigit(sBranchName[i]) )
					{
						bIsSha1 = FALSE;
						break;
					}
				if (bIsSha1)
					sBranchName = sBranchName.Left(8) + _T("....");
			}

			// sanity check
			if (sBranchName.GetLength() > 64)
				sBranchName = sBranchName.Left(64) + _T("...");

			// scan to before "..."
			LPTSTR s = menutextbuffer + _tcslen(menutextbuffer)-1;
			if (s > menutextbuffer)
			{
				while (s > menutextbuffer)
				{
					if (*s != _T('.'))
					{
						s++;
						break;
					}
					s--;
				}
			}
			else
			{
				s = menutextbuffer;
			}

			// append branch name and end with ...
616
			_tcscpy_s(s, 255 - _tcslen(menutextbuffer) - 1, _T(" -> \"") + sBranchName + _T("\"..."));
617 618
		}
	}
ch3cooli's avatar
ch3cooli committed
619 620 621 622 623 624 625 626 627

	if (com == ShellMenuDiffLater)
	{
		std::wstring sPath = regDiffLater;
		if (!sPath.empty())
		{
			// add the path of the saved file
			wchar_t compact[40] = {0};
			PathCompactPathEx(compact, sPath.c_str(), _countof(compact) - 1, 0);
628
			MAKESTRING(IDS_MENUDIFFNOW);
ch3cooli's avatar
ch3cooli committed
629
			CString sMenu;
630
			sMenu.Format(CString(stringtablebuffer), compact);
ch3cooli's avatar
ch3cooli committed
631 632 633
			wcscpy_s(menutextbuffer, sMenu);
		}
	}
Sven Strickroth's avatar
Sven Strickroth committed
634

635 636 637 638 639 640 641
	MENUITEMINFO menuiteminfo;
	SecureZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
	menuiteminfo.cbSize = sizeof(menuiteminfo);
	menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
	menuiteminfo.fType = MFT_STRING;
	menuiteminfo.dwTypeData = menutextbuffer;
	if (icon)
Frank Li's avatar
Frank Li committed
642
	{
643
		menuiteminfo.fMask |= MIIM_BITMAP;
644
		menuiteminfo.hbmpItem = SysInfo::Instance().IsVistaOrLater() ? m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon) : HBMMENU_CALLBACK;
Frank Li's avatar
Frank Li committed
645
	}
646
	menuiteminfo.wID = (UINT)id;
647
	InsertMenuItem(menu, pos, TRUE, &menuiteminfo);
Frank Li's avatar
Frank Li committed
648 649 650

	if (istop)
	{
李智's avatar
李智 committed
651
		//menu entry for the top context menu, so append an "Git " before
Frank Li's avatar
Frank Li committed
652
		//the menu text to indicate where the entry comes from
李智's avatar
李智 committed
653
		_tcscpy_s(menutextbuffer, 255, _T("Git "));
Frank Li's avatar
Frank Li committed
654
	}
655
	LoadString(g_hResInst, stringid, verbsbuffer, _countof(verbsbuffer));
Frank Li's avatar
Frank Li committed
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678
	_tcscat_s(menutextbuffer, 255, verbsbuffer);
	stdstring verb = stdstring(menutextbuffer);
	if (verb.find('&') != -1)
	{
		verb.erase(verb.find('&'),1);
	}
	myVerbsMap[verb] = id - idCmdFirst;
	myVerbsMap[verb] = id;
	myVerbsIDMap[id - idCmdFirst] = verb;
	myVerbsIDMap[id] = verb;
	// We store the relative and absolute diameter
	// (drawitem callback uses absolute, others relative)
	myIDMap[id - idCmdFirst] = com;
	myIDMap[id] = com;
	if (!istop)
		mySubMenuMap[pos] = com;
}

bool CShellExt::WriteClipboardPathsToTempFile(stdstring& tempfile)
{
	bool bRet = true;
	tempfile = stdstring();
	//write all selected files and paths to a temporary file
679
	//for TortoiseGitProc.exe to read out again.
Frank Li's avatar
Frank Li committed
680
	DWORD written = 0;
681
	DWORD pathlength = GetTortoiseGitTempPath(0, NULL);
Sven Strickroth's avatar
Sven Strickroth committed
682 683 684 685 686
	std::unique_ptr<TCHAR[]> path(new TCHAR[pathlength + 1]);
	std::unique_ptr<TCHAR[]> tempFile(new TCHAR[pathlength + 100]);
	GetTortoiseGitTempPath(pathlength+1, path.get());
	GetTempFileName(path.get(), _T("git"), 0, tempFile.get());
	tempfile = stdstring(tempFile.get());
Frank Li's avatar
Frank Li committed
687

Sven Strickroth's avatar
Sven Strickroth committed
688
	CAutoFile file = ::CreateFile(tempFile.get(),
689 690 691 692
		GENERIC_WRITE,
		FILE_SHARE_READ,
		0,
		CREATE_ALWAYS,
Frank Li's avatar
Frank Li committed
693 694 695
		FILE_ATTRIBUTE_TEMPORARY,
		0);

Sven Strickroth's avatar
Sven Strickroth committed
696
	if (!file)
Frank Li's avatar
Frank Li committed
697 698 699 700 701 702 703 704 705 706 707 708
		return false;

	if (!IsClipboardFormatAvailable(CF_HDROP))
		return false;
	if (!OpenClipboard(NULL))
		return false;

	stdstring sClipboardText;
	HGLOBAL hglb = GetClipboardData(CF_HDROP);
	HDROP hDrop = (HDROP)GlobalLock(hglb);
	if(hDrop != NULL)
	{
Sven Strickroth's avatar
Sven Strickroth committed
709
		TCHAR szFileName[MAX_PATH] = {0};
710
		UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
Frank Li's avatar
Frank Li committed
711 712
		for(UINT i = 0; i < cFiles; ++i)
		{
713
			DragQueryFile(hDrop, i, szFileName, _countof(szFileName));
Frank Li's avatar
Frank Li committed
714
			stdstring filename = szFileName;
715
			::WriteFile (file, filename.c_str(), (DWORD)filename.size()*sizeof(TCHAR), &written, 0);
Frank Li's avatar
Frank Li committed
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730
			::WriteFile (file, _T("\n"), 2, &written, 0);
		}
		GlobalUnlock(hDrop);
	}
	else bRet = false;
	GlobalUnlock(hglb);

	CloseClipboard();

	return bRet;
}

stdstring CShellExt::WriteFileListToTempFile()
{
	//write all selected files and paths to a temporary file
731
	//for TortoiseGitProc.exe to read out again.
732
	DWORD pathlength = GetTortoiseGitTempPath(0, NULL);
Sven Strickroth's avatar
Sven Strickroth committed
733 734 735 736 737
	std::unique_ptr<TCHAR[]> path(new TCHAR[pathlength + 1]);
	std::unique_ptr<TCHAR[]> tempFile(new TCHAR[pathlength + 100]);
	GetTortoiseGitTempPath(pathlength + 1, path.get());
	GetTempFileName(path.get(), _T("git"), 0, tempFile.get());
	stdstring retFilePath = stdstring(tempFile.get());
738

Sven Strickroth's avatar
Sven Strickroth committed
739
	CAutoFile file = ::CreateFile (tempFile.get(),
740 741 742 743
								GENERIC_WRITE,
								FILE_SHARE_READ,
								0,
								CREATE_ALWAYS,
Frank Li's avatar
Frank Li committed
744 745 746
								FILE_ATTRIBUTE_TEMPORARY,
								0);

Sven Strickroth's avatar
Sven Strickroth committed
747
	if (!file)
Frank Li's avatar
Frank Li committed
748
		return stdstring();
749

Frank Li's avatar
Frank Li committed
750 751 752
	DWORD written = 0;
	if (files_.empty())
	{
753
		::WriteFile (file, folder_.c_str(), (DWORD)folder_.size()*sizeof(TCHAR), &written, 0);
Frank Li's avatar
Frank Li committed
754 755 756 757 758
		::WriteFile (file, _T("\n"), 2, &written, 0);
	}

	for (std::vector<stdstring>::iterator I = files_.begin(); I != files_.end(); ++I)
	{
759
		::WriteFile (file, I->c_str(), (DWORD)I->size()*sizeof(TCHAR), &written, 0);
Frank Li's avatar
Frank Li committed
760 761 762 763 764 765 766
		::WriteFile (file, _T("\n"), 2, &written, 0);
	}
	return retFilePath;
}

STDMETHODIMP CShellExt::QueryDropContext(UINT uFlags, UINT idCmdFirst, HMENU hMenu, UINT &indexMenu)
{
767 768 769
	if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE))
		return S_OK;

Frank Li's avatar
Frank Li committed
770 771 772 773
	PreserveChdir preserveChdir;
	LoadLangDll();

	if ((uFlags & CMF_DEFAULTONLY)!=0)
Sven Strickroth's avatar
Sven Strickroth committed
774
		return S_OK;					//we don't change the default action
Frank Li's avatar
Frank Li committed
775

776
	if (files_.empty() || folder_.empty())
Sven Strickroth's avatar
Sven Strickroth committed
777
		return S_OK;
Frank Li's avatar
Frank Li committed
778 779

	if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
Sven Strickroth's avatar
Sven Strickroth committed
780
		return S_OK;
Frank Li's avatar
Frank Li committed
781 782 783 784 785

	bool bSourceAndTargetFromSameRepository = (uuidSource.compare(uuidTarget) == 0) || uuidSource.empty() || uuidTarget.empty();

	//the drop handler only has eight commands, but not all are visible at the same time:
	//if the source file(s) are under version control then those files can be moved
786
	//to the new location or they can be moved with a rename,
Frank Li's avatar
Frank Li committed
787 788 789 790
	//if they are unversioned then they can be added to the working copy
	//if they are versioned, they also can be exported to an unversioned location
	UINT idCmd = idCmdFirst;

791
	bool moveAvailable = false;
李智's avatar
李智 committed
792
	// Git move here
Frank Li's avatar
Frank Li committed
793
	// available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
794 795
	if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && ((itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && ((~itemStates) & ITEMIS_ADDED)))
	{
796
		InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVEMENU, 0, idCmdFirst, ShellMenuDropMove, uFlags);
797 798
		moveAvailable = true;
	}
Frank Li's avatar
Frank Li committed
799

李智's avatar
李智 committed
800
	// Git move and rename here
Frank Li's avatar
Frank Li committed
801
	// available if source is a single, versioned but not added item, target is versioned, source and target from same repository or target folder is added
802 803
	if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && (itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && (itemStates & ITEMIS_ONLYONE) && ((~itemStates) & ITEMIS_ADDED))
	{
804
		InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVERENAMEMENU, 0, idCmdFirst, ShellMenuDropMoveRename, uFlags);
805 806
		moveAvailable = true;
	}
Frank Li's avatar
Frank Li committed
807

李智's avatar
李智 committed
808
	// Git copy here
Frank Li's avatar
Frank Li committed
809
	// available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
810 811
	//if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
	//	InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
Frank Li's avatar
Frank Li committed
812

李智's avatar
李智 committed
813
	// Git copy and rename here, source and target from same repository
Frank Li's avatar
Frank Li committed
814
	// available if source is a single, versioned but not added item, target is versioned or target folder is added
815 816
	//if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
	//	InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
Frank Li's avatar
Frank Li committed
817

李智's avatar
李智 committed
818
	// Git add here
Frank Li's avatar
Frank Li committed
819
	// available if target is versioned and source is either unversioned or from another repository
820
	if ((itemStatesFolder & ITEMIS_FOLDERINGIT) && (((~itemStates) & ITEMIS_INGIT) || !bSourceAndTargetFromSameRepository) && !moveAvailable)
821
		InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYADDMENU, 0, idCmdFirst, ShellMenuDropCopyAdd, uFlags);
Frank Li's avatar
Frank Li committed
822

李智's avatar
李智 committed
823
	// Git export here
Frank Li's avatar
Frank Li committed
824
	// available if source is versioned and a folder
825
	//if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
826
	//	InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
Frank Li's avatar
Frank Li committed
827

李智's avatar
李智 committed
828
	// Git export all here
Frank Li's avatar
Frank Li committed
829
	// available if source is versioned and a folder
830
	//if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
831
	//	InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
Frank Li's avatar
Frank Li committed
832 833 834 835

	// apply patch
	// available if source is a patchfile
	if (itemStates & ITEMIS_PATCHFILE)
836 837 838 839 840
	{
		if (itemStates & ITEMIS_ONLYONE)
			InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUAPPLYPATCH, 0, idCmdFirst, ShellMenuApplyPatch, uFlags);
		InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUIMPORTPATCH, 0, idCmdFirst, ShellMenuImportPatchDrop, uFlags);
	}
Frank Li's avatar
Frank Li committed
841 842 843

	// separator
	if (idCmd != idCmdFirst)
844
		InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
Frank Li's avatar
Frank Li committed
845

846 847
	TweakMenu(hMenu);

Frank Li's avatar
Frank Li committed
848 849 850 851 852 853
	return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
}

STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
                                         UINT indexMenu,
                                         UINT idCmdFirst,
854
                                         UINT idCmdLast,
Frank Li's avatar
Frank Li committed
855
                                         UINT uFlags)
856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871
{
	__try
	{
		return QueryContextMenu_Wrap(hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
	}
	__except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
	{
	}
	return E_FAIL;
}

STDMETHODIMP CShellExt::QueryContextMenu_Wrap(HMENU hMenu,
                                              UINT indexMenu,
                                              UINT idCmdFirst,
                                              UINT /*idCmdLast*/,
                                              UINT uFlags)
Frank Li's avatar
Frank Li committed
872
{
873
	CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: QueryContextMenu itemStates=%ld\n", itemStates);
Frank Li's avatar
Frank Li committed
874
	PreserveChdir preserveChdir;
875

Frank Li's avatar
Frank Li committed
876 877 878 879 880 881 882 883 884
	//first check if our drop handler is called
	//and then (if true) provide the context menu for the
	//drop handler
	if (m_State == FileStateDropHandler)
	{
		return QueryDropContext(uFlags, idCmdFirst, hMenu, indexMenu);
	}

	if ((uFlags & CMF_DEFAULTONLY)!=0)
Sven Strickroth's avatar
Sven Strickroth committed
885
		return S_OK;					//we don't change the default action
Frank Li's avatar
Frank Li committed
886

887
	if (files_.empty() && folder_.empty())
Sven Strickroth's avatar
Sven Strickroth committed
888
		return S_OK;
Frank Li's avatar
Frank Li committed
889 890

	if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
Sven Strickroth's avatar
Sven Strickroth committed
891
		return S_OK;
Frank Li's avatar
Frank Li committed
892

893
	int csidlarray[] =
Frank Li's avatar
Frank Li committed
894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917
	{
		CSIDL_BITBUCKET,
		CSIDL_CDBURN_AREA,
		CSIDL_COMMON_FAVORITES,
		CSIDL_COMMON_STARTMENU,
		CSIDL_COMPUTERSNEARME,
		CSIDL_CONNECTIONS,
		CSIDL_CONTROLS,
		CSIDL_COOKIES,
		CSIDL_FAVORITES,
		CSIDL_FONTS,
		CSIDL_HISTORY,
		CSIDL_INTERNET,
		CSIDL_INTERNET_CACHE,
		CSIDL_NETHOOD,
		CSIDL_NETWORK,
		CSIDL_PRINTERS,
		CSIDL_PRINTHOOD,
		CSIDL_RECENT,
		CSIDL_SENDTO,
		CSIDL_STARTMENU,
		0
	};
	if (IsIllegalFolder(folder_, csidlarray))
Sven Strickroth's avatar
Sven Strickroth committed
918
		return S_OK;
Frank Li's avatar
Frank Li committed
919 920 921 922

	if (folder_.empty())
	{
		// folder is empty, but maybe files are selected
923
		if (files_.empty())
Sven Strickroth's avatar
Sven Strickroth committed
924
			return S_OK;	// nothing selected - we don't have a menu to show
Frank Li's avatar
Frank Li committed
925 926 927 928 929
		// check whether a selected entry is an UID - those are namespace extensions
		// which we can't handle
		for (std::vector<stdstring>::const_iterator it = files_.begin(); it != files_.end(); ++it)
		{
			if (_tcsncmp(it->c_str(), _T("::{"), 3)==0)
Sven Strickroth's avatar
Sven Strickroth committed
930
				return S_OK;
Frank Li's avatar
Frank Li committed
931 932
		}
	}
933 934 935 936 937
	else
	{
		if (_tcsncmp(folder_.c_str(), _T("::{"), 3) == 0)
			return S_OK;
	}
Frank Li's avatar
Frank Li committed
938

939 940
	if (((uFlags & CMF_EXTENDEDVERBS) == 0) && g_ShellCache.HideMenusForUnversionedItems())
	{
941
		if ((itemStates & (ITEMIS_INGIT|ITEMIS_INVERSIONEDFOLDER|ITEMIS_FOLDERINGIT|ITEMIS_BAREREPO|ITEMIS_TWO))==0)
942 943 944
			return S_OK;
	}

ch3cooli's avatar
ch3cooli committed
945
	//check if our menu is requested for a git admin directory
946
	if (GitAdminDir::IsAdminDirPath(folder_.c_str()))
Sven Strickroth's avatar
Sven Strickroth committed
947
		return S_OK;
Frank Li's avatar
Frank Li committed
948 949 950 951

	if (uFlags & CMF_EXTENDEDVERBS)
		itemStates |= ITEMIS_EXTENDED;

ch3cooli's avatar
ch3cooli committed
952 953 954 955
	regDiffLater.read();
	if (!std::wstring(regDiffLater).empty())
		itemStates |= ITEMIS_HASDIFFLATER;

Frank Li's avatar
Frank Li committed
956 957 958 959 960 961 962 963
	const BOOL bShortcut = !!(uFlags & CMF_VERBSONLY);
	if ( bShortcut && (files_.size()==1))
	{
		// Don't show the context menu for a link if the
		// destination is not part of a working copy.
		// It would only show the standard menu items
		// which are already shown for the lnk-file.
		CString path = files_.front().c_str();
964
		if (!GitAdminDir::HasAdminDir(path))
Frank Li's avatar
Frank Li committed
965
		{
Sven Strickroth's avatar
Sven Strickroth committed
966
			return S_OK;
Frank Li's avatar
Frank Li committed
967 968 969 970
		}
	}

	//check if we already added our menu entry for a folder.
971
	//we check that by iterating through all menu entries and check if
Frank Li's avatar
Frank Li committed
972 973
	//the dwItemData member points to our global ID string. That string is set
	//by our shell extension when the folder menu is inserted.
Sven Strickroth's avatar
Sven Strickroth committed
974
	TCHAR menubuf[MAX_PATH] = {0};
Frank Li's avatar
Frank Li committed
975 976 977 978 979 980 981 982
	int count = GetMenuItemCount(hMenu);
	for (int i=0; i<count; ++i)
	{
		MENUITEMINFO miif;
		SecureZeroMemory(&miif, sizeof(MENUITEMINFO));
		miif.cbSize = sizeof(MENUITEMINFO);
		miif.fMask = MIIM_DATA;
		miif.dwTypeData = menubuf;
983
		miif.cch = _countof(menubuf);
Frank Li's avatar
Frank Li committed
984 985
		GetMenuItemInfo(hMenu, i, TRUE, &miif);
		if (miif.dwItemData == (ULONG_PTR)g_MenuIDString)
Sven Strickroth's avatar
Sven Strickroth committed
986
			return S_OK;
Frank Li's avatar
Frank Li committed
987 988 989 990 991 992 993 994 995 996 997
	}

	LoadLangDll();
	UINT idCmd = idCmdFirst;

	//create the sub menu
	HMENU subMenu = CreateMenu();
	int indexSubMenu = 0;

	unsigned __int64 topmenu = g_ShellCache.GetMenuLayout();
	unsigned __int64 menumask = g_ShellCache.GetMenuMask();
李智's avatar
李智 committed
998
	unsigned __int64 menuex = g_ShellCache.GetMenuExt();
Frank Li's avatar
Frank Li committed
999 1000 1001 1002

	int menuIndex = 0;
	bool bAddSeparator = false;
	bool bMenuEntryAdded = false;
1003
	bool bMenuEmpty = true;
Frank Li's avatar
Frank Li committed
1004 1005
	// insert separator at start
	InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); idCmd++;
李智's avatar
李智 committed
1006
	bool bShowIcons = !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\ShowContextMenuIcons"), TRUE));
1007

Frank Li's avatar
Frank Li committed
1008 1009 1010 1011 1012 1013 1014 1015
	while (menuInfo[menuIndex].command != ShellMenuLastEntry)
	{
		if (menuInfo[menuIndex].command == ShellSeparator)
		{
			// we don't add a separator immediately. Because there might not be
			// another 'normal' menu entry after we insert a separator.
			// we simply set a flag here, indicating that before the next
			// 'normal' menu entry, a separator should be added.
1016 1017
			if (!bMenuEmpty)
				bAddSeparator = true;
ch3cooli's avatar
ch3cooli committed
1018 1019
			if (bMenuEntryAdded)
				bAddSeparator = true;
Frank Li's avatar
Frank Li committed
1020 1021 1022 1023
		}
		else
		{
			// check the conditions whether to show the menu entry or not
Sven Strickroth's avatar
Sven Strickroth committed
1024
			bool bInsertMenu = ShouldInsertItem(menuInfo[menuIndex]);