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.
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"
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
);
53 sheetpage
= reinterpret_cast<CGitPropertyPage
*>(GetWindowLongPtr(hwnd
, GWLP_USERDATA
));
56 return sheetpage
->PageProc(hwnd
, uMessage
, wParam
, lParam
);
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
);
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
'\\')
84 CGitPropertyPage::~CGitPropertyPage()
88 void CGitPropertyPage::SetHwnd(HWND newHwnd
)
93 BOOL
CGitPropertyPage::PageProc (HWND
/*hwnd*/, UINT uMessage
, WPARAM wParam
, LPARAM lParam
)
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
));
118 if (git_repository_index(index
.GetPointer(), repository
))
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
)
133 file
.SetFromWin(CString(filename
.c_str()).Mid(m_iStripLength
));
134 CStringA pathA
= CUnicodeUtils::GetUTF8(file
.GetGitPathString());
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
;
148 else if (assumeValid
!= BST_INDETERMINATE
)
150 if (e
->flags
& GIT_INDEX_ENTRY_VALID
)
152 e
->flags
&= ~GIT_INDEX_ENTRY_VALID
;
156 if (skipWorktree
== BST_CHECKED
)
158 if (!(e
->flags_extended
& GIT_INDEX_ENTRY_SKIP_WORKTREE
))
160 e
->flags_extended
|= GIT_INDEX_ENTRY_SKIP_WORKTREE
;
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
;
172 if (executable
== BST_CHECKED
)
174 if (!(e
->mode
& 0111))
176 e
->mode
= GIT_FILEMODE_BLOB_EXECUTABLE
;
180 else if (executable
!= BST_INDETERMINATE
)
184 e
->mode
= GIT_FILEMODE_BLOB
;
188 if (symlink
== BST_CHECKED
)
190 if ((e
->mode
& GIT_FILEMODE_LINK
) != GIT_FILEMODE_LINK
)
192 e
->mode
= GIT_FILEMODE_LINK
;
196 else if (symlink
!= BST_INDETERMINATE
)
198 if ((e
->mode
& GIT_FILEMODE_LINK
) == GIT_FILEMODE_LINK
)
200 e
->mode
= GIT_FILEMODE_BLOB
;
205 git_index_add(index
, e
);
211 if (!git_index_write(index
))
216 SetWindowLongPtr (m_hwnd
, DWLP_MSGRESULT
, FALSE
);
223 PageProcOnCommand(wParam
);
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
);
235 void CGitPropertyPage::PageProcOnCommand(WPARAM wParam
)
237 if(HIWORD(wParam
) != BN_CLICKED
)
240 switch (LOWORD(wParam
))
244 std::wstring gitCmd
= L
" /command:";
245 gitCmd
+= L
"log /path:\"";
246 gitCmd
+= filenames
.front().c_str();
249 gitCmd
+= L
" /submodule";
253 case IDC_SHOWSETTINGS
:
255 std::wstring gitCmd
= L
" /command:";
256 gitCmd
+= L
"settings /path:\"";
257 gitCmd
+= m_ProjectTopDir
;
262 case IDC_ASSUMEVALID
:
263 case IDC_SKIPWORKTREE
:
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);
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);
281 EnableWindow(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), TRUE
);
283 SendMessage(GetParent(m_hwnd
), PSM_CHANGED
, reinterpret_cast<WPARAM
>(m_hwnd
), 0);
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
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
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
);
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
);
327 GetDateFormat(locale
, DATE_LONGDATE
, &systime
, nullptr, datebuf
, MAX_STRING_LENGTH
);
328 GetTimeFormat(locale
, 0, &systime
, nullptr, timebuf
, MAX_STRING_LENGTH
);
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
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
))
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
));
367 static git_commit
* FindFileRecentCommit(git_repository
* repository
, const CString
& path
)
370 if (git_revwalk_new(walk
.GetPointer(), repository
))
373 CStringA pathA
= CUnicodeUtils::GetUTF8(path
);
374 if (pathA
.GetLength() >= MAX_PATH
)
376 const char *pathC
= pathA
;
377 char folder
[MAX_PATH
] = {0}, file
[MAX_PATH
] = {0};
378 const char *slash
= strrchr(pathC
, '/');
381 strncpy(folder
, pathC
, slash
- pathC
+ 1);
382 folder
[slash
- pathC
+ 1] = '\0';
383 strcpy(file
, slash
+ 1);
391 TreewalkStruct treewalkstruct
= { folder
, file
};
393 if (git_revwalk_push_head(walk
))
398 while (!git_revwalk_next(&oid
, walk
))
400 if (git_commit_lookup(commit
.GetPointer(), repository
, &oid
))
404 if (git_commit_tree(tree
.GetPointer(), commit
))
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
)
411 // check if file not found
412 if (git_oid_is_zero(&treewalkstruct
.oid
))
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
)
421 if (git_commit_parent(commit2
.GetPointer(), commit
, i
))
425 if (git_commit_tree(tree2
.GetPointer(), commit2
))
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
)
433 if (!git_oid_cmp(&treewalkstruct
.oid
, &treewalkstruct2
.oid
))
435 else if (git_revwalk_hide(walk
, git_commit_parent_id(commit
, i
)))
443 return commit
.Detach();
446 void CGitPropertyPage::DisplayCommit(const git_commit
* commit
, UINT hashLabel
, UINT subjectLabel
, UINT authorLabel
, UINT dateLabel
)
450 SetDlgItemText(m_hwnd
, hashLabel
, L
"");
451 SetDlgItemText(m_hwnd
, subjectLabel
, L
"");
452 SetDlgItemText(m_hwnd
, authorLabel
, L
"");
453 SetDlgItemText(m_hwnd
, dateLabel
, L
"");
457 int encode
= CP_UTF8
;
458 const char * encodingString
= git_commit_message_encoding(commit
);
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
);
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
);
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
));
487 CTGitPath relatepath
;
488 relatepath
.SetFromWin(path
.GetWinPathString().Mid(m_iStripLength
));
490 CAutoCommit
commit(FindFileRecentCommit(repository
, relatepath
.GetGitPathString()));
493 SendMessage(m_hwnd
, m_UpdateLastCommit
, NULL
, reinterpret_cast<LPARAM
>(static_cast<git_commit
*>(commit
)));
497 SendMessage(m_hwnd
, m_UpdateLastCommit
, NULL
, NULL
);
503 void CGitPropertyPage::LogThreadEntry(void *param
)
505 reinterpret_cast<CGitPropertyPage
*>(param
)->LogThread();
508 void CGitPropertyPage::InitWorkfileView()
510 if (filenames
.empty())
513 CTGitPath
path(filenames
.front().c_str());
515 CAutoRepository
repository(CUnicodeUtils::GetUTF8(m_ProjectTopDir
));
518 SetDlgItemText(m_hwnd
, IDC_SHELL_CURRENT_BRANCH
, CGit::GetLibGit2LastErr());
527 CAutoConfig
config(repository
);
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
);
537 CString remotebranch
;
540 if (err
= git_repository_head_detached(repository
); err
== 1)
541 branch
= L
"detached 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
'/');
567 remoteName
.Format(L
"remote.%s.url", static_cast<LPCWSTR
>(remotebranch
.Left(pos
)));
568 config
.GetString(remoteName
, remoteUrl
);
572 remotebranch
= CGit::GetLibGit2LastErr();
576 branch
= CGit::GetLibGit2LastErr();
578 if (autocrlf
.Trim().IsEmpty())
580 if (safecrlf
.Trim().IsEmpty())
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
);
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;
601 if (git_repository_index(index
.GetPointer(), repository
))
604 m_fileStats
.allAreVersionedItems
= true;
605 for (const auto& filename
: filenames
)
608 file
.SetFromWin(CString(filename
.c_str()).Mid(m_iStripLength
));
609 CStringA pathA
= CUnicodeUtils::GetUTF8(file
.GetGitPathString());
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
;
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
;
632 m_fileStats
.allAreVersionedItems
= false;
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);
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);
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
);
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
);
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
);
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);
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
)
718 CTGitPath
firstFile(files_
[0].c_str());
720 CString projectTopDir
;
721 if (!firstFile
.HasAdminDir(&projectTopDir
))
724 if (files_
.size() == 1 && firstFile
.IsWCRoot()) // might be a submodule
727 if (firstFile
.IsRegisteredSubmoduleOfParentProject(&parentRepo
))
730 PROPSHEETPAGE psp
= { 0 };
731 HPROPSHEETPAGE hPage
;
732 CGitPropertyPage
* sheetpage
= new (std::nothrow
) CGitPropertyPage(files_
, parentRepo
, true);
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
))
753 DestroyPropertySheetPage(hPage
);
758 for (const auto& file_
: files_
)
760 CString currentProjectTopDir
;
761 if (!CTGitPath(file_
.c_str()).HasAdminDir(¤tProjectTopDir
) || !CPathUtils::ArePathStringsEqual(projectTopDir
, currentProjectTopDir
))
766 PROPSHEETPAGE psp
= { 0 };
767 HPROPSHEETPAGE hPage
;
768 CGitPropertyPage
* sheetpage
= new (std::nothrow
) CGitPropertyPage(files_
, projectTopDir
, false);
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
);
788 if (!lpfnAddPage (hPage
, lParam
))
791 DestroyPropertySheetPage (hPage
);
798 STDMETHODIMP
CShellExt::ReplacePage (UINT
/*uPageID*/, LPFNADDPROPSHEETPAGE
/*lpfnReplaceWith*/, LPARAM
/*lParam*/)