1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2012 - 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"
30 #define MAX_STRING_LENGTH 4096 //should be big enough
32 // Nonmember function prototypes
33 BOOL CALLBACK
PageProc (HWND
, UINT
, WPARAM
, LPARAM
);
34 UINT CALLBACK
PropPageCallbackProc ( HWND hwnd
, UINT uMsg
, LPPROPSHEETPAGE ppsp
);
36 /////////////////////////////////////////////////////////////////////////////
37 // Dialog procedures and other callback functions
39 BOOL CALLBACK
PageProc (HWND hwnd
, UINT uMessage
, WPARAM wParam
, LPARAM lParam
)
41 CGitPropertyPage
* sheetpage
;
43 if (uMessage
== WM_INITDIALOG
)
45 sheetpage
= (CGitPropertyPage
*) ((LPPROPSHEETPAGE
) lParam
)->lParam
;
46 SetWindowLongPtr (hwnd
, GWLP_USERDATA
, (LONG_PTR
) sheetpage
);
47 sheetpage
->SetHwnd(hwnd
);
51 sheetpage
= (CGitPropertyPage
*) GetWindowLongPtr (hwnd
, GWLP_USERDATA
);
55 return sheetpage
->PageProc(hwnd
, uMessage
, wParam
, lParam
);
60 UINT CALLBACK
PropPageCallbackProc ( HWND
/*hwnd*/, UINT uMsg
, LPPROPSHEETPAGE ppsp
)
62 // Delete the page before closing.
63 if (PSPCB_RELEASE
== uMsg
)
65 CGitPropertyPage
* sheetpage
= (CGitPropertyPage
*) ppsp
->lParam
;
66 if (sheetpage
!= NULL
)
72 // *********************** CGitPropertyPage *************************
74 CGitPropertyPage::CGitPropertyPage(const std::vector
<stdstring
> &newFilenames
)
75 :filenames(newFilenames
)
80 CGitPropertyPage::~CGitPropertyPage(void)
84 void CGitPropertyPage::SetHwnd(HWND newHwnd
)
89 BOOL
CGitPropertyPage::PageProc (HWND
/*hwnd*/, UINT uMessage
, WPARAM wParam
, LPARAM lParam
)
100 LPNMHDR point
= (LPNMHDR
)lParam
;
101 int code
= point
->code
;
103 // Respond to notifications.
105 if (code
== PSN_APPLY
&& filenames
.size() == 1 && m_bChanged
)
109 CTGitPath
path(filenames
.front().c_str());
110 CString projectTopDir
;
111 if(!path
.HasAdminDir(&projectTopDir
) || path
.IsDirectory())
114 CTGitPath relatepath
;
115 CString strpath
=path
.GetWinPathString();
117 if(projectTopDir
[projectTopDir
.GetLength() - 1] == _T('\\'))
118 relatepath
.SetFromWin(strpath
.Right(strpath
.GetLength() - projectTopDir
.GetLength()));
120 relatepath
.SetFromWin(strpath
.Right(strpath
.GetLength() - projectTopDir
.GetLength() - 1));
122 CStringA gitdir
= CUnicodeUtils::GetMulti(projectTopDir
, CP_UTF8
);
123 git_repository
*repository
= NULL
;
124 git_index
*index
= NULL
;
126 int ret
= git_repository_open(&repository
, gitdir
.GetBuffer());
127 gitdir
.ReleaseBuffer();
131 if (git_repository_index(&index
, repository
))
133 git_repository_free(repository
);
137 bool changed
= false;
139 CStringA pathA
= CUnicodeUtils::GetMulti(relatepath
.GetGitPathString(), CP_UTF8
);
140 int idx
= git_index_find(index
, pathA
);
143 git_index_entry
*e
= const_cast<git_index_entry
*>(git_index_get_byindex(index
, idx
)); // HACK
145 if (SendMessage(GetDlgItem(m_hwnd
, IDC_ASSUMEVALID
), BM_GETCHECK
, 0, 0) == BST_CHECKED
)
147 if (!(e
->flags
& GIT_IDXENTRY_VALID
))
149 e
->flags
|= GIT_IDXENTRY_VALID
;
155 if (e
->flags
& GIT_IDXENTRY_VALID
)
157 e
->flags
&= ~GIT_IDXENTRY_VALID
;
161 if (SendMessage(GetDlgItem(m_hwnd
, IDC_SKIPWORKTREE
), BM_GETCHECK
, 0, 0) == BST_CHECKED
)
163 if (!(e
->flags_extended
& GIT_IDXENTRY_SKIP_WORKTREE
))
165 e
->flags_extended
|= GIT_IDXENTRY_SKIP_WORKTREE
;
171 if (e
->flags_extended
& GIT_IDXENTRY_SKIP_WORKTREE
)
173 e
->flags_extended
&= ~GIT_IDXENTRY_SKIP_WORKTREE
;
177 if (SendMessage(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), BM_GETCHECK
, 0, 0) == BST_CHECKED
)
179 if (!(e
->mode
& 0111))
194 git_index_add(index
, e
);
199 if (!git_index_write(index
))
203 git_index_free(index
);
206 SetWindowLongPtr (m_hwnd
, DWLP_MSGRESULT
, FALSE
);
214 PageProcOnCommand(wParam
);
216 } // switch (uMessage)
219 void CGitPropertyPage::PageProcOnCommand(WPARAM wParam
)
221 if(HIWORD(wParam
) != BN_CLICKED
)
224 switch (LOWORD(wParam
))
228 tstring gitCmd
= _T(" /command:");
229 gitCmd
+= _T("log /path:\"");
230 gitCmd
+= filenames
.front().c_str();
235 case IDC_SHOWSETTINGS
:
237 CTGitPath
path(filenames
.front().c_str());
238 CString projectTopDir
;
239 if(!path
.HasAdminDir(&projectTopDir
))
242 tstring gitCmd
= _T(" /command:");
243 gitCmd
+= _T("settings /path:\"");
244 gitCmd
+= projectTopDir
;
249 case IDC_ASSUMEVALID
:
250 case IDC_SKIPWORKTREE
:
253 SendMessage(GetParent(m_hwnd
), PSM_CHANGED
, (WPARAM
)m_hwnd
, 0);
258 void CGitPropertyPage::RunCommand(const tstring
& command
)
260 tstring tortoiseProcPath
= CPathUtils::GetAppDirectory(g_hmodThisDll
) + _T("TortoiseProc.exe");
261 if (CCreateProcessHelper::CreateProcessDetached(tortoiseProcPath
.c_str(), const_cast<TCHAR
*>(command
.c_str())))
263 // process started - exit
267 MessageBox(NULL
, CFormatMessageWrapper(), _T("TortoiseProc launch failed"), MB_OK
| MB_ICONINFORMATION
);
270 void CGitPropertyPage::Time64ToTimeString(__time64_t time
, TCHAR
* buf
, size_t buflen
)
274 TCHAR timebuf
[MAX_STRING_LENGTH
];
275 TCHAR datebuf
[MAX_STRING_LENGTH
];
277 LCID locale
= (WORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\LanguageID"), MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
));
278 locale
= MAKELCID(locale
, SORT_DEFAULT
);
283 _localtime64_s(&newtime
, &time
);
285 systime
.wDay
= (WORD
)newtime
.tm_mday
;
286 systime
.wDayOfWeek
= (WORD
)newtime
.tm_wday
;
287 systime
.wHour
= (WORD
)newtime
.tm_hour
;
288 systime
.wMilliseconds
= 0;
289 systime
.wMinute
= (WORD
)newtime
.tm_min
;
290 systime
.wMonth
= (WORD
)newtime
.tm_mon
+1;
291 systime
.wSecond
= (WORD
)newtime
.tm_sec
;
292 systime
.wYear
= (WORD
)newtime
.tm_year
+1900;
293 if (CRegStdDWORD(_T("Software\\TortoiseGit\\LogDateFormat")) == 1)
294 GetDateFormat(locale
, DATE_SHORTDATE
, &systime
, NULL
, datebuf
, MAX_STRING_LENGTH
);
296 GetDateFormat(locale
, DATE_LONGDATE
, &systime
, NULL
, datebuf
, MAX_STRING_LENGTH
);
297 GetTimeFormat(locale
, 0, &systime
, NULL
, timebuf
, MAX_STRING_LENGTH
);
299 _tcsncat_s(buf
, buflen
, timebuf
, MAX_STRING_LENGTH
-1);
300 _tcsncat_s(buf
, buflen
, _T(", "), MAX_STRING_LENGTH
-1);
301 _tcsncat_s(buf
, buflen
, datebuf
, MAX_STRING_LENGTH
-1);
305 struct TreewalkStruct
312 static int TreewalkCB_FindFileRecentCommit(const char *root
, const git_tree_entry
*entry
, void *payload
)
314 TreewalkStruct
*treewalkstruct
= (TreewalkStruct
*)payload
;
315 char folder
[MAX_PATH
];
316 strcpy(folder
, root
);
317 strcat(folder
, git_tree_entry_name(entry
));
319 if (strstr(treewalkstruct
->folder
, folder
))
322 if (!strcmp(treewalkstruct
->folder
, root
))
324 if (!strcmp(git_tree_entry_name(entry
), treewalkstruct
->name
))
326 git_oid_cpy(&treewalkstruct
->oid
, git_tree_entry_id(entry
));
336 static git_commit
* FindFileRecentCommit(git_repository
*repository
, CString path
)
338 git_commit
*commit
= NULL
, *commit2
= NULL
;
340 if (git_revwalk_new(&walk
, repository
))
343 CStringA pathA
= CUnicodeUtils::GetUTF8(path
);
344 const char *pathC
= pathA
.GetBuffer();
345 char folder
[MAX_PATH
], file
[MAX_PATH
];
346 const char *slash
= strrchr(pathC
, '/');
349 strncpy(folder
, pathC
, slash
- pathC
+ 1);
350 folder
[slash
- pathC
+ 1] = '\0';
351 strcpy(file
, slash
+ 1);
358 pathA
.ReleaseBuffer();
360 TreewalkStruct treewalkstruct
= { folder
, file
};
361 TreewalkStruct treewalkstruct2
= { folder
, file
};
365 if (git_revwalk_push_head(walk
))
366 throw "git_revwalk_push_head";
369 while (!git_revwalk_next(&oid
, walk
))
373 git_commit_free(commit
);
377 if (git_commit_lookup(&commit
, repository
, &oid
))
380 throw "git_commit_lookup 1";
384 if (git_commit_tree(&tree
, commit
))
385 throw "git_commit_tree 1";
387 memset(&treewalkstruct
.oid
.id
, 0, sizeof(treewalkstruct
.oid
.id
));
388 int ret
= git_tree_walk(tree
, GIT_TREEWALK_PRE
, TreewalkCB_FindFileRecentCommit
, &treewalkstruct
);
390 if (ret
< 0 && ret
!= GIT_EUSER
)
391 throw "git_tree_walk 1";
393 // check if file not found
394 if (git_oid_iszero(&treewalkstruct
.oid
))
396 git_commit_free(commit
);
402 // for merge point, check if it is different to all parents, if yes then there are real change in the merge point.
403 // if no parent then of course it is different
404 for (unsigned int i
= 0; i
< git_commit_parentcount(commit
); i
++)
406 if (git_commit_parent(&commit2
, commit
, i
))
409 throw "git_commit_parent";
412 if (git_commit_tree(&tree
, commit2
))
413 throw "git_commit_tree 2";
415 git_commit_free(commit2
);
416 memset(&treewalkstruct2
.oid
.id
, 0, sizeof(treewalkstruct2
.oid
.id
));
417 int ret
= git_tree_walk(tree
, GIT_TREEWALK_PRE
, TreewalkCB_FindFileRecentCommit
, &treewalkstruct2
);
419 if (ret
< 0 && ret
!= GIT_EUSER
)
420 throw "git_tree_walk 2";
422 if (!git_oid_cmp(&treewalkstruct
.oid
, &treewalkstruct2
.oid
))
424 else if (git_revwalk_hide(walk
, git_commit_parent_id(commit
, i
)))
425 throw "git_revwalk_hide";
436 git_commit_free(commit
);
441 git_commit_free(commit2
);
446 git_revwalk_free(walk
);
450 void CGitPropertyPage::DisplayCommit(git_commit
* commit
, UINT hashLabel
, UINT subjectLabel
, UINT authorLabel
, UINT dateLabel
)
452 int encode
= CP_UTF8
;
453 const char * encodingString
= git_commit_message_encoding(commit
);
454 if (encodingString
!= NULL
)
457 g_Git
.StringAppend(&str
, (BYTE
*)encodingString
, CP_UTF8
);
458 encode
= CUnicodeUtils::GetCPCode(str
);
461 const git_signature
* author
= git_commit_author(commit
);
463 g_Git
.StringAppend(&authorName
, (BYTE
*)author
->name
, encode
);
466 g_Git
.StringAppend(&message
, (BYTE
*)git_commit_message(commit
), encode
);
469 message
= message
.Tokenize(L
"\n", start
);
471 SetDlgItemText(m_hwnd
, hashLabel
, CGitHash((char*)(git_commit_id(commit
)->id
)).ToString());
472 SetDlgItemText(m_hwnd
, subjectLabel
, message
);
473 SetDlgItemText(m_hwnd
, authorLabel
, authorName
);
476 Time64ToTimeString(author
->when
.time
, authorDate
.GetBufferSetLength(200), 200);
477 SetDlgItemText(m_hwnd
, dateLabel
, authorDate
);
480 void CGitPropertyPage::InitWorkfileView()
482 if (filenames
.empty())
485 CTGitPath
path(filenames
.front().c_str());
487 CString ProjectTopDir
;
488 if(!path
.HasAdminDir(&ProjectTopDir
))
491 CStringA gitdir
= CUnicodeUtils::GetMulti(ProjectTopDir
, CP_UTF8
);
492 git_repository
*repository
= NULL
;
494 int ret
= git_repository_open(&repository
, gitdir
.GetBuffer());
495 gitdir
.ReleaseBuffer();
501 git
.SetCurrentDir(ProjectTopDir
);
503 git
.Run(_T("git.exe config user.name"), &username
, CP_UTF8
);
505 git
.Run(_T("git.exe config user.email"), &useremail
, CP_UTF8
);
507 git
.Run(_T("git.exe config core.autocrlf"), &autocrlf
, CP_UTF8
);
509 git
.Run(_T("git.exe config core.safecrlf"), &safecrlf
, CP_UTF8
);
512 CString remotebranch
;
514 if (!g_Git
.GetCurrentBranchFromFile(ProjectTopDir
, branch
))
517 cmd
.Format(_T("git.exe config branch.%s.merge"), branch
.Trim());
518 git
.Run(cmd
, &remotebranch
, CP_UTF8
);
519 cmd
.Format(_T("git.exe config branch.%s.remote"), branch
.Trim());
520 git
.Run(cmd
, &remote
, CP_UTF8
);
523 if((!remote
.IsEmpty()) && (!remotebranch
.IsEmpty()))
525 remotebranch
= remotebranch
.Mid(11);
526 remotebranch
= remote
+ _T("/") + remotebranch
;
530 branch
= _T("detached HEAD");
532 if (autocrlf
.Trim().IsEmpty())
533 autocrlf
= _T("false");
534 if (safecrlf
.Trim().IsEmpty())
535 safecrlf
= _T("false");
537 SetDlgItemText(m_hwnd
,IDC_CONFIG_USERNAME
,username
.Trim());
538 SetDlgItemText(m_hwnd
,IDC_CONFIG_USEREMAIL
,useremail
.Trim());
539 SetDlgItemText(m_hwnd
,IDC_CONFIG_AUTOCRLF
,autocrlf
.Trim());
540 SetDlgItemText(m_hwnd
,IDC_CONFIG_SAFECRLF
,safecrlf
.Trim());
542 SetDlgItemText(m_hwnd
,IDC_SHELL_CURRENT_BRANCH
,branch
.Trim());
543 remotebranch
.Trim().Replace(_T("\n"), _T("; "));
544 SetDlgItemText(m_hwnd
,IDC_SHELL_REMOTE_BRANCH
, remotebranch
);
547 git_commit
*HEADcommit
= NULL
;
548 if (!git_reference_name_to_id(&oid
, repository
, "HEAD") && !git_commit_lookup(&HEADcommit
, repository
, &oid
) && HEADcommit
!= NULL
)
550 DisplayCommit(HEADcommit
, IDC_HEAD_HASH
, IDC_HEAD_SUBJECT
, IDC_HEAD_AUTHOR
, IDC_HEAD_DATE
);
551 git_commit_free(HEADcommit
);
555 if (filenames
.size() == 1)
557 CTGitPath relatepath
;
558 CString strpath
=path
.GetWinPathString();
560 if(ProjectTopDir
[ProjectTopDir
.GetLength()-1] == _T('\\'))
562 relatepath
.SetFromWin( strpath
.Right(strpath
.GetLength()-ProjectTopDir
.GetLength()));
566 relatepath
.SetFromWin( strpath
.Right(strpath
.GetLength()-ProjectTopDir
.GetLength()-1));
569 git_commit
*commit
= FindFileRecentCommit(repository
, relatepath
.GetGitPathString());
572 DisplayCommit(commit
, IDC_LAST_HASH
, IDC_LAST_SUBJECT
, IDC_LAST_AUTHOR
, IDC_LAST_DATE
);
573 git_commit_free(commit
);
576 if (!path
.IsDirectory())
578 // get assume valid flag
579 bool assumevalid
= false;
580 bool skipworktree
= false;
581 bool executable
= false;
584 git_index
*index
= NULL
;
586 if (git_repository_index(&index
, repository
))
589 CStringA pathA
= CUnicodeUtils::GetMulti(relatepath
.GetGitPathString(), CP_UTF8
);
590 int idx
= git_index_find(index
, pathA
);
593 const git_index_entry
*e
= git_index_get_byindex(index
, idx
);
595 if (e
->flags
& GIT_IDXENTRY_VALID
)
598 if (e
->flags_extended
& GIT_IDXENTRY_SKIP_WORKTREE
)
606 // do not show checkboxes for unversioned files
607 ShowWindow(GetDlgItem(m_hwnd
, IDC_ASSUMEVALID
), SW_HIDE
);
608 ShowWindow(GetDlgItem(m_hwnd
, IDC_SKIPWORKTREE
), SW_HIDE
);
609 ShowWindow(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), SW_HIDE
);
612 git_index_free(index
);
614 SendMessage(GetDlgItem(m_hwnd
, IDC_ASSUMEVALID
), BM_SETCHECK
, assumevalid
? BST_CHECKED
: BST_UNCHECKED
, 0);
615 SendMessage(GetDlgItem(m_hwnd
, IDC_SKIPWORKTREE
), BM_SETCHECK
, skipworktree
? BST_CHECKED
: BST_UNCHECKED
, 0);
616 SendMessage(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), BM_SETCHECK
, executable
? BST_CHECKED
: BST_UNCHECKED
, 0);
620 ShowWindow(GetDlgItem(m_hwnd
, IDC_ASSUMEVALID
), SW_HIDE
);
621 ShowWindow(GetDlgItem(m_hwnd
, IDC_SKIPWORKTREE
), SW_HIDE
);
622 ShowWindow(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), SW_HIDE
);
627 ShowWindow(GetDlgItem(m_hwnd
, IDC_ASSUMEVALID
), SW_HIDE
);
628 ShowWindow(GetDlgItem(m_hwnd
, IDC_SKIPWORKTREE
), SW_HIDE
);
629 ShowWindow(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), SW_HIDE
);
632 git_repository_free(repository
);
636 // CShellExt member functions (needed for IShellPropSheetExt)
637 STDMETHODIMP
CShellExt::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage
, LPARAM lParam
)
641 return AddPages_Wrap(lpfnAddPage
, lParam
);
643 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
649 STDMETHODIMP
CShellExt::AddPages_Wrap(LPFNADDPROPSHEETPAGE lpfnAddPage
, LPARAM lParam
)
651 CString ProjectTopDir
;
653 for (std::vector
<stdstring
>::iterator I
= files_
.begin(); I
!= files_
.end(); ++I
)
656 GitStatus svn = GitStatus();
657 if (svn.GetStatus(CTGitPath(I->c_str())) == (-2))
658 return S_OK; // file/directory not under version control
660 if (svn.status->entry == NULL)
663 if( CTGitPath(I
->c_str()).HasAdminDir(&ProjectTopDir
))
674 SecureZeroMemory(&psp
, sizeof(PROPSHEETPAGE
));
675 HPROPSHEETPAGE hPage
;
676 CGitPropertyPage
*sheetpage
= new (std::nothrow
) CGitPropertyPage(files_
);
678 psp
.dwSize
= sizeof (psp
);
679 psp
.dwFlags
= PSP_USEREFPARENT
| PSP_USETITLE
| PSP_USEICONID
| PSP_USECALLBACK
;
680 psp
.hInstance
= g_hResInst
;
681 psp
.pszTemplate
= MAKEINTRESOURCE(IDD_PROPPAGE
);
682 psp
.pszIcon
= MAKEINTRESOURCE(IDI_APPSMALL
);
683 psp
.pszTitle
= _T("Git");
684 psp
.pfnDlgProc
= (DLGPROC
) PageProc
;
685 psp
.lParam
= (LPARAM
) sheetpage
;
686 psp
.pfnCallback
= PropPageCallbackProc
;
687 psp
.pcRefParent
= (UINT
*)&g_cRefThisDll
;
689 hPage
= CreatePropertySheetPage (&psp
);
693 if (!lpfnAddPage (hPage
, lParam
))
696 DestroyPropertySheetPage (hPage
);
703 STDMETHODIMP
CShellExt::ReplacePage (UINT
/*uPageID*/, LPFNADDPROPSHEETPAGE
/*lpfnReplaceWith*/, LPARAM
/*lParam*/)