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 "GitStatus.h"
28 #include "UnicodeUtils.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
);
142 git_index_entry
*e
= git_index_get(index
, idx
);
144 if (SendMessage(GetDlgItem(m_hwnd
, IDC_ASSUMEVALID
), BM_GETCHECK
, 0, 0) == BST_CHECKED
)
146 if (!(e
->flags
& GIT_IDXENTRY_VALID
))
148 e
->flags
|= GIT_IDXENTRY_VALID
;
154 if (e
->flags
& GIT_IDXENTRY_VALID
)
156 e
->flags
&= ~GIT_IDXENTRY_VALID
;
160 if (SendMessage(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), BM_GETCHECK
, 0, 0) == BST_CHECKED
)
162 if (!(e
->mode
& 0111))
177 git_index_add2(index
, e
);
182 if (!git_index_write(index
))
186 git_index_free(index
);
189 SetWindowLongPtr (m_hwnd
, DWLP_MSGRESULT
, FALSE
);
197 PageProcOnCommand(wParam
);
199 } // switch (uMessage)
202 void CGitPropertyPage::PageProcOnCommand(WPARAM wParam
)
204 if(HIWORD(wParam
) != BN_CLICKED
)
207 switch (LOWORD(wParam
))
212 PROCESS_INFORMATION process
;
213 memset(&startup
, 0, sizeof(startup
));
214 startup
.cb
= sizeof(startup
);
215 memset(&process
, 0, sizeof(process
));
216 CRegStdString
tortoiseProcPath(_T("Software\\TortoiseGit\\ProcPath"), _T("TortoiseProc.exe"), false, HKEY_LOCAL_MACHINE
);
217 stdstring gitCmd
= _T(" /command:");
218 gitCmd
+= _T("log /path:\"");
219 gitCmd
+= filenames
.front().c_str();
221 if (CreateProcess(((stdstring
)tortoiseProcPath
).c_str(), const_cast<TCHAR
*>(gitCmd
.c_str()), NULL
, NULL
, FALSE
, 0, 0, 0, &startup
, &process
))
223 CloseHandle(process
.hThread
);
224 CloseHandle(process
.hProcess
);
228 case IDC_SHOWSETTINGS
:
230 CTGitPath
path(filenames
.front().c_str());
231 CString projectTopDir
;
232 if(!path
.HasAdminDir(&projectTopDir
))
236 PROCESS_INFORMATION process
;
237 memset(&startup
, 0, sizeof(startup
));
238 startup
.cb
= sizeof(startup
);
239 memset(&process
, 0, sizeof(process
));
240 CRegStdString
tortoiseProcPath(_T("Software\\TortoiseGit\\ProcPath"), _T("TortoiseProc.exe"), false, HKEY_LOCAL_MACHINE
);
242 stdstring gitCmd
= _T(" /command:");
243 gitCmd
+= _T("settings /path:\"");
244 gitCmd
+= projectTopDir
;
246 if (CreateProcess(((stdstring
)tortoiseProcPath
).c_str(), const_cast<TCHAR
*>(gitCmd
.c_str()), NULL
, NULL
, FALSE
, 0, 0, 0, &startup
, &process
))
248 CloseHandle(process
.hThread
);
249 CloseHandle(process
.hProcess
);
253 case IDC_ASSUMEVALID
:
256 SendMessage(GetParent(m_hwnd
), PSM_CHANGED
, (WPARAM
)m_hwnd
, 0);
260 void CGitPropertyPage::Time64ToTimeString(__time64_t time
, TCHAR
* buf
, size_t buflen
)
264 TCHAR timebuf
[MAX_STRING_LENGTH
];
265 TCHAR datebuf
[MAX_STRING_LENGTH
];
267 LCID locale
= (WORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\LanguageID"), MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
));
268 locale
= MAKELCID(locale
, SORT_DEFAULT
);
273 _localtime64_s(&newtime
, &time
);
275 systime
.wDay
= (WORD
)newtime
.tm_mday
;
276 systime
.wDayOfWeek
= (WORD
)newtime
.tm_wday
;
277 systime
.wHour
= (WORD
)newtime
.tm_hour
;
278 systime
.wMilliseconds
= 0;
279 systime
.wMinute
= (WORD
)newtime
.tm_min
;
280 systime
.wMonth
= (WORD
)newtime
.tm_mon
+1;
281 systime
.wSecond
= (WORD
)newtime
.tm_sec
;
282 systime
.wYear
= (WORD
)newtime
.tm_year
+1900;
283 if (CRegStdDWORD(_T("Software\\TortoiseGit\\LogDateFormat")) == 1)
284 GetDateFormat(locale
, DATE_SHORTDATE
, &systime
, NULL
, datebuf
, MAX_STRING_LENGTH
);
286 GetDateFormat(locale
, DATE_LONGDATE
, &systime
, NULL
, datebuf
, MAX_STRING_LENGTH
);
287 GetTimeFormat(locale
, 0, &systime
, NULL
, timebuf
, MAX_STRING_LENGTH
);
289 _tcsncat_s(buf
, buflen
, timebuf
, MAX_STRING_LENGTH
-1);
290 _tcsncat_s(buf
, buflen
, _T(", "), MAX_STRING_LENGTH
-1);
291 _tcsncat_s(buf
, buflen
, datebuf
, MAX_STRING_LENGTH
-1);
295 void CGitPropertyPage::InitWorkfileView()
299 if (filenames
.empty())
302 CTGitPath
path(filenames
.front().c_str());
303 CString ProjectTopDir
;
305 if(!path
.HasAdminDir(&ProjectTopDir
))
308 git
.SetCurrentDir(ProjectTopDir
);
309 //can't git.exe when create process
310 git
.Run(_T("git.exe config user.name"), &username
, CP_UTF8
);
312 git
.Run(_T("git.exe config user.email"), &useremail
, CP_UTF8
);
314 git
.Run(_T("git.exe config core.autocrlf"), &autocrlf
, CP_UTF8
);
316 git
.Run(_T("git.exe config core.safecrlf"), &safecrlf
, CP_UTF8
);
320 CString remotebranch
;
322 git
.Run(_T("git.exe symbolic-ref HEAD"), &branch
, CP_UTF8
);
325 if(!branch
.IsEmpty())
327 if( branch
.Find(_T("refs/heads/"),0) >=0 )
330 branch
=branch
.Mid(11);
332 branch
=branch
.Tokenize(_T("\n"),start
);
334 branch
=branch
.Tokenize(_T("\r"),start
);
335 cmd
.Format(_T("git.exe config branch.%s.merge"),branch
);
336 git
.Run(cmd
, &remotebranch
, CP_UTF8
);
337 cmd
.Format(_T("git.exe config branch.%s.remote"),branch
);
338 git
.Run(cmd
, &remote
, CP_UTF8
);
339 if((!remote
.IsEmpty()) && (!remotebranch
.IsEmpty()))
341 remotebranch
=remotebranch
.Mid(11);
342 remotebranch
=remote
+_T("/")+remotebranch
;
347 TCHAR oldpath
[MAX_PATH
+1];
349 ::GetCurrentDirectory(MAX_PATH
, oldpath
);
351 ::SetCurrentDirectory(ProjectTopDir
);
353 if (autocrlf
.Trim().IsEmpty())
354 autocrlf
= _T("false");
355 if (safecrlf
.Trim().IsEmpty())
356 safecrlf
= _T("false");
358 SetDlgItemText(m_hwnd
,IDC_CONFIG_USERNAME
,username
.Trim());
359 SetDlgItemText(m_hwnd
,IDC_CONFIG_USEREMAIL
,useremail
.Trim());
360 SetDlgItemText(m_hwnd
,IDC_CONFIG_AUTOCRLF
,autocrlf
.Trim());
361 SetDlgItemText(m_hwnd
,IDC_CONFIG_SAFECRLF
,safecrlf
.Trim());
363 SetDlgItemText(m_hwnd
,IDC_SHELL_CURRENT_BRANCH
,branch
.Trim());
364 remotebranch
.Trim().Replace(_T("\n"), _T("; "));
365 SetDlgItemText(m_hwnd
,IDC_SHELL_REMOTE_BRANCH
, remotebranch
);
369 AutoLocker
lock(g_Git
.m_critGitDllSec
);
370 g_Git
.CheckAndInitDll();
372 revHEAD
.GetCommit(CString(_T("HEAD")));
374 SetDlgItemText(m_hwnd
, IDC_HEAD_HASH
, revHEAD
.m_CommitHash
.ToString());
375 SetDlgItemText(m_hwnd
, IDC_HEAD_SUBJECT
, revHEAD
.GetSubject());
376 SetDlgItemText(m_hwnd
, IDC_HEAD_AUTHOR
, revHEAD
.GetAuthorName());
377 SetDlgItemText(m_hwnd
, IDC_HEAD_DATE
, revHEAD
.GetAuthorDate().Format(_T("%Y-%m-%d %H:%M:%S")));
379 if (filenames
.size() == 1)
381 CTGitPath relatepath
;
382 CString strpath
=path
.GetWinPathString();
384 if(ProjectTopDir
[ProjectTopDir
.GetLength()-1] == _T('\\'))
386 relatepath
.SetFromWin( strpath
.Right(strpath
.GetLength()-ProjectTopDir
.GetLength()));
390 relatepath
.SetFromWin( strpath
.Right(strpath
.GetLength()-ProjectTopDir
.GetLength()-1));
394 if(! relatepath
.GetGitPathString().IsEmpty())
396 cmd
=_T("-z --topo-order -n1 --parents -- \"");
397 cmd
+=relatepath
.GetGitPathString();
403 if(git_open_log(&handle
, CUnicodeUtils::GetUTF8(cmd
).GetBuffer()))
405 if(git_get_log_firstcommit(handle
))
409 if (git_get_log_nextcommit(handle
, &commit
, 0))
412 git_close_log(handle
);
414 revLast
.ParserFromCommit(&commit
);
415 git_free_commit(&commit
);
418 if (handle
!= NULL
) {
419 git_close_log(handle
);
423 SetDlgItemText(m_hwnd
, IDC_LAST_HASH
, revLast
.m_CommitHash
.ToString());
424 SetDlgItemText(m_hwnd
, IDC_LAST_SUBJECT
, revLast
.GetSubject());
425 SetDlgItemText(m_hwnd
, IDC_LAST_AUTHOR
, revLast
.GetAuthorName());
426 if (revLast
.m_CommitHash
.IsEmpty())
427 SetDlgItemText(m_hwnd
, IDC_LAST_DATE
, _T(""));
429 SetDlgItemText(m_hwnd
, IDC_LAST_DATE
, revLast
.GetAuthorDate().Format(_T("%Y-%m-%d %H:%M:%S")));
431 if (!path
.IsDirectory())
433 // get assume valid flag
434 bool assumevalid
= false;
435 bool executable
= false;
438 CStringA gitdir
= CUnicodeUtils::GetMulti(ProjectTopDir
, CP_UTF8
);
439 git_repository
*repository
= NULL
;
440 git_index
*index
= NULL
;
442 int ret
= git_repository_open(&repository
, gitdir
.GetBuffer());
443 gitdir
.ReleaseBuffer();
447 if (git_repository_index(&index
, repository
))
449 git_repository_free(repository
);
453 CStringA pathA
= CUnicodeUtils::GetMulti(relatepath
.GetGitPathString(), CP_UTF8
);
454 int idx
= git_index_find(index
, pathA
);
456 git_index_entry
*e
= git_index_get(index
, idx
);
458 if (e
->flags
& GIT_IDXENTRY_VALID
)
466 // do not show checkboxes for unversioned files
467 ShowWindow(GetDlgItem(m_hwnd
, IDC_ASSUMEVALID
), SW_HIDE
);
468 ShowWindow(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), SW_HIDE
);
471 git_index_free(index
);
473 SendMessage(GetDlgItem(m_hwnd
, IDC_ASSUMEVALID
), BM_SETCHECK
, assumevalid
? BST_CHECKED
: BST_UNCHECKED
, 0);
474 SendMessage(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), BM_SETCHECK
, executable
? BST_CHECKED
: BST_UNCHECKED
, 0);
478 ShowWindow(GetDlgItem(m_hwnd
, IDC_ASSUMEVALID
), SW_HIDE
);
479 ShowWindow(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), SW_HIDE
);
484 ShowWindow(GetDlgItem(m_hwnd
, IDC_ASSUMEVALID
), SW_HIDE
);
485 ShowWindow(GetDlgItem(m_hwnd
, IDC_EXECUTABLE
), SW_HIDE
);
491 ::SetCurrentDirectory(oldpath
);
495 // CShellExt member functions (needed for IShellPropSheetExt)
496 STDMETHODIMP
CShellExt::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage
, LPARAM lParam
)
500 return AddPages_Wrap(lpfnAddPage
, lParam
);
502 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
508 STDMETHODIMP
CShellExt::AddPages_Wrap(LPFNADDPROPSHEETPAGE lpfnAddPage
, LPARAM lParam
)
510 CString ProjectTopDir
;
512 for (std::vector
<stdstring
>::iterator I
= files_
.begin(); I
!= files_
.end(); ++I
)
515 GitStatus svn = GitStatus();
516 if (svn.GetStatus(CTGitPath(I->c_str())) == (-2))
517 return S_OK; // file/directory not under version control
519 if (svn.status->entry == NULL)
522 if( CTGitPath(I
->c_str()).HasAdminDir(&ProjectTopDir
))
533 SecureZeroMemory(&psp
, sizeof(PROPSHEETPAGE
));
534 HPROPSHEETPAGE hPage
;
535 CGitPropertyPage
*sheetpage
= new (std::nothrow
) CGitPropertyPage(files_
);
537 psp
.dwSize
= sizeof (psp
);
538 psp
.dwFlags
= PSP_USEREFPARENT
| PSP_USETITLE
| PSP_USEICONID
| PSP_USECALLBACK
;
539 psp
.hInstance
= g_hResInst
;
540 psp
.pszTemplate
= MAKEINTRESOURCE(IDD_PROPPAGE
);
541 psp
.pszIcon
= MAKEINTRESOURCE(IDI_APPSMALL
);
542 psp
.pszTitle
= _T("Git");
543 psp
.pfnDlgProc
= (DLGPROC
) PageProc
;
544 psp
.lParam
= (LPARAM
) sheetpage
;
545 psp
.pfnCallback
= PropPageCallbackProc
;
546 psp
.pcRefParent
= (UINT
*)&g_cRefThisDll
;
548 hPage
= CreatePropertySheetPage (&psp
);
552 if (!lpfnAddPage (hPage
, lParam
))
555 DestroyPropertySheetPage (hPage
);
562 STDMETHODIMP
CShellExt::ReplacePage (UINT
/*uPageID*/, LPFNADDPROPSHEETPAGE
/*lpfnReplaceWith*/, LPARAM
/*lParam*/)