Fixed issue #4126: Capitalize the first letter in the Push dialog
[TortoiseGit.git] / src / TortoiseShell / GITPropertyPage.cpp
blob238d51a2ab393878240eeeb8a015d25e8dadc874
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008, 2014 - TortoiseSVN
4 // Copyright (C) 2008-2023 - 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 = reinterpret_cast<CGitPropertyPage*>(reinterpret_cast<LPPROPSHEETPAGE>(lParam)->lParam);
49 SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<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_bIsSubmodule(bIsSubmodule)
79 m_iStripLength = m_ProjectTopDir.GetLength();
80 if (m_ProjectTopDir[m_iStripLength - 1] != L'\\')
81 ++m_iStripLength;
84 CGitPropertyPage::~CGitPropertyPage()
88 void CGitPropertyPage::SetHwnd(HWND newHwnd)
90 m_hwnd = newHwnd;
93 BOOL CGitPropertyPage::PageProc (HWND /*hwnd*/, UINT uMessage, WPARAM wParam, LPARAM lParam)
95 switch (uMessage)
97 case WM_INITDIALOG:
99 InitWorkfileView();
100 return TRUE;
102 case WM_NOTIFY:
104 auto point = reinterpret_cast<LPNMHDR>(lParam);
105 int code = point->code;
107 // Respond to notifications.
109 if (code == PSN_APPLY && m_bChanged)
113 CAutoRepository repository(CUnicodeUtils::GetUTF8(m_ProjectTopDir));
114 if (!repository)
115 break;
117 CAutoIndex index;
118 if (git_repository_index(index.GetPointer(), repository))
119 break;
121 auto assumeValid = static_cast<BOOL>(SendMessage(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), BM_GETCHECK, 0, 0));
122 auto skipWorktree = static_cast<BOOL>(SendMessage(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), BM_GETCHECK, 0, 0));
123 auto executable = static_cast<BOOL>(SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_GETCHECK, 0, 0));
124 auto symlink = static_cast<BOOL>(SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_GETCHECK, 0, 0));
125 if (m_fileStats.submodule != 0)
126 executable = symlink = BST_INDETERMINATE; // don't update executable or symlink state if we have at least one submodule
128 bool changed = false;
130 for (const auto& filename : filenames)
132 CTGitPath file;
133 file.SetFromWin(CString(filename.c_str()).Mid(m_iStripLength));
134 CStringA pathA = CUnicodeUtils::GetUTF8(file.GetGitPathString());
135 size_t idx;
136 if (!git_index_find(&idx, index, pathA))
138 git_index_entry *e = const_cast<git_index_entry *>(git_index_get_byindex(index, idx)); // HACK
140 if (assumeValid == BST_CHECKED)
142 if (!(e->flags & GIT_INDEX_ENTRY_VALID))
144 e->flags |= GIT_INDEX_ENTRY_VALID;
145 changed = true;
148 else if (assumeValid != BST_INDETERMINATE)
150 if (e->flags & GIT_INDEX_ENTRY_VALID)
152 e->flags &= ~GIT_INDEX_ENTRY_VALID;
153 changed = true;
156 if (skipWorktree == BST_CHECKED)
158 if (!(e->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE))
160 e->flags_extended |= GIT_INDEX_ENTRY_SKIP_WORKTREE;
161 changed = true;
164 else if (skipWorktree != BST_INDETERMINATE)
166 if (e->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE)
168 e->flags_extended &= ~GIT_INDEX_ENTRY_SKIP_WORKTREE;
169 changed = true;
172 if (executable == BST_CHECKED)
174 if (!(e->mode & 0111))
176 e->mode = GIT_FILEMODE_BLOB_EXECUTABLE;
177 changed = true;
180 else if (executable != BST_INDETERMINATE)
182 if (e->mode & 0111)
184 e->mode = GIT_FILEMODE_BLOB;
185 changed = true;
188 if (symlink == BST_CHECKED)
190 if ((e->mode & GIT_FILEMODE_LINK) != GIT_FILEMODE_LINK)
192 e->mode = GIT_FILEMODE_LINK;
193 changed = true;
196 else if (symlink != BST_INDETERMINATE)
198 if ((e->mode & GIT_FILEMODE_LINK) == GIT_FILEMODE_LINK)
200 e->mode = GIT_FILEMODE_BLOB;
201 changed = true;
204 if (changed)
205 git_index_add(index, e);
209 if (changed)
211 if (!git_index_write(index))
212 m_bChanged = false;
214 } while (0);
216 SetWindowLongPtr (m_hwnd, DWLP_MSGRESULT, FALSE);
217 return TRUE;
219 case WM_DESTROY:
220 return TRUE;
222 case WM_COMMAND:
223 PageProcOnCommand(wParam);
224 break;
225 } // switch (uMessage)
227 if (uMessage == m_UpdateLastCommit)
229 DisplayCommit(reinterpret_cast<git_commit*>(lParam), IDC_LAST_HASH, IDC_LAST_SUBJECT, IDC_LAST_AUTHOR, IDC_LAST_DATE);
230 return TRUE;
233 return FALSE;
235 void CGitPropertyPage::PageProcOnCommand(WPARAM wParam)
237 if(HIWORD(wParam) != BN_CLICKED)
238 return;
240 switch (LOWORD(wParam))
242 case IDC_SHOWLOG:
244 std::wstring gitCmd = L" /command:";
245 gitCmd += L"log /path:\"";
246 gitCmd += filenames.front().c_str();
247 gitCmd += L'"';
248 if (m_bIsSubmodule)
249 gitCmd += L" /submodule";
250 RunCommand(gitCmd);
252 break;
253 case IDC_SHOWSETTINGS:
255 std::wstring gitCmd = L" /command:";
256 gitCmd += L"settings /path:\"";
257 gitCmd += m_ProjectTopDir;
258 gitCmd += L'"';
259 RunCommand(gitCmd);
261 break;
262 case IDC_ASSUMEVALID:
263 case IDC_SKIPWORKTREE:
264 case IDC_EXECUTABLE:
265 case IDC_SYMLINK:
266 auto executable = static_cast<BOOL>(SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_GETCHECK, 0, 0));
267 auto symlink = static_cast<BOOL>(SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_GETCHECK, 0, 0));
268 if (executable == BST_CHECKED)
270 EnableWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), FALSE);
271 SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_SETCHECK, BST_UNCHECKED, 0);
273 else
274 EnableWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), TRUE);
275 if (symlink == BST_CHECKED)
277 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), FALSE);
278 SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_SETCHECK, BST_UNCHECKED, 0);
280 else
281 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), TRUE);
282 m_bChanged = true;
283 SendMessage(GetParent(m_hwnd), PSM_CHANGED, reinterpret_cast<WPARAM>(m_hwnd), 0);
284 break;
288 void CGitPropertyPage::RunCommand(const std::wstring& command)
290 std::wstring tortoiseProcPath { static_cast<LPCWSTR>(CPathUtils::GetAppDirectory(g_hmodThisDll) + L"TortoiseGitProc.exe") };
291 if (CCreateProcessHelper::CreateProcessDetached(tortoiseProcPath.c_str(), command.c_str()))
293 // process started - exit
294 return;
297 MessageBox(nullptr, CFormatMessageWrapper(), L"TortoiseGitProc launch failed", MB_OK | MB_ICONERROR);
300 void CGitPropertyPage::Time64ToTimeString(__time64_t time, wchar_t* buf, size_t buflen) const
302 struct tm newtime;
303 SYSTEMTIME systime;
305 LCID locale = LOCALE_USER_DEFAULT;
306 if (!CRegDWORD(L"Software\\TortoiseGit\\UseSystemLocaleForDates", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY))
307 locale = MAKELCID(static_cast<WORD>(CRegStdDWORD(L"Software\\TortoiseGit\\LanguageID", MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), false, HKEY_CURRENT_USER, KEY_WOW64_64KEY)), SORT_DEFAULT);
309 *buf = '\0';
310 if (time)
312 wchar_t timebuf[MAX_STRING_LENGTH] = { 0 };
313 wchar_t datebuf[MAX_STRING_LENGTH] = { 0 };
314 _localtime64_s(&newtime, &time);
316 systime.wDay = static_cast<WORD>(newtime.tm_mday);
317 systime.wDayOfWeek = static_cast<WORD>(newtime.tm_wday);
318 systime.wHour = static_cast<WORD>(newtime.tm_hour);
319 systime.wMilliseconds = 0;
320 systime.wMinute = static_cast<WORD>(newtime.tm_min);
321 systime.wMonth = static_cast<WORD>(newtime.tm_mon) + 1;
322 systime.wSecond = static_cast<WORD>(newtime.tm_sec);
323 systime.wYear = static_cast<WORD>(newtime.tm_year) + 1900;
324 if (CRegStdDWORD(L"Software\\TortoiseGit\\LogDateFormat", 0, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY) == 1)
325 GetDateFormat(locale, DATE_SHORTDATE, &systime, nullptr, datebuf, MAX_STRING_LENGTH);
326 else
327 GetDateFormat(locale, DATE_LONGDATE, &systime, nullptr, datebuf, MAX_STRING_LENGTH);
328 GetTimeFormat(locale, 0, &systime, nullptr, timebuf, MAX_STRING_LENGTH);
329 *buf = '\0';
330 wcsncat_s(buf, buflen, datebuf, MAX_STRING_LENGTH - 1);
331 wcsncat_s(buf, buflen, L" ", MAX_STRING_LENGTH - 1);
332 wcsncat_s(buf, buflen, timebuf, MAX_STRING_LENGTH - 1);
336 struct TreewalkStruct
338 const char *folder;
339 const char *name;
340 git_oid oid;
343 static int TreewalkCB_FindFileRecentCommit(const char *root, const git_tree_entry *entry, void *payload)
345 auto treewalkstruct = reinterpret_cast<TreewalkStruct*>(payload);
346 char folder[MAX_PATH] = {0};
347 strcpy_s(folder, root);
348 strcat_s(folder, git_tree_entry_name(entry));
349 strcat_s(folder, "/");
350 if (strstr(treewalkstruct->folder, folder))
351 return 0;
353 if (!strcmp(treewalkstruct->folder, root))
355 if (!strcmp(git_tree_entry_name(entry), treewalkstruct->name))
357 git_oid_cpy(&treewalkstruct->oid, git_tree_entry_id(entry));
358 return GIT_EUSER;
361 return 1;
364 return 1;
367 static git_commit* FindFileRecentCommit(git_repository* repository, const CString& path)
369 CAutoRevwalk walk;
370 if (git_revwalk_new(walk.GetPointer(), repository))
371 return nullptr;
373 CStringA pathA = CUnicodeUtils::GetUTF8(path);
374 if (pathA.GetLength() >= MAX_PATH)
375 return nullptr;
376 const char *pathC = pathA;
377 char folder[MAX_PATH] = {0}, file[MAX_PATH] = {0};
378 const char *slash = strrchr(pathC, '/');
379 if (slash)
381 strncpy(folder, pathC, slash - pathC + 1);
382 folder[slash - pathC + 1] = '\0';
383 strcpy(file, slash + 1);
385 else
387 folder[0] = '\0';
388 strcpy(file, pathC);
391 TreewalkStruct treewalkstruct = { folder, file };
393 if (git_revwalk_push_head(walk))
394 return nullptr;
396 git_oid oid;
397 CAutoCommit commit;
398 while (!git_revwalk_next(&oid, walk))
400 if (git_commit_lookup(commit.GetPointer(), repository, &oid))
401 return nullptr;
403 CAutoTree tree;
404 if (git_commit_tree(tree.GetPointer(), commit))
405 return nullptr;
407 memset(&treewalkstruct.oid.id, 0, sizeof(treewalkstruct.oid.id));
408 if (auto ret = git_tree_walk(tree, GIT_TREEWALK_PRE, TreewalkCB_FindFileRecentCommit, &treewalkstruct); ret < 0 && ret != GIT_EUSER)
409 return nullptr;
411 // check if file not found
412 if (git_oid_is_zero(&treewalkstruct.oid))
413 return nullptr;
415 bool diff = true;
416 // for merge point, check if it is different to all parents, if yes then there are real change in the merge point.
417 // if no parent then of course it is different
418 for (unsigned int i = 0; i < git_commit_parentcount(commit); ++i)
420 CAutoCommit commit2;
421 if (git_commit_parent(commit2.GetPointer(), commit, i))
422 return nullptr;
424 CAutoTree tree2;
425 if (git_commit_tree(tree2.GetPointer(), commit2))
426 return nullptr;
428 TreewalkStruct treewalkstruct2 = { folder, file };
429 memset(&treewalkstruct2.oid.id, 0, sizeof(treewalkstruct2.oid.id));
430 if (auto ret = git_tree_walk(tree2, GIT_TREEWALK_PRE, TreewalkCB_FindFileRecentCommit, &treewalkstruct2); ret < 0 && ret != GIT_EUSER)
431 return nullptr;
433 if (!git_oid_cmp(&treewalkstruct.oid, &treewalkstruct2.oid))
434 diff = false;
435 else if (git_revwalk_hide(walk, git_commit_parent_id(commit, i)))
436 return nullptr;
439 if (diff)
440 break;
443 return commit.Detach();
446 void CGitPropertyPage::DisplayCommit(const git_commit* commit, UINT hashLabel, UINT subjectLabel, UINT authorLabel, UINT dateLabel)
448 if (!commit)
450 SetDlgItemText(m_hwnd, hashLabel, L"");
451 SetDlgItemText(m_hwnd, subjectLabel, L"");
452 SetDlgItemText(m_hwnd, authorLabel, L"");
453 SetDlgItemText(m_hwnd, dateLabel, L"");
454 return;
457 int encode = CP_UTF8;
458 const char * encodingString = git_commit_message_encoding(commit);
459 if (encodingString)
460 encode = CUnicodeUtils::GetCPCode(CUnicodeUtils::GetUnicode(encodingString));
462 const git_signature * author = git_commit_author(commit);
463 CString authorName = CUnicodeUtils::GetUnicode(author->name, encode);
465 CString message = CUnicodeUtils::GetUnicode(git_commit_message(commit), encode);
467 int start = 0;
468 message = message.Tokenize(L"\n", start);
470 SetDlgItemText(m_hwnd, hashLabel, CGitHash(git_commit_id(commit)).ToString());
471 SetDlgItemText(m_hwnd, subjectLabel, message);
472 SetDlgItemText(m_hwnd, authorLabel, authorName);
474 CString authorDate;
475 Time64ToTimeString(author->when.time, authorDate.GetBufferSetLength(200), 200);
476 SetDlgItemText(m_hwnd, dateLabel, authorDate);
479 int CGitPropertyPage::LogThread()
481 CTGitPath path(filenames.front().c_str());
483 CAutoRepository repository(CUnicodeUtils::GetUTF8(m_ProjectTopDir));
484 if (!repository)
485 return 0;
487 CTGitPath relatepath;
488 relatepath.SetFromWin(path.GetWinPathString().Mid(m_iStripLength));
490 CAutoCommit commit(FindFileRecentCommit(repository, relatepath.GetGitPathString()));
491 if (commit)
493 SendMessage(m_hwnd, m_UpdateLastCommit, NULL, reinterpret_cast<LPARAM>(static_cast<git_commit*>(commit)));
495 else
497 SendMessage(m_hwnd, m_UpdateLastCommit, NULL, NULL);
500 return 0;
503 void CGitPropertyPage::LogThreadEntry(void *param)
505 reinterpret_cast<CGitPropertyPage*>(param)->LogThread();
508 void CGitPropertyPage::InitWorkfileView()
510 if (filenames.empty())
511 return;
513 CTGitPath path(filenames.front().c_str());
515 CAutoRepository repository(CUnicodeUtils::GetUTF8(m_ProjectTopDir));
516 if (!repository)
518 SetDlgItemText(m_hwnd, IDC_SHELL_CURRENT_BRANCH, CGit::GetLibGit2LastErr());
519 return;
522 CString username;
523 CString useremail;
524 CString autocrlf;
525 CString safecrlf;
527 CAutoConfig config(repository);
528 if (config)
530 config.GetString(L"user.name", username);
531 config.GetString(L"user.email", useremail);
532 config.GetString(L"core.autocrlf", autocrlf);
533 config.GetString(L"core.safecrlf", safecrlf);
536 CString branch;
537 CString remotebranch;
538 CString remoteUrl;
539 int err = 0;
540 if (err = git_repository_head_detached(repository); err == 1)
541 branch = L"detached HEAD";
542 else if (err == 0)
544 CAutoReference head;
545 if ((err = git_repository_head_unborn(repository)) == 1)
547 err = 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(static_cast<int>(wcslen(L"refs/heads/")));
552 else if (err == 0 && (err = git_repository_head(head.GetPointer(), repository)) == 0)
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 (int ret = git_branch_upstream_name(upstreambranchname, repository, branchFullChar); ret == 0)
561 remotebranch = CUnicodeUtils::GetUnicodeLengthSizeT(upstreambranchname->ptr, upstreambranchname->size);
562 remotebranch = remotebranch.Mid(static_cast<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", static_cast<LPCWSTR>(remotebranch.Left(pos)));
568 config.GetString(remoteName, remoteUrl);
571 else if (ret == 1)
572 remotebranch = CGit::GetLibGit2LastErr();
575 if (err < 0)
576 branch = CGit::GetLibGit2LastErr();
578 if (autocrlf.Trim().IsEmpty())
579 autocrlf = L"false";
580 if (safecrlf.Trim().IsEmpty())
581 safecrlf = L"false";
583 SetDlgItemText(m_hwnd,IDC_CONFIG_USERNAME,username.Trim());
584 SetDlgItemText(m_hwnd,IDC_CONFIG_USEREMAIL,useremail.Trim());
585 SetDlgItemText(m_hwnd,IDC_CONFIG_AUTOCRLF,autocrlf.Trim());
586 SetDlgItemText(m_hwnd,IDC_CONFIG_SAFECRLF,safecrlf.Trim());
588 SetDlgItemText(m_hwnd,IDC_SHELL_CURRENT_BRANCH,branch.Trim());
589 SetDlgItemText(m_hwnd,IDC_SHELL_REMOTE_BRANCH, remotebranch);
590 SetDlgItemText(m_hwnd, IDC_SHELL_REMOTE_URL, remoteUrl);
592 git_oid oid;
593 CAutoCommit HEADcommit;
594 if (!git_reference_name_to_id(&oid, repository, "HEAD") && !git_commit_lookup(HEADcommit.GetPointer(), repository, &oid) && HEADcommit)
595 DisplayCommit(HEADcommit, IDC_HEAD_HASH, IDC_HEAD_SUBJECT, IDC_HEAD_AUTHOR, IDC_HEAD_DATE);
597 m_fileStats.allAreVersionedItems = false;
600 CAutoIndex index;
601 if (git_repository_index(index.GetPointer(), repository))
602 break;
604 m_fileStats.allAreVersionedItems = true;
605 for (const auto& filename : filenames)
607 CTGitPath file;
608 file.SetFromWin(CString(filename.c_str()).Mid(m_iStripLength));
609 CStringA pathA = CUnicodeUtils::GetUTF8(file.GetGitPathString());
610 size_t idx;
611 if (!git_index_find(&idx, index, pathA))
613 const git_index_entry *e = git_index_get_byindex(index, idx);
615 if (e->flags & GIT_INDEX_ENTRY_VALID)
616 ++m_fileStats.assumevalid;
618 if (e->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE)
619 ++m_fileStats.skipworktree;
621 if (e->mode & 0111)
622 ++m_fileStats.executable;
624 if ((e->mode & GIT_FILEMODE_COMMIT) == GIT_FILEMODE_COMMIT)
625 ++m_fileStats.submodule;
627 if ((e->mode & GIT_FILEMODE_LINK) == GIT_FILEMODE_LINK)
628 ++m_fileStats.symlink;
630 else
632 m_fileStats.allAreVersionedItems = false;
633 break;
636 } while (0);
638 if (m_fileStats.allAreVersionedItems)
640 if (m_fileStats.assumevalid != 0 && m_fileStats.assumevalid != filenames.size())
642 SendMessage(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), BM_SETSTYLE, static_cast<DWORD>(GetWindowLong(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), GWL_STYLE)) & ~BS_AUTOCHECKBOX | BS_AUTO3STATE, 0);
643 SendMessage(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), BM_SETCHECK, BST_INDETERMINATE, 0);
645 else
646 SendMessage(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), BM_SETCHECK, (m_fileStats.assumevalid == 0) ? BST_UNCHECKED : BST_CHECKED, 0);
648 if (m_fileStats.skipworktree != 0 && m_fileStats.skipworktree != filenames.size())
650 SendMessage(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), BM_SETSTYLE, static_cast<DWORD>(GetWindowLong(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), GWL_STYLE)) & ~BS_AUTOCHECKBOX | BS_AUTO3STATE, 0);
651 SendMessage(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), BM_SETCHECK, BST_INDETERMINATE, 0);
653 else
654 SendMessage(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), BM_SETCHECK, (m_fileStats.skipworktree == 0) ? BST_UNCHECKED : BST_CHECKED, 0);
656 if (m_fileStats.executable != 0 && m_fileStats.executable != filenames.size())
658 SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_SETSTYLE, static_cast<DWORD>(GetWindowLong(GetDlgItem(m_hwnd, IDC_EXECUTABLE), GWL_STYLE)) & ~BS_AUTOCHECKBOX | BS_AUTO3STATE, 0);
659 SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_SETCHECK, BST_INDETERMINATE, 0);
660 EnableWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), TRUE);
662 else
664 SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_SETCHECK, (m_fileStats.executable == 0) ? BST_UNCHECKED : BST_CHECKED, 0);
665 EnableWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), (m_fileStats.executable == 0) ? TRUE : FALSE);
668 if (m_fileStats.symlink != 0 && m_fileStats.symlink != filenames.size())
670 SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_SETSTYLE, static_cast<DWORD>(GetWindowLong(GetDlgItem(m_hwnd, IDC_SYMLINK), GWL_STYLE)) & ~BS_AUTOCHECKBOX | BS_AUTO3STATE, 0);
671 SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_SETCHECK, BST_INDETERMINATE, 0);
672 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), TRUE);
674 else
676 SendMessage(GetDlgItem(m_hwnd, IDC_SYMLINK), BM_SETCHECK, (m_fileStats.symlink == 0) ? BST_UNCHECKED : BST_CHECKED, 0);
677 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), (m_fileStats.symlink == 0) ? TRUE : FALSE);
680 // check this last, so that we hide executable and symlink checkboxes in any case if we have at least one submodule
681 if (m_fileStats.submodule != 0)
683 ShowWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), SW_HIDE);
684 ShowWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), SW_HIDE);
685 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), FALSE);
686 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), FALSE);
689 else
691 // do not show checkboxes for unversioned files
692 ShowWindow(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), SW_HIDE);
693 ShowWindow(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), SW_HIDE);
694 ShowWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), SW_HIDE);
695 ShowWindow(GetDlgItem(m_hwnd, IDC_SYMLINK), SW_HIDE);
696 EnableWindow(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), FALSE);
697 EnableWindow(GetDlgItem(m_hwnd, IDC_SKIPWORKTREE), FALSE);
698 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), FALSE);
699 EnableWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), FALSE);
702 if (filenames.size() == 1 && m_fileStats.allAreVersionedItems)
704 SetDlgItemText(m_hwnd, IDC_LAST_SUBJECT, CString(MAKEINTRESOURCE(IDS_LOADING)));
705 _beginthread(LogThreadEntry, 0, this);
707 else
708 ShowWindow(GetDlgItem(m_hwnd, IDC_STATIC_LASTMODIFIED), SW_HIDE);
712 // CShellExt member functions (needed for IShellPropSheetExt)
713 STDMETHODIMP CShellExt::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
715 if (files_.empty())
716 return S_OK;
718 CTGitPath firstFile(files_[0].c_str());
720 CString projectTopDir;
721 if (!firstFile.HasAdminDir(&projectTopDir))
722 return S_OK;
724 if (files_.size() == 1 && firstFile.IsWCRoot()) // might be a submodule
726 CString parentRepo;
727 if (firstFile.IsRegisteredSubmoduleOfParentProject(&parentRepo))
729 LoadLangDll();
730 PROPSHEETPAGE psp = { 0 };
731 HPROPSHEETPAGE hPage;
732 CGitPropertyPage* sheetpage = new (std::nothrow) CGitPropertyPage(files_, parentRepo, true);
734 if (!sheetpage)
735 return E_OUTOFMEMORY;
737 psp.dwSize = sizeof(psp);
738 psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE | PSP_USEICONID | PSP_USECALLBACK;
739 psp.hInstance = g_hResInst;
740 psp.pszTemplate = MAKEINTRESOURCE(IDD_PROPPAGE);
741 psp.pszIcon = MAKEINTRESOURCE(IDI_APPSMALL);
742 psp.pszTitle = L"Git Submodule";
743 psp.pfnDlgProc = reinterpret_cast<DLGPROC>(PageProc);
744 psp.lParam = reinterpret_cast<LPARAM>(sheetpage);
745 psp.pfnCallback = PropPageCallbackProc;
746 psp.pcRefParent = (UINT*)&g_cRefThisDll;
748 hPage = CreatePropertySheetPage(&psp);
750 if (hPage && !lpfnAddPage(hPage, lParam))
752 delete sheetpage;
753 DestroyPropertySheetPage(hPage);
758 for (const auto& file_ : files_)
760 CString currentProjectTopDir;
761 if (!CTGitPath(file_.c_str()).HasAdminDir(&currentProjectTopDir) || !CPathUtils::ArePathStringsEqual(projectTopDir, currentProjectTopDir))
762 return S_OK;
765 LoadLangDll();
766 PROPSHEETPAGE psp = { 0 };
767 HPROPSHEETPAGE hPage;
768 CGitPropertyPage* sheetpage = new (std::nothrow) CGitPropertyPage(files_, projectTopDir, false);
770 if (!sheetpage)
771 return E_OUTOFMEMORY;
773 psp.dwSize = sizeof (psp);
774 psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE | PSP_USEICONID | PSP_USECALLBACK;
775 psp.hInstance = g_hResInst;
776 psp.pszTemplate = MAKEINTRESOURCE(IDD_PROPPAGE);
777 psp.pszIcon = MAKEINTRESOURCE(IDI_APPSMALL);
778 psp.pszTitle = L"Git";
779 psp.pfnDlgProc = reinterpret_cast<DLGPROC>(PageProc);
780 psp.lParam = reinterpret_cast<LPARAM>(sheetpage);
781 psp.pfnCallback = PropPageCallbackProc;
782 psp.pcRefParent = (UINT*)&g_cRefThisDll;
784 hPage = CreatePropertySheetPage (&psp);
786 if (hPage)
788 if (!lpfnAddPage (hPage, lParam))
790 delete sheetpage;
791 DestroyPropertySheetPage (hPage);
795 return S_OK;
798 STDMETHODIMP CShellExt::ReplacePage (UINT /*uPageID*/, LPFNADDPROPSHEETPAGE /*lpfnReplaceWith*/, LPARAM /*lParam*/)
800 return E_FAIL;