Get rid of magic numbers
[TortoiseGit.git] / src / TortoiseShell / GITPropertyPage.cpp
blobe118888a05237b070bef20e25d1c8dcd01695ce7
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008, 2014 - TortoiseSVN
4 // Copyright (C) 2008-2018 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
22 #include "ShellExt.h"
23 #include "gitpropertypage.h"
24 #include "UnicodeUtils.h"
25 #include "PathUtils.h"
26 #include "UnicodeUtils.h"
27 #include "CreateProcessHelper.h"
28 #include "FormatMessageWrapper.h"
29 #include "StringUtils.h"
30 #include "Git.h"
31 #include "GitAdminDir.h"
33 #define MAX_STRING_LENGTH 4096 //should be big enough
35 // Nonmember function prototypes
36 BOOL CALLBACK PageProc (HWND, UINT, WPARAM, LPARAM);
37 UINT CALLBACK PropPageCallbackProc ( HWND hwnd, UINT uMsg, LPPROPSHEETPAGE ppsp );
39 /////////////////////////////////////////////////////////////////////////////
40 // Dialog procedures and other callback functions
42 BOOL CALLBACK PageProc (HWND hwnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
44 CGitPropertyPage * sheetpage;
46 if (uMessage == WM_INITDIALOG)
48 sheetpage = (CGitPropertyPage*) ((LPPROPSHEETPAGE) lParam)->lParam;
49 SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) sheetpage);
50 sheetpage->SetHwnd(hwnd);
52 else
53 sheetpage = reinterpret_cast<CGitPropertyPage*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
55 if (sheetpage)
56 return sheetpage->PageProc(hwnd, uMessage, wParam, lParam);
57 else
58 return FALSE;
61 UINT CALLBACK PropPageCallbackProc ( HWND /*hwnd*/, UINT uMsg, LPPROPSHEETPAGE ppsp )
63 // Delete the page before closing.
64 if (PSPCB_RELEASE == uMsg)
66 delete reinterpret_cast<CGitPropertyPage*>(ppsp->lParam);
68 return 1;
71 // *********************** CGitPropertyPage *************************
72 const UINT CGitPropertyPage::m_UpdateLastCommit = RegisterWindowMessage(L"TORTOISEGIT_PROP_UPDATELASTCOMMIT");
74 CGitPropertyPage::CGitPropertyPage(const std::vector<std::wstring>& newFilenames, CString projectTopDir, bool bIsSubmodule)
75 : filenames(newFilenames)
76 , m_ProjectTopDir(projectTopDir)
77 ,m_bChanged(false)
78 , m_hwnd(nullptr)
79 , m_bIsSubmodule(bIsSubmodule)
81 m_iStripLength = m_ProjectTopDir.GetLength();
82 if (m_ProjectTopDir[m_iStripLength - 1] != L'\\')
83 ++m_iStripLength;
86 CGitPropertyPage::~CGitPropertyPage(void)
90 void CGitPropertyPage::SetHwnd(HWND newHwnd)
92 m_hwnd = newHwnd;
95 BOOL CGitPropertyPage::PageProc (HWND /*hwnd*/, UINT uMessage, WPARAM wParam, LPARAM lParam)
97 switch (uMessage)
99 case WM_INITDIALOG:
101 InitWorkfileView();
102 return TRUE;
104 case WM_NOTIFY:
106 LPNMHDR point = (LPNMHDR)lParam;
107 int code = point->code;
109 // Respond to notifications.
111 if (code == PSN_APPLY && m_bChanged)
115 CAutoRepository repository(CUnicodeUtils::GetUTF8(m_ProjectTopDir));
116 if (!repository)
117 break;
119 CAutoIndex index;
120 if (git_repository_index(index.GetPointer(), repository))
121 break;
123 BOOL assumeValid = (BOOL)SendMessage(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), BM_GETCHECK, 0, 0);
124 BOOL skipWorktree = (BOOL)SendMessage(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), BM_GETCHECK, 0, 0);
125 BOOL executable = (BOOL)SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_GETCHECK, 0, 0);
126 BOOL symlink = (BOOL)SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_GETCHECK, 0, 0);
127 if (m_fileStats.submodule != 0)
128 executable = symlink = BST_INDETERMINATE; // don't update executable or symlink state if we have at least one submodule
130 bool changed = false;
132 for (const auto& filename : filenames)
134 CTGitPath file;
135 file.SetFromWin(CString(filename.c_str()).Mid(m_iStripLength));
136 CStringA pathA = CUnicodeUtils::GetMulti(file.GetGitPathString(), CP_UTF8);
137 size_t idx;
138 if (!git_index_find(&idx, index, pathA))
140 git_index_entry *e = const_cast<git_index_entry *>(git_index_get_byindex(index, idx)); // HACK
142 if (assumeValid == BST_CHECKED)
144 if (!(e->flags & GIT_IDXENTRY_VALID))
146 e->flags |= GIT_IDXENTRY_VALID;
147 changed = true;
150 else if (assumeValid != BST_INDETERMINATE)
152 if (e->flags & GIT_IDXENTRY_VALID)
154 e->flags &= ~GIT_IDXENTRY_VALID;
155 changed = true;
158 if (skipWorktree == BST_CHECKED)
160 if (!(e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE))
162 e->flags_extended |= GIT_IDXENTRY_SKIP_WORKTREE;
163 changed = true;
166 else if (skipWorktree != BST_INDETERMINATE)
168 if (e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE)
170 e->flags_extended &= ~GIT_IDXENTRY_SKIP_WORKTREE;
171 changed = true;
174 if (executable == BST_CHECKED)
176 if (!(e->mode & 0111))
178 e->mode = GIT_FILEMODE_BLOB_EXECUTABLE;
179 changed = true;
182 else if (executable != BST_INDETERMINATE)
184 if (e->mode & 0111)
186 e->mode = GIT_FILEMODE_BLOB;
187 changed = true;
190 if (symlink == BST_CHECKED)
192 if ((e->mode & GIT_FILEMODE_LINK) != GIT_FILEMODE_LINK)
194 e->mode = GIT_FILEMODE_LINK;
195 changed = true;
198 else if (symlink != BST_INDETERMINATE)
200 if ((e->mode & GIT_FILEMODE_LINK) == GIT_FILEMODE_LINK)
202 e->mode = GIT_FILEMODE_BLOB;
203 changed = true;
206 if (changed)
207 git_index_add(index, e);
211 if (changed)
213 if (!git_index_write(index))
214 m_bChanged = false;
216 } while (0);
218 SetWindowLongPtr (m_hwnd, DWLP_MSGRESULT, FALSE);
219 return TRUE;
221 case WM_DESTROY:
222 return TRUE;
224 case WM_COMMAND:
225 PageProcOnCommand(wParam);
226 break;
227 } // switch (uMessage)
229 if (uMessage == m_UpdateLastCommit)
231 DisplayCommit((git_commit *)lParam, IDC_LAST_HASH, IDC_LAST_SUBJECT, IDC_LAST_AUTHOR, IDC_LAST_DATE);
232 return TRUE;
235 return FALSE;
237 void CGitPropertyPage::PageProcOnCommand(WPARAM wParam)
239 if(HIWORD(wParam) != BN_CLICKED)
240 return;
242 switch (LOWORD(wParam))
244 case IDC_SHOWLOG:
246 tstring gitCmd = L" /command:";
247 gitCmd += L"log /path:\"";
248 gitCmd += filenames.front().c_str();
249 gitCmd += L'"';
250 if (m_bIsSubmodule)
251 gitCmd += L" /submodule";
252 RunCommand(gitCmd);
254 break;
255 case IDC_SHOWSETTINGS:
257 tstring gitCmd = L" /command:";
258 gitCmd += L"settings /path:\"";
259 gitCmd += m_ProjectTopDir;
260 gitCmd += L'"';
261 RunCommand(gitCmd);
263 break;
264 case IDC_ASSUMEVALID:
265 case IDC_SKIPWORKTREE:
266 case IDC_EXECUTABLE:
267 case IDC_SYMLINK:
268 BOOL executable = (BOOL)SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_GETCHECK, 0, 0);
269 BOOL symlink = (BOOL)SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_GETCHECK, 0, 0);
270 if (executable == BST_CHECKED)
272 EnableWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), FALSE);
273 SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_SETCHECK, BST_UNCHECKED, 0);
275 else
276 EnableWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), TRUE);
277 if (symlink == BST_CHECKED)
279 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), FALSE);
280 SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_SETCHECK, BST_UNCHECKED, 0);
282 else
283 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), TRUE);
284 m_bChanged = true;
285 SendMessage(GetParent(m_hwnd), PSM_CHANGED, (WPARAM)m_hwnd, 0);
286 break;
290 void CGitPropertyPage::RunCommand(const tstring& command)
292 tstring tortoiseProcPath = CPathUtils::GetAppDirectory(g_hmodThisDll) + L"TortoiseGitProc.exe";
293 if (CCreateProcessHelper::CreateProcessDetached(tortoiseProcPath.c_str(), command.c_str()))
295 // process started - exit
296 return;
299 MessageBox(nullptr, CFormatMessageWrapper(), L"TortoiseGitProc launch failed", MB_OK | MB_ICONERROR);
302 void CGitPropertyPage::Time64ToTimeString(__time64_t time, TCHAR * buf, size_t buflen) const
304 struct tm newtime;
305 SYSTEMTIME systime;
307 LCID locale = LOCALE_USER_DEFAULT;
308 if (!CRegDWORD(L"Software\\TortoiseGit\\UseSystemLocaleForDates", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY))
309 locale = MAKELCID((WORD)CRegStdDWORD(L"Software\\TortoiseGit\\LanguageID", MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), false, HKEY_CURRENT_USER, KEY_WOW64_64KEY), SORT_DEFAULT);
311 *buf = '\0';
312 if (time)
314 TCHAR timebuf[MAX_STRING_LENGTH] = { 0 };
315 TCHAR datebuf[MAX_STRING_LENGTH] = { 0 };
316 _localtime64_s(&newtime, &time);
318 systime.wDay = (WORD)newtime.tm_mday;
319 systime.wDayOfWeek = (WORD)newtime.tm_wday;
320 systime.wHour = (WORD)newtime.tm_hour;
321 systime.wMilliseconds = 0;
322 systime.wMinute = (WORD)newtime.tm_min;
323 systime.wMonth = (WORD)newtime.tm_mon+1;
324 systime.wSecond = (WORD)newtime.tm_sec;
325 systime.wYear = (WORD)newtime.tm_year+1900;
326 if (CRegStdDWORD(L"Software\\TortoiseGit\\LogDateFormat", 0, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY) == 1)
327 GetDateFormat(locale, DATE_SHORTDATE, &systime, nullptr, datebuf, MAX_STRING_LENGTH);
328 else
329 GetDateFormat(locale, DATE_LONGDATE, &systime, nullptr, datebuf, MAX_STRING_LENGTH);
330 GetTimeFormat(locale, 0, &systime, nullptr, timebuf, MAX_STRING_LENGTH);
331 *buf = '\0';
332 wcsncat_s(buf, buflen, datebuf, MAX_STRING_LENGTH - 1);
333 wcsncat_s(buf, buflen, L" ", MAX_STRING_LENGTH - 1);
334 wcsncat_s(buf, buflen, timebuf, MAX_STRING_LENGTH - 1);
338 struct TreewalkStruct
340 const char *folder;
341 const char *name;
342 git_oid oid;
345 static int TreewalkCB_FindFileRecentCommit(const char *root, const git_tree_entry *entry, void *payload)
347 auto treewalkstruct = reinterpret_cast<TreewalkStruct*>(payload);
348 char folder[MAX_PATH] = {0};
349 strcpy_s(folder, root);
350 strcat_s(folder, git_tree_entry_name(entry));
351 strcat_s(folder, "/");
352 if (strstr(treewalkstruct->folder, folder))
353 return 0;
355 if (!strcmp(treewalkstruct->folder, root))
357 if (!strcmp(git_tree_entry_name(entry), treewalkstruct->name))
359 git_oid_cpy(&treewalkstruct->oid, git_tree_entry_id(entry));
360 return GIT_EUSER;
363 return 1;
366 return 1;
369 static git_commit* FindFileRecentCommit(git_repository* repository, const CString& path)
371 CAutoRevwalk walk;
372 if (git_revwalk_new(walk.GetPointer(), repository))
373 return nullptr;
375 CStringA pathA = CUnicodeUtils::GetUTF8(path);
376 if (pathA.GetLength() >= MAX_PATH)
377 return nullptr;
378 const char *pathC = pathA;
379 char folder[MAX_PATH] = {0}, file[MAX_PATH] = {0};
380 const char *slash = strrchr(pathC, '/');
381 if (slash)
383 strncpy(folder, pathC, slash - pathC + 1);
384 folder[slash - pathC + 1] = '\0';
385 strcpy(file, slash + 1);
387 else
389 folder[0] = '\0';
390 strcpy(file, pathC);
393 TreewalkStruct treewalkstruct = { folder, file };
395 if (git_revwalk_push_head(walk))
396 return nullptr;
398 git_oid oid;
399 CAutoCommit commit;
400 while (!git_revwalk_next(&oid, walk))
402 if (git_commit_lookup(commit.GetPointer(), repository, &oid))
403 return nullptr;
405 CAutoTree tree;
406 if (git_commit_tree(tree.GetPointer(), commit))
407 return nullptr;
409 memset(&treewalkstruct.oid.id, 0, sizeof(treewalkstruct.oid.id));
410 int ret = git_tree_walk(tree, GIT_TREEWALK_PRE, TreewalkCB_FindFileRecentCommit, &treewalkstruct);
412 if (ret < 0 && ret != GIT_EUSER)
413 return nullptr;
415 // check if file not found
416 if (git_oid_iszero(&treewalkstruct.oid))
417 return nullptr;
419 bool diff = true;
420 // for merge point, check if it is different to all parents, if yes then there are real change in the merge point.
421 // if no parent then of course it is different
422 for (unsigned int i = 0; i < git_commit_parentcount(commit); ++i)
424 CAutoCommit commit2;
425 if (git_commit_parent(commit2.GetPointer(), commit, i))
426 return nullptr;
428 CAutoTree tree2;
429 if (git_commit_tree(tree2.GetPointer(), commit2))
430 return nullptr;
432 TreewalkStruct treewalkstruct2 = { folder, file };
433 memset(&treewalkstruct2.oid.id, 0, sizeof(treewalkstruct2.oid.id));
434 ret = git_tree_walk(tree2, GIT_TREEWALK_PRE, TreewalkCB_FindFileRecentCommit, &treewalkstruct2);
436 if (ret < 0 && ret != GIT_EUSER)
437 return nullptr;
439 if (!git_oid_cmp(&treewalkstruct.oid, &treewalkstruct2.oid))
440 diff = false;
441 else if (git_revwalk_hide(walk, git_commit_parent_id(commit, i)))
442 return nullptr;
445 if (diff)
446 break;
449 return commit.Detach();
452 void CGitPropertyPage::DisplayCommit(const git_commit* commit, UINT hashLabel, UINT subjectLabel, UINT authorLabel, UINT dateLabel)
454 if (!commit)
456 SetDlgItemText(m_hwnd, hashLabel, L"");
457 SetDlgItemText(m_hwnd, subjectLabel, L"");
458 SetDlgItemText(m_hwnd, authorLabel, L"");
459 SetDlgItemText(m_hwnd, dateLabel, L"");
460 return;
463 int encode = CP_UTF8;
464 const char * encodingString = git_commit_message_encoding(commit);
465 if (encodingString)
466 encode = CUnicodeUtils::GetCPCode(CUnicodeUtils::GetUnicode(encodingString));
468 const git_signature * author = git_commit_author(commit);
469 CString authorName = CUnicodeUtils::GetUnicode(author->name, encode);
471 CString message = CUnicodeUtils::GetUnicode(git_commit_message(commit), encode);
473 int start = 0;
474 message = message.Tokenize(L"\n", start);
476 SetDlgItemText(m_hwnd, hashLabel, CGitHash(git_commit_id(commit)->id).ToString());
477 SetDlgItemText(m_hwnd, subjectLabel, message);
478 SetDlgItemText(m_hwnd, authorLabel, authorName);
480 CString authorDate;
481 Time64ToTimeString(author->when.time, authorDate.GetBufferSetLength(200), 200);
482 SetDlgItemText(m_hwnd, dateLabel, authorDate);
485 int CGitPropertyPage::LogThread()
487 CTGitPath path(filenames.front().c_str());
489 CAutoRepository repository(CUnicodeUtils::GetUTF8(m_ProjectTopDir));
490 if (!repository)
491 return 0;
493 CTGitPath relatepath;
494 relatepath.SetFromWin(path.GetWinPathString().Mid(m_iStripLength));
496 CAutoCommit commit(FindFileRecentCommit(repository, relatepath.GetGitPathString()));
497 if (commit)
499 SendMessage(m_hwnd, m_UpdateLastCommit, NULL, reinterpret_cast<LPARAM>(static_cast<git_commit*>(commit)));
501 else
503 SendMessage(m_hwnd, m_UpdateLastCommit, NULL, NULL);
506 return 0;
509 void CGitPropertyPage::LogThreadEntry(void *param)
511 reinterpret_cast<CGitPropertyPage*>(param)->LogThread();
514 void CGitPropertyPage::InitWorkfileView()
516 if (filenames.empty())
517 return;
519 CTGitPath path(filenames.front().c_str());
521 CAutoRepository repository(CUnicodeUtils::GetUTF8(m_ProjectTopDir));
522 if (!repository)
523 return;
525 CString username;
526 CString useremail;
527 CString autocrlf;
528 CString safecrlf;
530 CAutoConfig config(repository);
531 if (config)
533 config.GetString(L"user.name", username);
534 config.GetString(L"user.email", useremail);
535 config.GetString(L"core.autocrlf", autocrlf);
536 config.GetString(L"core.safecrlf", safecrlf);
539 CString branch;
540 CString remotebranch;
541 CString remoteUrl;
542 if (!git_repository_head_detached(repository))
544 CAutoReference head;
545 if (git_repository_head_unborn(repository))
547 git_reference_lookup(head.GetPointer(), repository, "HEAD");
548 branch = CUnicodeUtils::GetUnicode(git_reference_symbolic_target(head));
549 if (CStringUtils::StartsWith(branch, L"refs/heads/"))
550 branch = branch.Mid((int)wcslen(L"refs/heads/"));
552 else if (!git_repository_head(head.GetPointer(), repository))
554 const char * branchChar = git_reference_shorthand(head);
555 branch = CUnicodeUtils::GetUnicode(branchChar);
557 const char * branchFullChar = git_reference_name(head);
558 CAutoBuf upstreambranchname;
559 if (!git_branch_upstream_name(upstreambranchname, repository, branchFullChar))
561 remotebranch = CUnicodeUtils::GetUnicode(CStringA(upstreambranchname->ptr, (int)upstreambranchname->size));
562 remotebranch = remotebranch.Mid((int)wcslen(L"refs/remotes/"));
563 int pos = remotebranch.Find(L'/');
564 if (pos > 0)
566 CString remoteName;
567 remoteName.Format(L"remote.%s.url", (LPCTSTR)remotebranch.Left(pos));
568 config.GetString(remoteName, remoteUrl);
573 else
574 branch = L"detached HEAD";
576 if (autocrlf.Trim().IsEmpty())
577 autocrlf = L"false";
578 if (safecrlf.Trim().IsEmpty())
579 safecrlf = L"false";
581 SetDlgItemText(m_hwnd,IDC_CONFIG_USERNAME,username.Trim());
582 SetDlgItemText(m_hwnd,IDC_CONFIG_USEREMAIL,useremail.Trim());
583 SetDlgItemText(m_hwnd,IDC_CONFIG_AUTOCRLF,autocrlf.Trim());
584 SetDlgItemText(m_hwnd,IDC_CONFIG_SAFECRLF,safecrlf.Trim());
586 SetDlgItemText(m_hwnd,IDC_SHELL_CURRENT_BRANCH,branch.Trim());
587 SetDlgItemText(m_hwnd,IDC_SHELL_REMOTE_BRANCH, remotebranch);
588 SetDlgItemText(m_hwnd, IDC_SHELL_REMOTE_URL, remoteUrl);
590 git_oid oid;
591 CAutoCommit HEADcommit;
592 if (!git_reference_name_to_id(&oid, repository, "HEAD") && !git_commit_lookup(HEADcommit.GetPointer(), repository, &oid) && HEADcommit)
593 DisplayCommit(HEADcommit, IDC_HEAD_HASH, IDC_HEAD_SUBJECT, IDC_HEAD_AUTHOR, IDC_HEAD_DATE);
595 m_fileStats.allAreVersionedItems = false;
598 CAutoIndex index;
599 if (git_repository_index(index.GetPointer(), repository))
600 break;
602 m_fileStats.allAreVersionedItems = true;
603 for (const auto& filename : filenames)
605 CTGitPath file;
606 file.SetFromWin(CString(filename.c_str()).Mid(m_iStripLength));
607 CStringA pathA = CUnicodeUtils::GetMulti(file.GetGitPathString(), CP_UTF8);
608 size_t idx;
609 if (!git_index_find(&idx, index, pathA))
611 const git_index_entry *e = git_index_get_byindex(index, idx);
613 if (e->flags & GIT_IDXENTRY_VALID)
614 ++m_fileStats.assumevalid;
616 if (e->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE)
617 ++m_fileStats.skipworktree;
619 if (e->mode & 0111)
620 ++m_fileStats.executable;
622 if ((e->mode & GIT_FILEMODE_COMMIT) == GIT_FILEMODE_COMMIT)
623 ++m_fileStats.submodule;
625 if ((e->mode & GIT_FILEMODE_LINK) == GIT_FILEMODE_LINK)
626 ++m_fileStats.symlink;
628 else
630 m_fileStats.allAreVersionedItems = false;
631 break;
634 } while (0);
636 if (m_fileStats.allAreVersionedItems)
638 if (m_fileStats.assumevalid != 0 && m_fileStats.assumevalid != filenames.size())
640 SendMessage(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), BM_SETSTYLE, (DWORD)GetWindowLong(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), GWL_STYLE) & ~BS_AUTOCHECKBOX | BS_AUTO3STATE, 0);
641 SendMessage(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), BM_SETCHECK, BST_INDETERMINATE, 0);
643 else
644 SendMessage(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), BM_SETCHECK, (m_fileStats.assumevalid == 0) ? BST_UNCHECKED : BST_CHECKED, 0);
646 if (m_fileStats.skipworktree != 0 && m_fileStats.skipworktree != filenames.size())
648 SendMessage(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), BM_SETSTYLE, (DWORD)GetWindowLong(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), GWL_STYLE) & ~BS_AUTOCHECKBOX | BS_AUTO3STATE, 0);
649 SendMessage(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), BM_SETCHECK, BST_INDETERMINATE, 0);
651 else
652 SendMessage(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), BM_SETCHECK, (m_fileStats.skipworktree == 0) ? BST_UNCHECKED : BST_CHECKED, 0);
654 if (m_fileStats.executable != 0 && m_fileStats.executable != filenames.size())
656 SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_SETSTYLE, (DWORD)GetWindowLong(GetDlgItem(m_hwnd, IDC_EXECUTABLE), GWL_STYLE) & ~BS_AUTOCHECKBOX | BS_AUTO3STATE, 0);
657 SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_SETCHECK, BST_INDETERMINATE, 0);
658 EnableWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), TRUE);
660 else
662 SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_SETCHECK, (m_fileStats.executable == 0) ? BST_UNCHECKED : BST_CHECKED, 0);
663 EnableWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), (m_fileStats.executable == 0) ? TRUE : FALSE);
666 if (m_fileStats.symlink != 0 && m_fileStats.symlink != filenames.size())
668 SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_SETSTYLE, (DWORD)GetWindowLong(GetDlgItem(m_hwnd, IDC_SYMLINK), GWL_STYLE) & ~BS_AUTOCHECKBOX | BS_AUTO3STATE, 0);
669 SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_SETCHECK, BST_INDETERMINATE, 0);
670 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), TRUE);
672 else
674 SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_SETCHECK, (m_fileStats.symlink == 0) ? BST_UNCHECKED : BST_CHECKED, 0);
675 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), (m_fileStats.symlink == 0) ? TRUE : FALSE);
678 // check this last, so that we hide executable and symlink checkboxes in any case if we have at least one submodule
679 if (m_fileStats.submodule != 0)
681 ShowWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), SW_HIDE);
682 ShowWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), SW_HIDE);
685 else
687 // do not show checkboxes for unversioned files
688 ShowWindow(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), SW_HIDE);
689 ShowWindow(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), SW_HIDE);
690 ShowWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), SW_HIDE);
691 ShowWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), SW_HIDE);
694 if (filenames.size() == 1 && m_fileStats.allAreVersionedItems)
696 SetDlgItemText(m_hwnd, IDC_LAST_SUBJECT, CString(MAKEINTRESOURCE(IDS_LOADING)));
697 _beginthread(LogThreadEntry, 0, this);
699 else
700 ShowWindow(GetDlgItem(m_hwnd, IDC_STATIC_LASTMODIFIED), SW_HIDE);
704 // CShellExt member functions (needed for IShellPropSheetExt)
705 STDMETHODIMP CShellExt::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
707 if (files_.empty())
708 return S_OK;
710 CTGitPath firstFile(files_[0].c_str());
712 CString projectTopDir;
713 if (!firstFile.HasAdminDir(&projectTopDir))
714 return S_OK;
716 if (files_.size() == 1 && firstFile.IsWCRoot()) // might be a submodule
718 CString parentRepo;
719 if (firstFile.IsRegisteredSubmoduleOfParentProject(&parentRepo))
721 LoadLangDll();
722 PROPSHEETPAGE psp = { 0 };
723 HPROPSHEETPAGE hPage;
724 CGitPropertyPage* sheetpage = new (std::nothrow) CGitPropertyPage(files_, parentRepo, true);
726 if (!sheetpage)
727 return E_OUTOFMEMORY;
729 psp.dwSize = sizeof(psp);
730 psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE | PSP_USEICONID | PSP_USECALLBACK;
731 psp.hInstance = g_hResInst;
732 psp.pszTemplate = MAKEINTRESOURCE(IDD_PROPPAGE);
733 psp.pszIcon = MAKEINTRESOURCE(IDI_APPSMALL);
734 psp.pszTitle = L"Git Submodule";
735 psp.pfnDlgProc = (DLGPROC)PageProc;
736 psp.lParam = (LPARAM)sheetpage;
737 psp.pfnCallback = PropPageCallbackProc;
738 psp.pcRefParent = (UINT*)&g_cRefThisDll;
740 hPage = CreatePropertySheetPage(&psp);
742 if (hPage && !lpfnAddPage(hPage, lParam))
744 delete sheetpage;
745 DestroyPropertySheetPage(hPage);
750 for (const auto& file_ : files_)
752 CString currentProjectTopDir;
753 if (!CTGitPath(file_.c_str()).HasAdminDir(&currentProjectTopDir) || !CPathUtils::ArePathStringsEqual(projectTopDir, currentProjectTopDir))
754 return S_OK;
757 LoadLangDll();
758 PROPSHEETPAGE psp = { 0 };
759 HPROPSHEETPAGE hPage;
760 CGitPropertyPage* sheetpage = new (std::nothrow) CGitPropertyPage(files_, projectTopDir, false);
762 if (!sheetpage)
763 return E_OUTOFMEMORY;
765 psp.dwSize = sizeof (psp);
766 psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE | PSP_USEICONID | PSP_USECALLBACK;
767 psp.hInstance = g_hResInst;
768 psp.pszTemplate = MAKEINTRESOURCE(IDD_PROPPAGE);
769 psp.pszIcon = MAKEINTRESOURCE(IDI_APPSMALL);
770 psp.pszTitle = L"Git";
771 psp.pfnDlgProc = (DLGPROC) PageProc;
772 psp.lParam = (LPARAM) sheetpage;
773 psp.pfnCallback = PropPageCallbackProc;
774 psp.pcRefParent = (UINT*)&g_cRefThisDll;
776 hPage = CreatePropertySheetPage (&psp);
778 if (hPage)
780 if (!lpfnAddPage (hPage, lParam))
782 delete sheetpage;
783 DestroyPropertySheetPage (hPage);
787 return S_OK;
790 STDMETHODIMP CShellExt::ReplacePage (UINT /*uPageID*/, LPFNADDPROPSHEETPAGE /*lpfnReplaceWith*/, LPARAM /*lParam*/)
792 return E_FAIL;