1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2021, 2023-2024 - 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
)
40 , m_bPushDefault(FALSE
)
44 CSettingGitRemote::~CSettingGitRemote()
48 void CSettingGitRemote::DoDataExchange(CDataExchange
* pDX
)
50 CPropertyPage::DoDataExchange(pDX
);
51 DDX_Control(pDX
, IDC_LIST_REMOTE
, m_ctrlRemoteList
);
52 DDX_Text(pDX
, IDC_EDIT_REMOTE
, m_strRemote
);
53 DDX_Text(pDX
, IDC_EDIT_URL
, m_strUrl
);
54 DDX_Text(pDX
, IDC_EDIT_PUSHURL
, m_strPushUrl
);
55 DDX_Text(pDX
, IDC_EDIT_PUTTY_KEY
, m_strPuttyKeyfile
);
56 DDX_Control(pDX
, IDC_COMBO_TAGOPT
, m_ctrlTagOpt
);
57 DDX_Check(pDX
, IDC_CHECK_PRUNE
, m_bPrune
);
58 DDX_Check(pDX
, IDC_CHECK_PUSHDEFAULT
, m_bPushDefault
);
61 BEGIN_MESSAGE_MAP(CSettingGitRemote
, CPropertyPage
)
63 ON_BN_CLICKED(IDC_BUTTON_BROWSE
, &CSettingGitRemote::OnBnClickedButtonBrowse
)
64 ON_BN_CLICKED(IDC_BUTTON_ADD
, &CSettingGitRemote::OnBnClickedButtonAdd
)
65 ON_LBN_SELCHANGE(IDC_LIST_REMOTE
, &CSettingGitRemote::OnLbnSelchangeListRemote
)
66 ON_EN_CHANGE(IDC_EDIT_REMOTE
, &CSettingGitRemote::OnEnChangeEditRemote
)
67 ON_EN_CHANGE(IDC_EDIT_URL
, &CSettingGitRemote::OnEnChangeEditUrl
)
68 ON_EN_CHANGE(IDC_EDIT_PUSHURL
, &CSettingGitRemote::OnEnChangeEditPushUrl
)
69 ON_EN_CHANGE(IDC_EDIT_PUTTY_KEY
, &CSettingGitRemote::OnEnChangeEditPuttyKey
)
70 ON_CBN_SELCHANGE(IDC_COMBO_TAGOPT
, &CSettingGitRemote::OnCbnSelchangeComboTagOpt
)
71 ON_BN_CLICKED(IDC_CHECK_PRUNE
, &CSettingGitRemote::OnBnClickedCheckprune
)
72 ON_BN_CLICKED(IDC_CHECK_PUSHDEFAULT
, &CSettingGitRemote::OnBnClickedCheckpushdefault
)
73 ON_BN_CLICKED(IDC_BUTTON_REMOVE
, &CSettingGitRemote::OnBnClickedButtonRemove
)
74 ON_BN_CLICKED(IDC_BUTTON_RENAME_REMOTE
, &CSettingGitRemote::OnBnClickedButtonRenameRemote
)
77 static void ShowEditBalloon(HWND hDlg
, UINT nIdControl
, UINT nIdText
, UINT nIdTitle
, int nIcon
= TTI_WARNING
)
79 CString
text(MAKEINTRESOURCE(nIdText
));
80 CString
title(MAKEINTRESOURCE(nIdTitle
));
82 bt
.cbStruct
= sizeof(bt
);
86 SendDlgItemMessage(hDlg
, nIdControl
, EM_SHOWBALLOONTIP
, 0, reinterpret_cast<LPARAM
>(&bt
));
89 #define TIMER_PREFILL 1
91 BOOL
CSettingGitRemote::OnInitDialog()
93 ISettingsPropPage::OnInitDialog();
95 AdjustControlSize(IDC_CHECK_PRUNE
);
96 AdjustControlSize(IDC_CHECK_PUSHDEFAULT
);
98 STRING_VECTOR remotes
;
99 if (g_Git
.GetRemoteList(remotes
) != 0)
100 MessageBox(g_Git
.GetGitLastErr(L
"Could not load remotes."), L
"TortoiseGit", MB_ICONERROR
);
102 m_ctrlRemoteList
.ResetContent();
103 for (size_t i
= 0; i
< remotes
.size(); i
++)
104 m_ctrlRemoteList
.AddString(remotes
[i
]);
106 m_ctrlTagOpt
.AddString(CString(MAKEINTRESOURCE(IDS_FETCH_REACHABLE
)));
107 m_ctrlTagOpt
.AddString(CString(MAKEINTRESOURCE(IDS_NONE
)));
108 m_ctrlTagOpt
.AddString(CString(MAKEINTRESOURCE(IDS_ALL
)));
109 m_ctrlTagOpt
.SetCurSel(0);
113 tmp
.Format(IDS_GITCONFIG_SETTING
, L
"remote.pushdefault");
114 m_tooltips
.AddTool(IDC_CHECK_PUSHDEFAULT
, tmp
);
115 tmp
.Format(IDS_GITCONFIG_SETTING
, L
"remote.<name>.prune");
117 guide
.LoadString(IDS_PRUNE_OPTION_GUIDE
);
118 tmp
+= L
"\n" + guide
;
119 m_tooltips
.AddTool(IDC_CHECK_PRUNE
, tmp
);
120 tmp
.Format(IDS_GITCONFIG_SETTING
, L
"remote<name>.tagopt");
121 m_tooltips
.AddTool(IDC_COMBO_TAGOPT
, tmp
);
124 //this->GetDlgItem(IDC_EDIT_REMOTE)->EnableWindow(FALSE);
125 this->UpdateData(FALSE
);
127 SetTimer(TIMER_PREFILL
, 1000, nullptr);
130 // CSettingGitRemote message handlers
132 void CSettingGitRemote::OnTimer(UINT_PTR nIDEvent
)
134 if (nIDEvent
== TIMER_PREFILL
)
136 if (m_strRemote
.IsEmpty() && m_ctrlRemoteList
.GetCount() == 0)
137 ShowEditBalloon(IDC_EDIT_URL
, IDS_B_T_PREFILL_ORIGIN
, IDS_HINT
, TTI_INFO
);
139 KillTimer(TIMER_PREFILL
);
143 void CSettingGitRemote::OnBnClickedButtonBrowse()
146 CString filename
= m_strPuttyKeyfile
;
147 if (!PathFileExists(filename
))
149 if (!CAppUtils::FileOpenSave(filename
, nullptr, 0, IDS_PUTTYKEYFILEFILTER
, true, GetSafeHwnd()))
152 m_strPuttyKeyfile
= filename
;
154 OnEnChangeEditPuttyKey();
157 void CSettingGitRemote::OnBnClickedButtonAdd()
161 if(m_strRemote
.IsEmpty())
163 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_REMOTEEMPTY
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
166 if(m_strUrl
.IsEmpty())
168 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_URLEMPTY
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
172 m_ChangedMask
= REMOTE_NAME
| REMOTE_URL
| REMOTE_PUTTYKEY
| REMOTE_TAGOPT
| REMOTE_PRUNE
| REMOTE_PUSHDEFAULT
| REMOTE_PUSHURL
;
173 if(IsRemoteExist(m_strRemote
))
176 msg
.Format(IDS_PROC_GITCONFIG_OVERWRITEREMOTE
, static_cast<LPCWSTR
>(m_strRemote
));
177 if (CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
| MB_DEFBUTTON2
) == IDYES
)
178 m_ChangedMask
&= ~REMOTE_NAME
;
186 BOOL
CSettingGitRemote::IsRemoteExist(const CString
& remote
)
189 for(int i
=0;i
<m_ctrlRemoteList
.GetCount();i
++)
191 m_ctrlRemoteList
.GetText(i
,str
);
198 struct CheckRefspecStruct
204 static int CheckRemoteCollideWithRefspec(const git_config_entry
*entry
, void * payload
)
206 auto crs
= reinterpret_cast<CheckRefspecStruct
*>(payload
);
208 if ("remote." + crs
->remote
+ ".fetch" == entry
->name
)
210 CStringA value
= CStringA(entry
->value
);
211 CStringA match
= ":refs/remotes/" + crs
->remote
;
212 int pos
= value
.Find(match
);
215 if ((pos
+ match
.GetLength() == value
.GetLength()) || (value
[pos
+ match
.GetLength()] == '/'))
223 bool CSettingGitRemote::IsRemoteCollideWithRefspec(CString remote
)
225 CheckRefspecStruct crs
= { CUnicodeUtils::GetUTF8(remote
), false };
226 CAutoConfig
config(true);
227 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(g_Git
.GetGitLocalConfig()), GIT_CONFIG_LEVEL_LOCAL
, g_Git
.GetGitRepository(), FALSE
);
228 for (const auto pattern
: { "remote\\..*\\.fetch", "svn-remote\\..*\\.fetch", "svn-remote\\..*\\.branches", "svn-remote\\..*\\.tags" })
230 git_config_foreach_match(config
, pattern
, CheckRemoteCollideWithRefspec
, &crs
);
237 void CSettingGitRemote::OnLbnSelchangeListRemote()
243 if (CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_SAVEREMOTE
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_SAVEBUTTON
, IDS_DISCARDBUTTON
) == 1)
248 int index
= this->m_ctrlRemoteList
.GetCurSel();
252 m_strPushUrl
.Empty();
254 m_strPuttyKeyfile
.Empty();
255 this->UpdateData(FALSE
);
258 m_ctrlRemoteList
.GetText(index
, m_strRemote
);
261 cmd
.Format(L
"remote.%s.url", static_cast<LPCWSTR
>(m_strRemote
));
262 m_strUrl
= g_Git
.GetConfigValue(cmd
);
264 cmd
.Format(L
"remote.%s.pushurl", static_cast<LPCWSTR
>(m_strRemote
));
265 m_strPushUrl
= g_Git
.GetConfigValue(cmd
);
267 cmd
.Format(L
"remote.%s.puttykeyfile", static_cast<LPCWSTR
>(m_strRemote
));
269 this->m_strPuttyKeyfile
= g_Git
.GetConfigValue(cmd
);
274 cmd
.Format(L
"remote.%s.tagopt", static_cast<LPCWSTR
>(m_strRemote
));
275 CString tagopt
= g_Git
.GetConfigValue(cmd
);
277 if (tagopt
== "--no-tags")
279 else if (tagopt
== "--tags")
281 m_ctrlTagOpt
.SetCurSel(index
);
283 CString pushDefault
= g_Git
.GetConfigValue(L
"remote.pushdefault");
284 m_bPushDefault
= pushDefault
== m_strRemote
? TRUE
: FALSE
;
285 cmd
.Format(L
"remote.%s.prune", static_cast<LPCWSTR
>(m_strRemote
));
286 CString prune
= g_Git
.GetConfigValue(cmd
);
287 m_bPrune
= prune
== L
"true" ? TRUE
: prune
== L
"false" ? FALSE
: 2;
289 GetDlgItem(IDC_BUTTON_ADD
)->EnableWindow(TRUE
);
290 GetDlgItem(IDC_BUTTON_REMOVE
)->EnableWindow(TRUE
);
291 GetDlgItem(IDC_BUTTON_RENAME_REMOTE
)->EnableWindow(TRUE
);
292 this->UpdateData(FALSE
);
295 void CSettingGitRemote::OnEnChangeEditRemote()
297 m_ChangedMask
|=REMOTE_NAME
;
301 if (IsRemoteCollideWithRefspec(m_strRemote
))
302 ShowEditBalloon(IDC_EDIT_REMOTE
, IDS_B_T_REMOTE_NAME_COLLIDE
, IDS_HINT
, TTI_WARNING
);
303 if( (!this->m_strRemote
.IsEmpty())&&(!this->m_strUrl
.IsEmpty()) )
307 void CSettingGitRemote::OnEnChangeEditUrl()
309 m_ChangedMask
|=REMOTE_URL
;
313 if (m_strRemote
.IsEmpty() && !m_strUrl
.IsEmpty() && m_ctrlRemoteList
.GetCount() == 0)
315 GetDlgItem(IDC_EDIT_REMOTE
)->SetWindowText(L
"origin");
316 OnEnChangeEditRemote();
319 if( (!this->m_strRemote
.IsEmpty())&&(!this->m_strUrl
.IsEmpty()) )
323 void CSettingGitRemote::OnEnChangeEditPushUrl()
325 m_ChangedMask
|= REMOTE_PUSHURL
;
329 if (!this->m_strRemote
.IsEmpty())
333 void CSettingGitRemote::OnEnChangeEditPuttyKey()
335 m_ChangedMask
|=REMOTE_PUTTYKEY
;
338 if (!this->m_strUrl
.IsEmpty())
342 void CSettingGitRemote::OnCbnSelchangeComboTagOpt()
344 m_ChangedMask
|= REMOTE_TAGOPT
;
350 void CSettingGitRemote::OnBnClickedCheckprune()
352 m_ChangedMask
|= REMOTE_PRUNE
;
358 void CSettingGitRemote::OnBnClickedCheckpushdefault()
360 m_ChangedMask
|= REMOTE_PUSHDEFAULT
;
365 BOOL
CSettingGitRemote::Save(CString key
,CString value
)
369 cmd
.Format(L
"remote.%s.%s", static_cast<LPCWSTR
>(m_strRemote
), static_cast<LPCWSTR
>(key
));
372 // don't check result code. it fails if the entry not exist
373 g_Git
.UnsetConfigValue(cmd
, CONFIG_LOCAL
);
374 if (!g_Git
.GetConfigValue(cmd
).IsEmpty())
377 msg
.FormatMessage(IDS_PROC_SAVECONFIGFAILED
, static_cast<LPCWSTR
>(cmd
), static_cast<LPCWSTR
>(value
));
378 CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
384 if (g_Git
.SetConfigValue(cmd
, value
, CONFIG_LOCAL
))
387 msg
.FormatMessage(IDS_PROC_SAVECONFIGFAILED
, static_cast<LPCWSTR
>(cmd
), static_cast<LPCWSTR
>(value
));
388 CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
394 BOOL
CSettingGitRemote::SaveGeneral(CString key
, CString value
)
398 // don't check result code. it fails if the entry not exist
399 g_Git
.UnsetConfigValue(key
, CONFIG_LOCAL
);
400 if (!g_Git
.GetConfigValue(key
).IsEmpty())
403 msg
.FormatMessage(IDS_PROC_SAVECONFIGFAILED
, static_cast<LPCWSTR
>(key
), static_cast<LPCWSTR
>(value
));
404 CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
410 if (g_Git
.SetConfigValue(key
, value
, CONFIG_LOCAL
))
413 msg
.FormatMessage(IDS_PROC_SAVECONFIGFAILED
, static_cast<LPCWSTR
>(key
), static_cast<LPCWSTR
>(value
));
414 CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
421 BOOL
CSettingGitRemote::OnApply()
426 if (m_ChangedMask
& REMOTE_PUSHDEFAULT
)
428 if (!m_strRemote
.Trim().IsEmpty() && m_bPushDefault
)
430 if (!SaveGeneral(L
"remote.pushdefault", m_strRemote
.Trim()))
433 if (!m_bPushDefault
&& m_strRemote
.Trim() == g_Git
.GetConfigValue(L
"remote.pushdefault"))
435 if (!SaveGeneral(L
"remote.pushdefault", L
""))
439 m_ChangedMask
&= ~REMOTE_PUSHDEFAULT
;
442 if (m_ChangedMask
&& m_strRemote
.Trim().IsEmpty())
444 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_REMOTEEMPTY
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
448 if(m_ChangedMask
& REMOTE_NAME
)
451 if(m_strRemote
.IsEmpty())
453 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_REMOTEEMPTY
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
456 if(m_strUrl
.IsEmpty())
458 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_GITCONFIG_URLEMPTY
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
462 if (m_ctrlRemoteList
.GetCount() > 0)
464 // tagopt not --no-tags
465 if (m_ctrlTagOpt
.GetCurSel() != 1)
467 if (CMessageBox::ShowCheck(GetSafeHwnd(), IDS_PROC_GITCONFIG_ASKTAGOPT
, IDS_APPNAME
, MB_YESNO
| MB_ICONQUESTION
, L
"TagOptNoTagsWarning", IDS_MSGBOX_DONOTSHOWAGAIN
) == IDYES
)
469 m_ctrlTagOpt
.SetCurSel(1);
470 m_ChangedMask
|= REMOTE_TAGOPT
;
475 CString endOfOptions
;
476 if (CGit::ms_LastMsysGitVersion
>= ConvertVersionToInt(2, 38, 0))
477 endOfOptions
= L
" --";
479 m_strUrl
.Replace(L
'\\', L
'/');
481 cmd
.Format(L
"git.exe remote add%s \"%s\" \"%s\"", static_cast<LPCWSTR
>(endOfOptions
), static_cast<LPCWSTR
>(m_strRemote
), static_cast<LPCWSTR
>(m_strUrl
));
482 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
484 CMessageBox::Show(GetSafeHwnd(), out
, L
"TorotiseGit", MB_OK
| MB_ICONERROR
);
487 m_ChangedMask
&= ~REMOTE_URL
;
489 m_ctrlRemoteList
.SetCurSel(m_ctrlRemoteList
.AddString(m_strRemote
));
490 GetDlgItem(IDC_BUTTON_ADD
)->EnableWindow(TRUE
);
491 GetDlgItem(IDC_BUTTON_RENAME_REMOTE
)->EnableWindow(TRUE
);
492 if (!m_bNoFetch
&& CMessageBox::Show(GetSafeHwnd(), IDS_SETTINGS_FETCH_ADDEDREMOTE
, IDS_APPNAME
, MB_ICONQUESTION
| MB_YESNO
) == IDYES
)
493 CCommonAppUtils::RunTortoiseGitProc(L
"/command:fetch /path:\"" + g_Git
.m_CurrentDir
+ L
"\" /remote:\"" + m_strRemote
+ L
'"');
495 if(m_ChangedMask
& REMOTE_URL
)
497 m_strUrl
.Replace(L
'\\', L
'/');
498 if (!Save(L
"url", m_strUrl
))
502 if(m_ChangedMask
& REMOTE_PUTTYKEY
)
504 if (!Save(L
"puttykeyfile", m_strPuttyKeyfile
))
508 if (m_ChangedMask
& REMOTE_TAGOPT
)
511 int index
= m_ctrlTagOpt
.GetCurSel();
513 tagopt
= "--no-tags";
516 if (!Save(L
"tagopt", tagopt
))
520 if (m_ChangedMask
& REMOTE_PRUNE
)
522 if (!Save(L
"prune", m_bPrune
== TRUE
? L
"true" : m_bPrune
== FALSE
? L
"false" : L
""))
526 if (m_ChangedMask
& REMOTE_PUSHURL
)
528 m_strPushUrl
.Replace(L
'\\', L
'/');
529 if (!Save(L
"pushurl", m_strPushUrl
))
536 return ISettingsPropPage::OnApply();
539 void CleanupSyncRemotes(const CString
& remote
)
541 CString workingDir
= g_Git
.m_CurrentDir
;
542 workingDir
.Replace(L
':', L
'_');
543 CHistoryCombo::RemoveEntryFromHistory(L
"Software\\TortoiseGit\\History\\SyncURL\\" + workingDir
, L
"url", remote
);
546 void CSettingGitRemote::OnBnClickedButtonRemove()
548 int index
= m_ctrlRemoteList
.GetCurSel();
552 m_ctrlRemoteList
.GetText(index
,str
);
554 msg
.Format(IDS_WARN_REMOVE
, static_cast<LPCWSTR
>(str
));
555 if (CMessageBox::Show(GetSafeHwnd(), msg
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) == IDYES
)
557 CString endOfOptions
;
558 if (CGit::ms_LastMsysGitVersion
>= ConvertVersionToInt(2, 38, 0))
559 endOfOptions
= L
" --";
562 cmd
.Format(L
"git.exe remote rm%s \"%s\"", static_cast<LPCWSTR
>(endOfOptions
), static_cast<LPCWSTR
>(str
));
563 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
565 CMessageBox::Show(GetSafeHwnd(), out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
569 CleanupSyncRemotes(str
);
570 m_ctrlRemoteList
.DeleteString(index
);
571 OnLbnSelchangeListRemote();
576 void CSettingGitRemote::OnBnClickedButtonRenameRemote()
578 int sel
= m_ctrlRemoteList
.GetCurSel();
581 CString oldRemote
, newRemote
;
582 m_ctrlRemoteList
.GetText(sel
, oldRemote
);
583 GetDlgItem(IDC_EDIT_REMOTE
)->GetWindowText(newRemote
);
585 CString endOfOptions
;
586 if (CGit::ms_LastMsysGitVersion
>= ConvertVersionToInt(2, 38, 0))
587 endOfOptions
= L
" --";
590 cmd
.Format(L
"git.exe remote rename%s \"%s\" \"%s\"", static_cast<LPCWSTR
>(endOfOptions
), static_cast<LPCWSTR
>(oldRemote
), static_cast<LPCWSTR
>(newRemote
));
591 if (g_Git
.Run(cmd
, &out
, CP_UTF8
))
593 CMessageBox::Show(GetSafeHwnd(), out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
597 CleanupSyncRemotes(oldRemote
);
598 m_ctrlRemoteList
.DeleteString(sel
);
599 m_ctrlRemoteList
.SetCurSel(m_ctrlRemoteList
.AddString(newRemote
));
600 m_ChangedMask
&= ~REMOTE_NAME
;
602 this->SetModified(FALSE
);