1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2020 - 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 // SettingGitRemote.cpp : implementation file
23 #include "TortoiseProc.h"
24 #include "SettingGitRemote.h"
26 #include "GitAdminDir.h"
27 #include "MessageBox.h"
30 #include "HistoryCombo.h"
32 // CSettingGitRemote dialog
34 IMPLEMENT_DYNAMIC(CSettingGitRemote
, ISettingsPropPage
)
36 CSettingGitRemote::CSettingGitRemote()
37 : ISettingsPropPage(CSettingGitRemote::IDD
)
41 , m_bPushDefault(FALSE
)
46 CSettingGitRemote::~CSettingGitRemote()
50 void CSettingGitRemote::DoDataExchange(CDataExchange
* pDX
)
52 CPropertyPage::DoDataExchange(pDX
);
53 DDX_Control(pDX
, IDC_LIST_REMOTE
, m_ctrlRemoteList
);
54 DDX_Text(pDX
, IDC_EDIT_REMOTE
, m_strRemote
);
55 DDX_Text(pDX
, IDC_EDIT_URL
, m_strUrl
);
56 DDX_Text(pDX
, IDC_EDIT_PUSHURL
, m_strPushUrl
);
57 DDX_Text(pDX
, IDC_EDIT_PUTTY_KEY
, m_strPuttyKeyfile
);
58 DDX_Control(pDX
, IDC_COMBO_TAGOPT
, m_ctrlTagOpt
);
59 DDX_Check(pDX
, IDC_CHECK_PRUNE
, m_bPrune
);
60 DDX_Check(pDX
, IDC_CHECK_PRUNEALL
, m_bPruneAll
);
61 DDX_Check(pDX
, IDC_CHECK_PUSHDEFAULT
, m_bPushDefault
);
64 BEGIN_MESSAGE_MAP(CSettingGitRemote
, CPropertyPage
)
66 ON_BN_CLICKED(IDC_BUTTON_BROWSE
, &CSettingGitRemote::OnBnClickedButtonBrowse
)
67 ON_BN_CLICKED(IDC_BUTTON_ADD
, &CSettingGitRemote::OnBnClickedButtonAdd
)
68 ON_LBN_SELCHANGE(IDC_LIST_REMOTE
, &CSettingGitRemote::OnLbnSelchangeListRemote
)
69 ON_EN_CHANGE(IDC_EDIT_REMOTE
, &CSettingGitRemote::OnEnChangeEditRemote
)
70 ON_EN_CHANGE(IDC_EDIT_URL
, &CSettingGitRemote::OnEnChangeEditUrl
)
71 ON_EN_CHANGE(IDC_EDIT_PUSHURL
, &CSettingGitRemote::OnEnChangeEditPushUrl
)
72 ON_EN_CHANGE(IDC_EDIT_PUTTY_KEY
, &CSettingGitRemote::OnEnChangeEditPuttyKey
)
73 ON_CBN_SELCHANGE(IDC_COMBO_TAGOPT
, &CSettingGitRemote::OnCbnSelchangeComboTagOpt
)
74 ON_BN_CLICKED(IDC_CHECK_PRUNE
, &CSettingGitRemote::OnBnClickedCheckprune
)
75 ON_BN_CLICKED(IDC_CHECK_PRUNEALL
, &CSettingGitRemote::OnBnClickedCheckpruneall
)
76 ON_BN_CLICKED(IDC_CHECK_PUSHDEFAULT
, &CSettingGitRemote::OnBnClickedCheckpushdefault
)
77 ON_BN_CLICKED(IDC_BUTTON_REMOVE
, &CSettingGitRemote::OnBnClickedButtonRemove
)
78 ON_BN_CLICKED(IDC_BUTTON_RENAME_REMOTE
, &CSettingGitRemote::OnBnClickedButtonRenameRemote
)
81 static void ShowEditBalloon(HWND hDlg
, UINT nIdControl
, UINT nIdText
, UINT nIdTitle
, int nIcon
= TTI_WARNING
)
83 CString
text(MAKEINTRESOURCE(nIdText
));
84 CString
title(MAKEINTRESOURCE(nIdTitle
));
86 bt
.cbStruct
= sizeof(bt
);
90 SendDlgItemMessage(hDlg
, nIdControl
, EM_SHOWBALLOONTIP
, 0, reinterpret_cast<LPARAM
>(&bt
));
93 #define TIMER_PREFILL 1
95 BOOL
CSettingGitRemote::OnInitDialog()
97 ISettingsPropPage::OnInitDialog();
99 AdjustControlSize(IDC_CHECK_PRUNE
);
100 AdjustControlSize(IDC_CHECK_PRUNEALL
);
101 AdjustControlSize(IDC_CHECK_PUSHDEFAULT
);
103 STRING_VECTOR remotes
;
104 g_Git
.GetRemoteList(remotes
);
105 m_ctrlRemoteList
.ResetContent();
106 for (size_t i
= 0; i
< remotes
.size(); i
++)
107 m_ctrlRemoteList
.AddString(remotes
[i
]);
109 m_ctrlTagOpt
.AddString(CString(MAKEINTRESOURCE(IDS_FETCH_REACHABLE
)));
110 m_ctrlTagOpt
.AddString(CString(MAKEINTRESOURCE(IDS_NONE
)));
111 m_ctrlTagOpt
.AddString(CString(MAKEINTRESOURCE(IDS_ALL
)));
112 m_ctrlTagOpt
.SetCurSel(0);
114 CString pruneAll
= g_Git
.GetConfigValue(L
"fetch.prune");
115 m_bPruneAll
= pruneAll
== L
"true" ? TRUE
: FALSE
;
119 tmp
.Format(IDS_GITCONFIG_SETTING
, L
"remote.pushdefault");
120 m_tooltips
.AddTool(IDC_CHECK_PUSHDEFAULT
, tmp
);
121 tmp
.Format(IDS_GITCONFIG_SETTING
, L
"remote.<name>.prune");
122 m_tooltips
.AddTool(IDC_CHECK_PRUNE
, tmp
);
123 tmp
.Format(IDS_GITCONFIG_SETTING
, L
"fetch.prune");
124 m_tooltips
.AddTool(IDC_CHECK_PRUNEALL
, tmp
);
125 tmp
.Format(IDS_GITCONFIG_SETTING
, L
"remote<name>.tagopt");
126 m_tooltips
.AddTool(IDC_COMBO_TAGOPT
, tmp
);
129 //this->GetDlgItem(IDC_EDIT_REMOTE)->EnableWindow(FALSE);
130 this->UpdateData(FALSE
);
132 SetTimer(TIMER_PREFILL
, 1000, nullptr);
135 // CSettingGitRemote message handlers
137 void CSettingGitRemote::OnTimer(UINT_PTR nIDEvent
)
139 if (nIDEvent
== TIMER_PREFILL
)
141 if (m_strRemote
.IsEmpty() && m_ctrlRemoteList
.GetCount() == 0)
142 ShowEditBalloon(IDC_EDIT_URL
, IDS_B_T_PREFILL_ORIGIN
, IDS_HINT
, TTI_INFO
);
144 KillTimer(TIMER_PREFILL
);
148 void CSettingGitRemote::OnBnClickedButtonBrowse()
151 CString filename
= m_strPuttyKeyfile
;
152 if (!PathFileExists(filename
))
154 if (!CAppUtils::FileOpenSave(filename
, nullptr, 0, IDS_PUTTYKEYFILEFILTER
, true, GetSafeHwnd()))
157 m_strPuttyKeyfile
= filename
;
159 OnEnChangeEditPuttyKey();
162 void CSettingGitRemote::OnBnClickedButtonAdd()
166 if(m_strRemote
.IsEmpty())
168 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_REMOTEEMPTY
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
171 if(m_strUrl
.IsEmpty())
173 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_URLEMPTY
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
177 m_ChangedMask
= REMOTE_NAME
| REMOTE_URL
| REMOTE_PUTTYKEY
| REMOTE_TAGOPT
| REMOTE_PRUNE
| REMOTE_PRUNEALL
| REMOTE_PUSHDEFAULT
| REMOTE_PUSHURL
;
178 if(IsRemoteExist(m_strRemote
))
181 msg
.Format(IDS_PROC_GITCONFIG_OVERWRITEREMOTE
, static_cast<LPCTSTR
>(m_strRemote
));
182 if (CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
| MB_DEFBUTTON2
) == IDYES
)
183 m_ChangedMask
&= ~REMOTE_NAME
;
191 BOOL
CSettingGitRemote::IsRemoteExist(const CString
& remote
)
194 for(int i
=0;i
<m_ctrlRemoteList
.GetCount();i
++)
196 m_ctrlRemoteList
.GetText(i
,str
);
203 struct CheckRefspecStruct
209 static int CheckRemoteCollideWithRefspec(const git_config_entry
*entry
, void * payload
)
211 auto crs
= reinterpret_cast<CheckRefspecStruct
*>(payload
);
213 if (entry
->name
== "remote." + crs
->remote
+ ".fetch")
215 CStringA value
= CStringA(entry
->value
);
216 CStringA match
= ":refs/remotes/" + crs
->remote
;
217 int pos
= value
.Find(match
);
220 if ((pos
+ match
.GetLength() == value
.GetLength()) || (value
[pos
+ match
.GetLength()] == '/'))
228 bool CSettingGitRemote::IsRemoteCollideWithRefspec(CString remote
)
230 CheckRefspecStruct crs
= { CUnicodeUtils::GetUTF8(remote
), false };
231 CAutoConfig
config(true);
232 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(g_Git
.GetGitLocalConfig()), GIT_CONFIG_LEVEL_LOCAL
, g_Git
.GetGitRepository(), FALSE
);
233 for (const auto pattern
: { "remote\\..*\\.fetch", "svn-remote\\..*\\.fetch", "svn-remote\\..*\\.branches", "svn-remote\\..*\\.tags" })
235 git_config_foreach_match(config
, pattern
, CheckRemoteCollideWithRefspec
, &crs
);
242 void CSettingGitRemote::OnLbnSelchangeListRemote()
248 if (CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_SAVEREMOTE
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_SAVEBUTTON
, IDS_DISCARDBUTTON
) == 1)
253 int index
= this->m_ctrlRemoteList
.GetCurSel();
257 m_strPushUrl
.Empty();
259 m_strPuttyKeyfile
.Empty();
260 this->UpdateData(FALSE
);
263 m_ctrlRemoteList
.GetText(index
, m_strRemote
);
266 cmd
.Format(L
"remote.%s.url", static_cast<LPCTSTR
>(m_strRemote
));
267 m_strUrl
= g_Git
.GetConfigValue(cmd
);
269 cmd
.Format(L
"remote.%s.pushurl", static_cast<LPCTSTR
>(m_strRemote
));
270 m_strPushUrl
= g_Git
.GetConfigValue(cmd
);
272 cmd
.Format(L
"remote.%s.puttykeyfile", static_cast<LPCTSTR
>(m_strRemote
));
274 this->m_strPuttyKeyfile
= g_Git
.GetConfigValue(cmd
);
279 cmd
.Format(L
"remote.%s.tagopt", static_cast<LPCTSTR
>(m_strRemote
));
280 CString tagopt
= g_Git
.GetConfigValue(cmd
);
282 if (tagopt
== "--no-tags")
284 else if (tagopt
== "--tags")
286 m_ctrlTagOpt
.SetCurSel(index
);
288 CString pushDefault
= g_Git
.GetConfigValue(L
"remote.pushdefault");
289 m_bPushDefault
= pushDefault
== m_strRemote
? TRUE
: FALSE
;
290 cmd
.Format(L
"remote.%s.prune", static_cast<LPCTSTR
>(m_strRemote
));
291 CString prune
= g_Git
.GetConfigValue(cmd
);
292 m_bPrune
= prune
== L
"true" ? TRUE
: prune
== L
"false" ? FALSE
: 2;
293 CString pruneAll
= g_Git
.GetConfigValue(L
"fetch.prune");
294 m_bPruneAll
= pruneAll
== L
"true" ? TRUE
: FALSE
;
296 GetDlgItem(IDC_BUTTON_ADD
)->EnableWindow(TRUE
);
297 GetDlgItem(IDC_BUTTON_REMOVE
)->EnableWindow(TRUE
);
298 GetDlgItem(IDC_BUTTON_RENAME_REMOTE
)->EnableWindow(TRUE
);
299 this->UpdateData(FALSE
);
302 void CSettingGitRemote::OnEnChangeEditRemote()
304 m_ChangedMask
|=REMOTE_NAME
;
308 if (IsRemoteCollideWithRefspec(m_strRemote
))
309 ShowEditBalloon(IDC_EDIT_REMOTE
, IDS_B_T_REMOTE_NAME_COLLIDE
, IDS_HINT
, TTI_WARNING
);
310 if( (!this->m_strRemote
.IsEmpty())&&(!this->m_strUrl
.IsEmpty()) )
314 void CSettingGitRemote::OnEnChangeEditUrl()
316 m_ChangedMask
|=REMOTE_URL
;
320 if (m_strRemote
.IsEmpty() && !m_strUrl
.IsEmpty() && m_ctrlRemoteList
.GetCount() == 0)
322 GetDlgItem(IDC_EDIT_REMOTE
)->SetWindowText(L
"origin");
323 OnEnChangeEditRemote();
326 if( (!this->m_strRemote
.IsEmpty())&&(!this->m_strUrl
.IsEmpty()) )
330 void CSettingGitRemote::OnEnChangeEditPushUrl()
332 m_ChangedMask
|= REMOTE_PUSHURL
;
336 if (!this->m_strRemote
.IsEmpty())
340 void CSettingGitRemote::OnEnChangeEditPuttyKey()
342 m_ChangedMask
|=REMOTE_PUTTYKEY
;
345 if (!this->m_strUrl
.IsEmpty())
349 void CSettingGitRemote::OnCbnSelchangeComboTagOpt()
351 m_ChangedMask
|= REMOTE_TAGOPT
;
357 void CSettingGitRemote::OnBnClickedCheckprune()
359 m_ChangedMask
|= REMOTE_PRUNE
;
365 void CSettingGitRemote::OnBnClickedCheckpruneall()
367 m_ChangedMask
|= REMOTE_PRUNEALL
;
372 void CSettingGitRemote::OnBnClickedCheckpushdefault()
374 m_ChangedMask
|= REMOTE_PUSHDEFAULT
;
379 BOOL
CSettingGitRemote::Save(CString key
,CString value
)
383 cmd
.Format(L
"remote.%s.%s", static_cast<LPCTSTR
>(m_strRemote
), static_cast<LPCTSTR
>(key
));
386 // don't check result code. it fails if the entry not exist
387 g_Git
.UnsetConfigValue(cmd
, CONFIG_LOCAL
);
388 if (!g_Git
.GetConfigValue(cmd
).IsEmpty())
391 msg
.FormatMessage(IDS_PROC_SAVECONFIGFAILED
, static_cast<LPCTSTR
>(cmd
), static_cast<LPCTSTR
>(value
));
392 CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
398 if (g_Git
.SetConfigValue(cmd
, value
, CONFIG_LOCAL
))
401 msg
.FormatMessage(IDS_PROC_SAVECONFIGFAILED
, static_cast<LPCTSTR
>(cmd
), static_cast<LPCTSTR
>(value
));
402 CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
408 BOOL
CSettingGitRemote::SaveGeneral(CString key
, CString value
)
412 // don't check result code. it fails if the entry not exist
413 g_Git
.UnsetConfigValue(key
, CONFIG_LOCAL
);
414 if (!g_Git
.GetConfigValue(key
).IsEmpty())
417 msg
.FormatMessage(IDS_PROC_SAVECONFIGFAILED
, static_cast<LPCTSTR
>(key
), static_cast<LPCTSTR
>(value
));
418 CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
424 if (g_Git
.SetConfigValue(key
, value
, CONFIG_LOCAL
))
427 msg
.FormatMessage(IDS_PROC_SAVECONFIGFAILED
, static_cast<LPCTSTR
>(key
), static_cast<LPCTSTR
>(value
));
428 CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
435 BOOL
CSettingGitRemote::OnApply()
440 if (m_ChangedMask
& REMOTE_PUSHDEFAULT
)
442 if (!m_strRemote
.Trim().IsEmpty() && m_bPushDefault
)
444 if (!SaveGeneral(L
"remote.pushdefault", m_strRemote
.Trim()))
449 if (!SaveGeneral(L
"remote.pushdefault", L
""))
453 m_ChangedMask
&= ~REMOTE_PUSHDEFAULT
;
456 if (m_ChangedMask
& REMOTE_PRUNEALL
)
458 if (!SaveGeneral(L
"fetch.prune", m_bPruneAll
== TRUE
? L
"true" : L
"false"))
460 m_ChangedMask
&= ~REMOTE_PRUNEALL
;
463 if (m_ChangedMask
&& m_strRemote
.Trim().IsEmpty())
465 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_REMOTEEMPTY
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
469 if(m_ChangedMask
& REMOTE_NAME
)
472 if(m_strRemote
.IsEmpty())
474 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_REMOTEEMPTY
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
477 if(m_strUrl
.IsEmpty())
479 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_URLEMPTY
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
483 if (m_ctrlRemoteList
.GetCount() > 0)
485 // tagopt not --no-tags
486 if (m_ctrlTagOpt
.GetCurSel() != 1)
488 if (CMessageBox::ShowCheck(GetSafeHwnd(), IDS_PROC_GITCONFIG_ASKTAGOPT
, IDS_APPNAME
, MB_YESNO
| MB_ICONQUESTION
, L
"TagOptNoTagsWarning", IDS_MSGBOX_DONOTSHOWAGAIN
) == IDYES
)
490 m_ctrlTagOpt
.SetCurSel(1);
491 m_ChangedMask
|= REMOTE_TAGOPT
;
496 m_strUrl
.Replace(L
'\\', L
'/');
498 cmd
.Format(L
"git.exe remote add \"%s\" \"%s\"", static_cast<LPCTSTR
>(m_strRemote
), static_cast<LPCTSTR
>(m_strUrl
));
499 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
501 CMessageBox::Show(GetSafeHwnd(), out
, L
"TorotiseGit", MB_OK
| MB_ICONERROR
);
504 m_ChangedMask
&= ~REMOTE_URL
;
506 m_ctrlRemoteList
.SetCurSel(m_ctrlRemoteList
.AddString(m_strRemote
));
507 GetDlgItem(IDC_BUTTON_ADD
)->EnableWindow(TRUE
);
508 GetDlgItem(IDC_BUTTON_RENAME_REMOTE
)->EnableWindow(TRUE
);
509 if (!m_bNoFetch
&& CMessageBox::Show(GetSafeHwnd(), IDS_SETTINGS_FETCH_ADDEDREMOTE
, IDS_APPNAME
, MB_ICONQUESTION
| MB_YESNO
) == IDYES
)
510 CCommonAppUtils::RunTortoiseGitProc(L
"/command:fetch /path:\"" + g_Git
.m_CurrentDir
+ L
"\" /remote:\"" + m_strRemote
+ L
'"');
512 if(m_ChangedMask
& REMOTE_URL
)
514 m_strUrl
.Replace(L
'\\', L
'/');
515 if (!Save(L
"url", m_strUrl
))
519 if(m_ChangedMask
& REMOTE_PUTTYKEY
)
521 if (!Save(L
"puttykeyfile", m_strPuttyKeyfile
))
525 if (m_ChangedMask
& REMOTE_TAGOPT
)
528 int index
= m_ctrlTagOpt
.GetCurSel();
530 tagopt
= "--no-tags";
533 if (!Save(L
"tagopt", tagopt
))
537 if (m_ChangedMask
& REMOTE_PRUNE
)
539 if (!Save(L
"prune", m_bPrune
== TRUE
? L
"true" : m_bPrune
== FALSE
? L
"false" : L
""))
543 if (m_ChangedMask
& REMOTE_PUSHURL
)
545 m_strPushUrl
.Replace(L
'\\', L
'/');
546 if (!Save(L
"pushurl", m_strPushUrl
))
553 return ISettingsPropPage::OnApply();
556 void CleanupSyncRemotes(const CString
& remote
)
558 CString workingDir
= g_Git
.m_CurrentDir
;
559 workingDir
.Replace(L
':', L
'_');
560 CHistoryCombo::RemoveEntryFromHistory(L
"Software\\TortoiseGit\\History\\SyncURL\\" + workingDir
, L
"url", remote
);
563 void CSettingGitRemote::OnBnClickedButtonRemove()
565 int index
= m_ctrlRemoteList
.GetCurSel();
569 m_ctrlRemoteList
.GetText(index
,str
);
571 msg
.Format(IDS_WARN_REMOVE
, static_cast<LPCTSTR
>(str
));
572 if (CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) == IDYES
)
575 cmd
.Format(L
"git.exe remote rm %s", static_cast<LPCTSTR
>(str
));
576 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
578 CMessageBox::Show(GetSafeHwnd(), out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
582 CleanupSyncRemotes(str
);
583 m_ctrlRemoteList
.DeleteString(index
);
584 OnLbnSelchangeListRemote();
589 void CSettingGitRemote::OnBnClickedButtonRenameRemote()
591 int sel
= m_ctrlRemoteList
.GetCurSel();
594 CString oldRemote
, newRemote
;
595 m_ctrlRemoteList
.GetText(sel
, oldRemote
);
596 GetDlgItem(IDC_EDIT_REMOTE
)->GetWindowText(newRemote
);
598 cmd
.Format(L
"git.exe remote rename %s %s", static_cast<LPCTSTR
>(oldRemote
), static_cast<LPCTSTR
>(newRemote
));
599 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
601 CMessageBox::Show(GetSafeHwnd(), out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
605 CleanupSyncRemotes(oldRemote
);
606 m_ctrlRemoteList
.DeleteString(sel
);
607 m_ctrlRemoteList
.SetCurSel(m_ctrlRemoteList
.AddString(newRemote
));
608 m_ChangedMask
&= ~REMOTE_NAME
;
610 this->SetModified(FALSE
);