Add credential helper settings in Settings Dialog
[TortoiseGit.git] / src / TortoiseProc / Settings / SettingGitCredential.cpp
blob3c5a24f2882ffe3e8a1090082090f6d12b8fe413
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // SettingGitCredential.cpp : implementation file
22 #include "stdafx.h"
23 #include "TortoiseProc.h"
24 #include "SettingGitCredential.h"
25 #include "Settings.h"
26 #include "GitAdminDir.h"
27 #include "MessageBox.h"
28 #include "AppUtils.h"
29 #include "git.h"
30 #include "PathUtils.h"
32 namespace ConfigType
34 static int Local;
35 static int Global;
36 static int System;
38 static void Init()
40 Local = -1;
41 Global = -1;
42 System = -1;
46 // CSettingGitCredential dialog
48 IMPLEMENT_DYNAMIC(CSettingGitCredential, ISettingsPropPage)
50 CSettingGitCredential::CSettingGitCredential(CString cmdPath)
51 : ISettingsPropPage(CSettingGitCredential::IDD)
52 , m_strUrl(_T(""))
53 , m_strHelper(_T(""))
54 , m_strUsername(_T(""))
55 , m_bUseHttpPath(FALSE)
56 , m_cmdPath(cmdPath)
59 m_ChangedMask = 0;
62 CSettingGitCredential::~CSettingGitCredential()
66 void CSettingGitCredential::DoDataExchange(CDataExchange* pDX)
68 CPropertyPage::DoDataExchange(pDX);
69 DDX_Control(pDX, IDC_LIST_REMOTE, m_ctrlUrlList);
70 DDX_Text(pDX, IDC_EDIT_URL, m_strUrl);
71 DDX_Text(pDX, IDC_COMBO_HELPER, m_strHelper);
72 DDX_Text(pDX, IDC_EDIT_USERNAME, m_strUsername);
73 DDX_Check(pDX, IDC_CHECK_USEHTTPPATH, m_bUseHttpPath);
74 DDX_Control(pDX, IDC_COMBO_CONFIGTYPE, m_ctrlConfigType);
78 BEGIN_MESSAGE_MAP(CSettingGitCredential, CPropertyPage)
79 ON_BN_CLICKED(IDC_BUTTON_ADD, &CSettingGitCredential::OnBnClickedButtonAdd)
80 ON_LBN_SELCHANGE(IDC_LIST_REMOTE, &CSettingGitCredential::OnLbnSelchangeListUrl)
81 ON_CBN_SELCHANGE(IDC_COMBO_CONFIGTYPE, &CSettingGitCredential::OnCbnSelchangeComboConfigType)
82 ON_EN_CHANGE(IDC_EDIT_URL, &CSettingGitCredential::OnEnChangeEditUrl)
83 ON_CBN_EDITCHANGE(IDC_COMBO_HELPER, &CSettingGitCredential::OnEnChangeEditHelper)
84 ON_CBN_SELCHANGE(IDC_COMBO_HELPER, &CSettingGitCredential::OnEnChangeEditHelper)
85 ON_EN_CHANGE(IDC_EDIT_USERNAME, &CSettingGitCredential::OnEnChangeEditUsername)
86 ON_BN_CLICKED(IDC_CHECK_USEHTTPPATH, &CSettingGitCredential::OnBnClickedCheckUsehttppath)
87 ON_BN_CLICKED(IDC_BUTTON_REMOVE, &CSettingGitCredential::OnBnClickedButtonRemove)
88 END_MESSAGE_MAP()
90 static bool RunUAC()
92 CString sCmd;
93 sCmd.Format(_T("/command:settings /page:gitcredential /path:\"%s\""), g_Git.m_CurrentDir);
94 return CAppUtils::RunTortoiseGitProc(sCmd, true);
97 static CString GetWinstorePath()
99 TCHAR winstorebuf[MAX_PATH];
100 ExpandEnvironmentStrings(_T("%AppData%\\GitCredStore\\git-credential-winstore.exe"), winstorebuf, MAX_PATH);
101 CString winstore;
102 winstore.Format(_T("!'%s'"), winstorebuf);
103 return winstore;
106 static bool WincredExists()
108 CString path = g_Git.ms_LastMsysGitDir;
109 if (g_Git.ms_LastMsysGitDir.Right(1) != _T("\\"))
110 path.AppendChar(_T('\\'));
111 path.Append(_T("..\\libexec\\git-core\\git-credential-wincred.exe"));
112 return !!PathFileExists(path);
115 static bool WinstoreExists()
117 return !!PathFileExists(GetWinstorePath());
120 static CStringA RegexEscape(CStringA str)
122 CStringA result;
123 for (int i = 0; i < str.GetLength(); ++i)
125 char c = str[i];
126 switch (c)
128 case '\\': case '*': case '+': case '?': case '|':
129 case '{': case '[': case '(': case ')': case '^':
130 case '$': case '.': case '#': case ' ':
131 result.AppendChar('\\');
132 result.AppendChar(c);
133 break;
134 case '\t': result.Append("\\t"); break;
135 case '\n': result.Append("\\n"); break;
136 case '\r': result.Append("\\r"); break;
137 case '\f': result.Append("\\f"); break;
138 default: result.AppendChar(c); break;
142 return result;
145 BOOL CSettingGitCredential::OnInitDialog()
147 ISettingsPropPage::OnInitDialog();
149 CString proj;
150 bool hasLocal = g_GitAdminDir.HasAdminDir(m_cmdPath, &proj);
151 if (hasLocal)
153 CString title;
154 this->GetWindowText(title);
155 this->SetWindowText(title + _T(" - ") + proj);
158 m_ctrlUrlList.ResetContent();
160 ConfigType::Init();
161 AddConfigType(ConfigType::Local, CString(MAKEINTRESOURCE(IDS_SETTINGS_LOCAL)), hasLocal);
162 AddConfigType(ConfigType::Global, CString(MAKEINTRESOURCE(IDS_SETTINGS_GLOBAL)));
163 AddConfigType(ConfigType::System, CString(MAKEINTRESOURCE(IDS_SETTINGS_SYSTEM)));
164 m_ctrlConfigType.SetCurSel(0);
166 if (WincredExists())
167 ((CComboBox*) GetDlgItem(IDC_COMBO_HELPER))->AddString(_T("wincred"));
168 if (WinstoreExists())
169 ((CComboBox*) GetDlgItem(IDC_COMBO_HELPER))->AddString(GetWinstorePath());
171 LoadList();
173 UpdateData(FALSE);
174 return TRUE;
176 // CSettingGitCredential message handlers
178 void CSettingGitCredential::OnBnClickedButtonAdd()
180 UpdateData();
182 if (m_strHelper.Trim().IsEmpty())
184 CMessageBox::Show(NULL, IDS_GITCREDENTIAL_HELPEREMPTY, IDS_APPNAME, MB_OK | MB_ICONERROR);
185 return;
188 m_ChangedMask = CREDENTIAL_URL | CREDENTIAL_HELPER | CREDENTIAL_USERNAME | CREDENTIAL_USEHTTPPATH;
189 int sel = m_ctrlConfigType.GetCurSel();
190 CString prefix = sel == ConfigType::System ? _T("S") : sel == ConfigType::Global ? _T("G") : _T("L");
191 CString text;
192 if (!m_strUrl.IsEmpty())
193 text.Format(_T("%s:%s"), prefix, m_strUrl);
194 else
195 text = prefix;
196 if (IsUrlExist(text))
198 CString msg;
199 msg.Format(IDS_GITCREDENTIAL_OVERWRITEHELPER, m_strUrl);
200 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDYES)
201 m_ChangedMask &= ~CREDENTIAL_URL;
202 else
203 return;
205 else
207 if (m_strUsername.IsEmpty())
208 m_ChangedMask &= ~CREDENTIAL_USERNAME;
209 if (!m_bUseHttpPath)
210 m_ChangedMask &= ~CREDENTIAL_USEHTTPPATH;
213 OnApply();
216 BOOL CSettingGitCredential::IsUrlExist(CString &text)
218 CString str;
219 for(int i = 0; i < m_ctrlUrlList.GetCount();i++)
221 m_ctrlUrlList.GetText(i, str);
222 if (str == text)
224 return true;
227 return false;
230 void CSettingGitCredential::OnLbnSelchangeListUrl()
232 CWaitCursor wait;
234 if (m_ChangedMask)
236 if (CMessageBox::Show(NULL, IDS_GITCREDENTIAL_SAVEHELPER, IDS_APPNAME, 1, IDI_QUESTION, IDS_SAVEBUTTON, IDS_DISCARDBUTTON) == 1)
237 OnApply();
239 SetModified(FALSE);
241 CString cmd, output;
242 int index = m_ctrlUrlList.GetCurSel();
243 if (index < 0)
245 m_strHelper.Empty();
246 m_strUrl.Empty();
247 m_strHelper.Empty();
248 m_bUseHttpPath = FALSE;
249 UpdateData(FALSE);
250 return;
253 CString text;
254 m_ctrlUrlList.GetText(index, text);
255 int pos = text.Find(_T(':'));
256 CString prefix = pos >= 0 ? text.Left(pos) : text.Left(1);
257 m_ctrlConfigType.SetCurSel(prefix == _T("S") ? ConfigType::System : prefix == _T("X") ? ConfigType::Global : prefix == _T("G") ? ConfigType::Global : ConfigType::Local);
258 m_strUrl = pos >= 0 ? text.Mid(pos + 1) : _T("");
260 m_strHelper = Load(_T("helper"));
261 m_strUsername = Load(_T("username"));
262 m_bUseHttpPath = Load(_T("useHttpPath")) == _T("true") ? TRUE : FALSE;
264 m_ChangedMask = 0;
266 GetDlgItem(IDC_BUTTON_ADD)->EnableWindow(TRUE);
267 GetDlgItem(IDC_BUTTON_REMOVE)->EnableWindow(TRUE);
268 this->UpdateData(FALSE);
271 void CSettingGitCredential::OnCbnSelchangeComboConfigType()
273 SetModified();
276 void CSettingGitCredential::OnEnChangeEditUrl()
278 m_ChangedMask |= CREDENTIAL_URL;
279 SetModified();
282 void CSettingGitCredential::OnEnChangeEditHelper()
284 m_ChangedMask |= CREDENTIAL_HELPER;
286 UpdateData();
287 if (!m_strHelper.IsEmpty())
288 SetModified();
289 else
290 SetModified(0);
293 void CSettingGitCredential::OnEnChangeEditUsername()
295 m_ChangedMask |= CREDENTIAL_USERNAME;
296 SetModified();
299 void CSettingGitCredential::OnBnClickedCheckUsehttppath()
301 m_ChangedMask |= CREDENTIAL_USEHTTPPATH;
302 SetModified();
305 static int GetCredentialDefaultUrlCallback(const git_config_entry *entry, void *payload)
307 CString display = entry->level == 1 ? _T("S") : entry->level == 2 ? _T("X") : entry->level == 3 ? _T("G") : _T("L");
308 ((STRING_VECTOR *)payload)->push_back(display);
309 return 0;
312 static int GetCredentialUrlCallback(const git_config_entry *entry, void *payload)
314 CString name = CUnicodeUtils::GetUnicode(entry->name);
315 int pos1 = name.Find(_T('.'));
316 int pos2 = name.ReverseFind(_T('.'));
317 CString url = name.Mid(pos1 + 1, pos2 - pos1 - 1);
318 CString display;
319 display.Format(_T("%s:%s"), entry->level == 1 ? _T("S") : entry->level == 2 ? _T("X") : entry->level == 3 ? _T("G") : _T("L"), url);
320 ((STRING_VECTOR *)payload)->push_back(display);
321 return 0;
324 static int GetCredentialEntryCallback(const git_config_entry *entry, void *payload)
326 CString name = CUnicodeUtils::GetUnicode(entry->name);
327 ((STRING_VECTOR *)payload)->push_back(name);
328 return 0;
331 void CSettingGitCredential::AddConfigType(int &index, CString text, bool add)
333 if (add)
334 index = m_ctrlConfigType.AddString(text);
337 void CSettingGitCredential::LoadList()
339 git_config * config;
340 git_config_new(&config);
341 CStringA projectConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitLocalConfig());
342 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 4, FALSE);
343 projectConfigA.ReleaseBuffer();
344 CStringA globalConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitGlobalConfig());
345 git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 3, FALSE);
346 globalConfigA.ReleaseBuffer();
347 CStringA globalXDGConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitGlobalXDGConfig());
348 git_config_add_file_ondisk(config, globalXDGConfigA.GetBuffer(), 2, FALSE);
349 globalXDGConfigA.ReleaseBuffer();
350 CStringA systemConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitSystemConfig());
351 git_config_add_file_ondisk(config, systemConfigA.GetBuffer(), 1, FALSE);
352 systemConfigA.ReleaseBuffer();
354 STRING_VECTOR defaultList, urlList;
355 git_config_foreach_match(config, "credential\\.helper", GetCredentialDefaultUrlCallback, &defaultList);
356 git_config_foreach_match(config, "credential\\..*\\.helper", GetCredentialUrlCallback, &urlList);
357 git_config_free(config);
359 for (int i = 0; i < defaultList.size(); ++i)
360 m_ctrlUrlList.AddString(defaultList[i]);
361 for (int i = 0; i < urlList.size(); ++i)
362 m_ctrlUrlList.AddString(urlList[i]);
365 CString CSettingGitCredential::Load(CString key)
367 CString cmd;
369 if (m_strUrl.IsEmpty())
370 cmd.Format(_T("credential.%s"), key);
371 else
372 cmd.Format(_T("credential.%s.%s"), m_strUrl, key);
374 git_config * config;
375 git_config_new(&config);
376 int sel = m_ctrlConfigType.GetCurSel();
377 if (sel == ConfigType::Local)
379 CStringA projectConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitLocalConfig());
380 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 1, FALSE);
381 projectConfigA.ReleaseBuffer();
383 else if (sel == ConfigType::Global)
385 CStringA globalConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitGlobalConfig());
386 git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 2, FALSE);
387 globalConfigA.ReleaseBuffer();
388 CStringA globalXDGConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitGlobalXDGConfig());
389 git_config_add_file_ondisk(config, globalXDGConfigA.GetBuffer(), 1, FALSE);
390 globalXDGConfigA.ReleaseBuffer();
392 else if (sel == ConfigType::System)
394 CStringA systemConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitSystemConfig());
395 git_config_add_file_ondisk(config, systemConfigA.GetBuffer(), 1, FALSE);
396 systemConfigA.ReleaseBuffer();
399 CStringA cmdA = CUnicodeUtils::GetUTF8(cmd);
400 CStringA valueA;
401 const git_config_entry *entry;
402 if (!git_config_get_entry(&entry, config, cmdA))
403 valueA = CStringA(entry->value);
404 cmdA.ReleaseBuffer();
405 git_config_free(config);
407 return CUnicodeUtils::GetUnicode(valueA);
410 void CSettingGitCredential::Save(CString key, CString value)
412 CString cmd, out;
414 if (m_strUrl.IsEmpty())
415 cmd.Format(_T("credential.%s"), key);
416 else
417 cmd.Format(_T("credential.%s.%s"), m_strUrl, key);
419 int sel = m_ctrlConfigType.GetCurSel();
420 CONFIG_TYPE configLevel = sel == ConfigType::System ? CONFIG_SYSTEM : sel == ConfigType::Global ? CONFIG_GLOBAL : CONFIG_LOCAL;
422 bool old = g_Git.m_IsUseGitDLL;
423 // workaround gitdll bug
424 // TODO: switch to libgit2
425 g_Git.m_IsUseGitDLL = false;
426 if (g_Git.SetConfigValue(cmd, value, configLevel, CP_UTF8, &g_Git.m_CurrentDir))
428 CString msg;
429 msg.Format(IDS_PROC_SAVECONFIGFAILED, cmd, value);
430 CMessageBox::Show(NULL, msg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
432 if (value.IsEmpty())
434 if (g_Git.UnsetConfigValue(cmd, configLevel, CP_UTF8, &g_Git.m_CurrentDir))
436 CString msg;
437 msg.Format(IDS_PROC_SAVECONFIGFAILED, cmd, value);
438 CMessageBox::Show(NULL, msg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
441 g_Git.m_IsUseGitDLL = old;
444 BOOL CSettingGitCredential::OnApply()
446 CWaitCursor wait;
447 UpdateData();
449 int sel = m_ctrlConfigType.GetCurSel();
450 if (sel == ConfigType::System && !CAppUtils::IsAdminLogin())
452 RunUAC();
453 EndDialog(0);
454 return FALSE;
457 if (m_ChangedMask & CREDENTIAL_URL)
459 //Add Helper
460 if (m_strHelper.IsEmpty())
462 CMessageBox::Show(NULL, IDS_GITCREDENTIAL_HELPEREMPTY, IDS_APPNAME, MB_OK | MB_ICONERROR);
463 return FALSE;
465 m_strUrl.Replace(L'\\', L'/');
466 m_strHelper.Replace(L'\\', L'/');
468 Save(_T("helper"), m_strHelper);
469 m_ChangedMask &= ~CREDENTIAL_HELPER;
471 int sel = m_ctrlConfigType.GetCurSel();
472 CString prefix = sel == ConfigType::System ? _T("S") : sel == ConfigType::Global ? _T("G") : _T("L");
473 CString text;
474 if (!m_strUrl.IsEmpty())
475 text.Format(_T("%s:%s"), prefix, m_strUrl);
476 else
477 text = prefix;
478 int urlIndex = m_ctrlUrlList.AddString(text);
479 m_ctrlUrlList.SetCurSel(urlIndex);
480 GetDlgItem(IDC_BUTTON_ADD)->EnableWindow(TRUE);
483 if (m_ChangedMask & CREDENTIAL_HELPER)
485 m_strHelper.Replace(L'\\', L'/');
486 Save(_T("helper"), m_strHelper);
489 if (m_ChangedMask & CREDENTIAL_USERNAME)
490 Save(_T("username"), m_strUsername);
492 if (m_ChangedMask & CREDENTIAL_USEHTTPPATH)
493 Save(_T("useHttpPath"), m_bUseHttpPath ? _T("true") : _T(""));
495 SetModified(FALSE);
497 m_ChangedMask = 0;
498 return ISettingsPropPage::OnApply();
501 void CSettingGitCredential::OnBnClickedButtonRemove()
503 int index = m_ctrlUrlList.GetCurSel();
504 if (index >= 0)
506 CString str;
507 m_ctrlUrlList.GetText(index, str);
508 CString msg;
509 msg.Format(IDS_GITCREDENTIAL_DELETEHELPER, str);
510 if (CMessageBox::Show(NULL, msg, _T("TortoiseGit"), MB_YESNO | MB_ICONQUESTION) == IDYES)
512 git_config * config;
513 git_config_new(&config);
514 int pos = str.Find(_T(':'));
515 CString prefix = pos >= 0 ? str.Left(pos) : str;
516 CString url = pos >= 0 ? str.Mid(pos + 1) : _T("");
517 CONFIG_TYPE configLevel = CONFIG_LOCAL;
518 switch (prefix[0])
520 case _T('L'):
522 configLevel = CONFIG_LOCAL;
523 CStringA projectConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitLocalConfig());
524 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 1, FALSE);
525 projectConfigA.ReleaseBuffer();
526 break;
528 case _T('G'):
529 case _T('X'):
531 configLevel = CONFIG_GLOBAL;
532 CStringA globalConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitGlobalConfig());
533 git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 2, FALSE);
534 globalConfigA.ReleaseBuffer();
535 CStringA globalXDGConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitGlobalXDGConfig());
536 git_config_add_file_ondisk(config, globalXDGConfigA.GetBuffer(), 1, FALSE);
537 globalXDGConfigA.ReleaseBuffer();
538 break;
540 case _T('S'):
542 if (!CAppUtils::IsAdminLogin())
544 RunUAC();
545 EndDialog(0);
546 return;
548 configLevel = CONFIG_SYSTEM;
549 CStringA systemConfigA = CUnicodeUtils::GetUTF8(g_Git.GetGitSystemConfig());
550 git_config_add_file_ondisk(config, systemConfigA.GetBuffer(), 1, FALSE);
551 systemConfigA.ReleaseBuffer();
552 break;
556 STRING_VECTOR list;
557 CStringA urlA = CUnicodeUtils::GetUTF8(url);
558 CStringA pattern = urlA.IsEmpty() ? "^credential\\.[^.]+$" : ("credential\\." + RegexEscape(urlA) + "\\..*");
559 git_config_foreach_match(config, pattern, GetCredentialEntryCallback, &list);
560 for (size_t i = 0; i < list.size(); ++i)
561 g_Git.UnsetConfigValue(list[i], configLevel, CP_UTF8, &g_Git.m_CurrentDir);
563 m_ctrlUrlList.DeleteString(index);
564 OnLbnSelchangeListUrl();