TortoiseShell: Merge crash fixes from TSVN
[TortoiseGit.git] / src / TortoiseShell / GITPropertyPage.cpp
blob32a8bd4f07199dc19b51d300d5936408eaf61395
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.
20 #include "stdafx.h"
22 #include "ShellExt.h"
23 #include "gitpropertypage.h"
24 #include "UnicodeUtils.h"
25 #include "PathUtils.h"
26 #include "GitStatus.h"
27 #include "git2.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);
49 else
51 sheetpage = (CGitPropertyPage*) GetWindowLongPtr (hwnd, GWLP_USERDATA);
54 if (sheetpage != 0L)
55 return sheetpage->PageProc(hwnd, uMessage, wParam, lParam);
56 else
57 return FALSE;
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)
67 delete sheetpage;
69 return 1;
72 // *********************** CGitPropertyPage *************************
74 CGitPropertyPage::CGitPropertyPage(const std::vector<stdstring> &newFilenames)
75 :filenames(newFilenames)
76 ,m_bChanged(false)
80 CGitPropertyPage::~CGitPropertyPage(void)
84 void CGitPropertyPage::SetHwnd(HWND newHwnd)
86 m_hwnd = newHwnd;
89 BOOL CGitPropertyPage::PageProc (HWND /*hwnd*/, UINT uMessage, WPARAM wParam, LPARAM lParam)
91 switch (uMessage)
93 case WM_INITDIALOG:
95 InitWorkfileView();
96 return TRUE;
98 case WM_NOTIFY:
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())
112 break;
114 CTGitPath relatepath;
115 CString strpath=path.GetWinPathString();
117 if(projectTopDir[projectTopDir.GetLength() - 1] == _T('\\'))
118 relatepath.SetFromWin(strpath.Right(strpath.GetLength() - projectTopDir.GetLength()));
119 else
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();
128 if (ret)
129 break;
131 if (git_repository_index(&index, repository))
133 git_repository_free(repository);
134 break;
137 bool changed = false;
139 CStringA pathA = CUnicodeUtils::GetMulti(relatepath.GetGitPathString(), CP_UTF8);
140 int idx = git_index_find(index, pathA);
141 if (idx >= 0) {
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;
149 changed = true;
152 else
154 if (e->flags & GIT_IDXENTRY_VALID)
156 e->flags &= ~GIT_IDXENTRY_VALID;
157 changed = true;
160 if (SendMessage(GetDlgItem(m_hwnd, IDC_EXECUTABLE), BM_GETCHECK, 0, 0) == BST_CHECKED)
162 if (!(e->mode & 0111))
164 e->mode |= 0111;
165 changed = true;
168 else
170 if (e->mode & 0111)
172 e->mode &= ~0111;
173 changed = true;
176 if (changed)
177 git_index_add2(index, e);
180 if (changed)
182 if (!git_index_write(index))
183 m_bChanged = false;
186 git_index_free(index);
187 } while (0);
189 SetWindowLongPtr (m_hwnd, DWLP_MSGRESULT, FALSE);
190 return TRUE;
193 case WM_DESTROY:
194 return TRUE;
196 case WM_COMMAND:
197 PageProcOnCommand(wParam);
198 break;
199 } // switch (uMessage)
200 return FALSE;
202 void CGitPropertyPage::PageProcOnCommand(WPARAM wParam)
204 if(HIWORD(wParam) != BN_CLICKED)
205 return;
207 switch (LOWORD(wParam))
209 case IDC_SHOWLOG:
211 STARTUPINFO startup;
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();
220 gitCmd += _T("\"");
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);
227 break;
228 case IDC_SHOWSETTINGS:
230 CTGitPath path(filenames.front().c_str());
231 CString projectTopDir;
232 if(!path.HasAdminDir(&projectTopDir))
233 return;
235 STARTUPINFO startup;
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;
245 gitCmd += _T("\"");
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);
252 break;
253 case IDC_ASSUMEVALID:
254 case IDC_EXECUTABLE:
255 m_bChanged = true;
256 SendMessage(GetParent(m_hwnd), PSM_CHANGED, (WPARAM)m_hwnd, 0);
257 break;
260 void CGitPropertyPage::Time64ToTimeString(__time64_t time, TCHAR * buf, size_t buflen)
262 struct tm newtime;
263 SYSTEMTIME systime;
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);
270 *buf = '\0';
271 if (time)
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);
285 else
286 GetDateFormat(locale, DATE_LONGDATE, &systime, NULL, datebuf, MAX_STRING_LENGTH);
287 GetTimeFormat(locale, 0, &systime, NULL, timebuf, MAX_STRING_LENGTH);
288 *buf = '\0';
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()
297 CString username;
298 CGit git;
299 if (filenames.empty())
300 return;
302 CTGitPath path(filenames.front().c_str());
303 CString ProjectTopDir;
305 if(!path.HasAdminDir(&ProjectTopDir))
306 return;
308 git.SetCurrentDir(ProjectTopDir);
309 //can't git.exe when create process
310 git.Run(_T("git.exe config user.name"), &username, CP_UTF8);
311 CString useremail;
312 git.Run(_T("git.exe config user.email"), &useremail, CP_UTF8);
313 CString autocrlf;
314 git.Run(_T("git.exe config core.autocrlf"), &autocrlf, CP_UTF8);
315 CString safecrlf;
316 git.Run(_T("git.exe config core.safecrlf"), &safecrlf, CP_UTF8);
318 CString branch;
319 CString headhash;
320 CString remotebranch;
322 git.Run(_T("git.exe symbolic-ref HEAD"), &branch, CP_UTF8);
323 CString cmd,log;
325 if(!branch.IsEmpty())
327 if( branch.Find(_T("refs/heads/"),0) >=0 )
329 CString remote;
330 branch=branch.Mid(11);
331 int start=0;
332 branch=branch.Tokenize(_T("\n"),start);
333 start=0;
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();
371 GitRev revHEAD;
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()));
388 else
390 relatepath.SetFromWin( strpath.Right(strpath.GetLength()-ProjectTopDir.GetLength()-1));
393 GitRev revLast;
394 if(! relatepath.GetGitPathString().IsEmpty())
396 cmd=_T("-z --topo-order -n1 --parents -- \"");
397 cmd+=relatepath.GetGitPathString();
398 cmd+=_T("\"");
400 GIT_LOG handle;
403 if(git_open_log(&handle, CUnicodeUtils::GetUTF8(cmd).GetBuffer()))
404 break;
405 if(git_get_log_firstcommit(handle))
406 break;
408 GIT_COMMIT commit;
409 if (git_get_log_nextcommit(handle, &commit, 0))
410 break;
412 git_close_log(handle);
413 handle = NULL;
414 revLast.ParserFromCommit(&commit);
415 git_free_commit(&commit);
417 }while(0);
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(""));
428 else
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();
444 if (ret)
445 break;
447 if (git_repository_index(&index, repository))
449 git_repository_free(repository);
450 break;
453 CStringA pathA = CUnicodeUtils::GetMulti(relatepath.GetGitPathString(), CP_UTF8);
454 int idx = git_index_find(index, pathA);
455 if (idx >= 0) {
456 git_index_entry *e = git_index_get(index, idx);
458 if (e->flags & GIT_IDXENTRY_VALID)
459 assumevalid = true;
461 if (e->mode & 0111)
462 executable = true;
464 else
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);
472 } while (0);
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);
476 else
478 ShowWindow(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), SW_HIDE);
479 ShowWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), SW_HIDE);
482 else
484 ShowWindow(GetDlgItem(m_hwnd, IDC_ASSUMEVALID), SW_HIDE);
485 ShowWindow(GetDlgItem(m_hwnd, IDC_EXECUTABLE), SW_HIDE);
488 }catch(...)
491 ::SetCurrentDirectory(oldpath);
495 // CShellExt member functions (needed for IShellPropSheetExt)
496 STDMETHODIMP CShellExt::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
498 __try
500 return AddPages_Wrap(lpfnAddPage, lParam);
502 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
505 return E_FAIL;
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)
520 return S_OK;
522 if( CTGitPath(I->c_str()).HasAdminDir(&ProjectTopDir))
523 break;
524 else
525 return S_OK;
528 if (files_.empty())
529 return S_OK;
531 LoadLangDll();
532 PROPSHEETPAGE psp;
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);
550 if (hPage != NULL)
552 if (!lpfnAddPage (hPage, lParam))
554 delete sheetpage;
555 DestroyPropertySheetPage (hPage);
559 return S_OK;
562 STDMETHODIMP CShellExt::ReplacePage (UINT /*uPageID*/, LPFNADDPROPSHEETPAGE /*lpfnReplaceWith*/, LPARAM /*lParam*/)
564 return E_FAIL;