KiCad PCB EDA Suite
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
dialog_git_repository.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
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 3
9 * of the License, or (at your option) any later version.
10 *
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.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25#include <confirm.h>
26
27#include <git2.h>
30#include <git/git_repo_mixin.h>
31#include <gestfich.h>
32
33#include <cerrno>
34#include <cstring>
35#include <fstream>
36
37#include <wx/clipbrd.h>
38#include <wx/msgdlg.h>
39#include <wx/regex.h>
40#include <wx/stdpaths.h>
41
42
43DIALOG_GIT_REPOSITORY::DIALOG_GIT_REPOSITORY( wxWindow* aParent, git_repository* aRepository,
44 wxString aURL ) :
46 m_repository( aRepository ),
47 m_prevFile( wxEmptyString ),
48 m_tempRepo( false )
49{
50 m_txtURL->SetFocus();
51
52 if( !m_repository )
53 {
54 // Make a temporary repository to test the connection
55 m_tempRepo = true;
56 m_tempPath = wxFileName::CreateTempFileName( "kicadtestrepo" );
57
58 git_repository_init_options options;
59 git_repository_init_init_options( &options, GIT_REPOSITORY_INIT_OPTIONS_VERSION );
60 options.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT;
61 git_repository_init_ext( &m_repository, m_tempPath.ToStdString().c_str(), &options );
62 }
63
64 if( !aURL.empty() )
65 m_txtURL->SetValue( aURL );
66 else
68
69 if( !m_txtURL->GetValue().IsEmpty() )
71 else
72 m_ConnType->SetSelection( static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL ) );
73
74
75 SetupStandardButtons( { { wxID_HELP, _( "Test Connection" ) } } );
76 Layout();
78
80}
81
82
84{
85 if( m_tempRepo )
86 {
87 git_repository_free( m_repository );
89 }
90}
91
92
94{
95 if( wxTheClipboard->Open() && wxTheClipboard->IsSupported( wxDF_TEXT ) )
96 {
97 wxString clipboardText;
98 wxTextDataObject textData;
99
100 if( wxTheClipboard->GetData( textData ) && !( clipboardText = textData.GetText() ).empty() )
101 {
102 if( std::get<0>( isValidHTTPS( clipboardText ) )
103 || std::get<0>( isValidSSH( clipboardText ) ) )
104 {
105 m_txtURL->SetValue( clipboardText );
106 }
107 }
108
109 wxTheClipboard->Close();
110 }
111
112 return false;
113}
114
115
117{
118 wxFileName sshKey;
119 sshKey.SetPath( wxGetUserHome() );
120 wxString retval;
121
122 sshKey.AppendDir( ".ssh" );
123 sshKey.SetFullName( "id_rsa" );
124
125 if( sshKey.FileExists() )
126 {
127 retval = sshKey.GetFullPath();
128 }
129 else if( sshKey.SetFullName( "id_dsa" ); sshKey.FileExists() )
130 {
131 retval = sshKey.GetFullPath();
132 }
133 else if( sshKey.SetFullName( "id_ecdsa" ); sshKey.FileExists() )
134 {
135 retval = sshKey.GetFullPath();
136 }
137
138 if( !retval.empty() )
139 {
140 m_fpSSHKey->SetFileName( retval );
141 wxFileDirPickerEvent evt;
142 evt.SetPath( retval );
143 OnFileUpdated( evt );
144 }
145}
146
147
148void DIALOG_GIT_REPOSITORY::onCbCustom( wxCommandEvent& event )
149{
151 event.Skip();
152}
153
154
155void DIALOG_GIT_REPOSITORY::OnUpdateUI( wxUpdateUIEvent& event )
156{
157 // event.Enable( !m_txtName->GetValue().IsEmpty() && !m_txtURL->GetValue().IsEmpty() );
158}
159
160
162{
163 if( aEncrypted )
164 {
165 m_txtPassword->Enable();
166 m_txtPassword->SetToolTip( _( "Enter the password for the SSH key" ) );
167 }
168 else
169 {
170 m_txtPassword->SetValue( wxEmptyString );
171 m_txtPassword->SetToolTip( wxEmptyString );
172 m_txtPassword->Disable();
173 }
174}
175
176
177std::tuple<bool,wxString,wxString,wxString> DIALOG_GIT_REPOSITORY::isValidHTTPS( const wxString& url )
178{
179 wxRegEx regex( R"((https?:\/\/)(([^:]+)(:([^@]+))?@)?([^\/]+\/[^\s]+))" );
180
181 if( regex.Matches( url ) )
182 {
183 wxString username = regex.GetMatch( url, 3 );
184 wxString password = regex.GetMatch( url, 5 );
185 wxString repoAddress = regex.GetMatch( url, 1 ) + regex.GetMatch( url, 6 );
186 return std::make_tuple( true, username, password, repoAddress );
187 }
188
189 return std::make_tuple( false, "", "", "" );
190}
191
192
193std::tuple<bool,wxString, wxString> DIALOG_GIT_REPOSITORY::isValidSSH( const wxString& url )
194{
195 wxRegEx regex( R"((?:ssh:\/\/)?([^@]+)@([^\/]+\/[^\s]+))" );
196
197 if( regex.Matches( url ) )
198 {
199 wxString username = regex.GetMatch( url, 1 );
200 wxString repoAddress = regex.GetMatch( url, 2 );
201 return std::make_tuple( true, username, repoAddress );
202 }
203
204 return std::make_tuple( false, "", "" );
205}
206
207
208static wxString get_repo_name( wxString& aRepoAddr )
209{
210 wxString retval;
211 size_t last_slash = aRepoAddr.find_last_of( '/' );
212 bool ends_with_dot_git = aRepoAddr.EndsWith( ".git" );
213
214 if( ends_with_dot_git )
215 retval = aRepoAddr.substr( last_slash + 1, aRepoAddr.size() - last_slash - 5 );
216 else
217 retval = aRepoAddr.substr( last_slash + 1, aRepoAddr.size() - last_slash );
218
219 return retval;
220}
221
222
223void DIALOG_GIT_REPOSITORY::OnLocationExit( wxFocusEvent& event )
224{
227 event.Skip();
228}
229
230
232{
233 wxString url = m_txtURL->GetValue();
234
235 if( url.IsEmpty() )
236 return;
237
238 if( url.Contains( "https://" ) || url.Contains( "http://" ) )
239 {
240 auto [valid, username, password, repoAddress] = isValidHTTPS( url );
241
242 if( valid )
243 {
244 m_fullURL = url;
245 m_ConnType->SetSelection( static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS ) );
246 SetUsername( username );
247 SetPassword( password );
248 m_txtURL->SetValue( repoAddress );
249
250 if( m_txtName->GetValue().IsEmpty() )
251 m_txtName->SetValue( get_repo_name( repoAddress ) );
252 }
253 }
254 else if( url.Contains( "ssh://" ) || url.Contains( "git@" ) )
255 {
256 auto [valid, username, repoAddress] = isValidSSH( url );
257
258 if( valid )
259 {
260 m_fullURL = url;
261 m_ConnType->SetSelection( static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH ) );
262 m_txtUsername->SetValue( username );
263 m_txtURL->SetValue( repoAddress );
264
265 if( m_txtName->GetValue().IsEmpty() )
266 m_txtName->SetValue( get_repo_name( repoAddress ) );
267
269 }
270 }
271}
272
273
274void DIALOG_GIT_REPOSITORY::OnTestClick( wxCommandEvent& event )
275{
276 if( m_txtURL->GetValue().Trim().Trim( false ).IsEmpty() )
277 return;
278
279 wxString error;
280 bool success = false;
281 git_remote* remote = nullptr;
282 git_remote_callbacks callbacks;
283 git_remote_init_callbacks( &callbacks, GIT_REMOTE_CALLBACKS_VERSION );
284
285 // We track if we have already tried to connect.
286 // If we have, the server may come back to offer another connection
287 // type, so we need to keep track of how many times we have tried.
288
289 KIGIT_COMMON common( m_repository );
290 common.SetRemote( m_txtURL->GetValue() );
291 callbacks.credentials = credentials_cb;
292 common.SetPassword( m_txtPassword->GetValue() );
293 common.SetUsername( m_txtUsername->GetValue() );
294 common.SetSSHKey( m_fpSSHKey->GetFileName().GetFullPath() );
295 KIGIT_REPO_MIXIN repoMixin( &common );
296 callbacks.payload = &repoMixin;
297
298 wxString txtURL = m_txtURL->GetValue();
299 git_remote_create_with_fetchspec( &remote, m_repository, "origin", txtURL.mbc_str(),
300 "+refs/heads/*:refs/remotes/origin/*" );
301 KIGIT::GitRemotePtr remotePtr( remote );
302
303 if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &callbacks, nullptr, nullptr ) == GIT_OK )
304 success = true;
305 else
307
308 git_remote_disconnect( remote );
309
310 auto dlg = wxMessageDialog( this, wxEmptyString, _( "Test Connection" ),
311 wxOK | wxICON_INFORMATION );
312
313 if( success )
314 {
315 dlg.SetMessage( _( "Connection successful" ) );
316 }
317 else
318 {
319 dlg.SetMessage( wxString::Format( _( "Could not connect to '%s' " ),
320 m_txtURL->GetValue() ) );
321 dlg.SetExtendedMessage( error );
322 }
323
324 dlg.ShowModal();
325}
326
327
328void DIALOG_GIT_REPOSITORY::OnFileUpdated( wxFileDirPickerEvent& aEvent )
329{
330 wxString file = aEvent.GetPath();
331
332 if( file.ends_with( wxS( ".pub" ) ) )
333 file = file.Left( file.size() - 4 );
334
335 std::ifstream ifs( file.fn_str() );
336
337 if( !ifs.good() || !ifs.is_open() )
338 {
339 DisplayErrorMessage( this, wxString::Format( _( "Could not open private key '%s'" ), file ),
340 wxString::Format( "%s: %d", std::strerror( errno ), errno ) );
341 return;
342 }
343
344 std::string line;
345 std::getline( ifs, line );
346
347 bool isValid = ( line.find( "PRIVATE KEY" ) != std::string::npos );
348 bool isEncrypted = ( line.find( "ENCRYPTED" ) != std::string::npos );
349
350 if( !isValid )
351 {
352 DisplayErrorMessage( this, _( "Invalid SSH Key" ),
353 _( "The selected file is not a valid SSH private key" ) );
354 CallAfter( [this] { SetRepoSSHPath( m_prevFile ); } );
355 return;
356 }
357
358 if( isEncrypted )
359 {
360 m_txtPassword->Enable();
361 m_txtPassword->SetToolTip( _( "Enter the password for the SSH key" ) );
362 }
363 else
364 {
365 m_txtPassword->SetValue( wxEmptyString );
366 m_txtPassword->SetToolTip( wxEmptyString );
367 m_txtPassword->Disable();
368 }
369
370 ifs.close();
371
372 wxString pubFile = file + wxS( ".pub" );
373 std::ifstream pubIfs( pubFile.fn_str() );
374
375 if( !pubIfs.good() || !pubIfs.is_open() )
376 {
377 DisplayErrorMessage( this, wxString::Format( _( "Could not open public key '%s'" ),
378 file + ".pub" ),
379 wxString::Format( "%s: %d", std::strerror( errno ), errno ) );
380 aEvent.SetPath( wxEmptyString );
381 CallAfter( [this] { SetRepoSSHPath( m_prevFile ); } );
382 return;
383 }
384
385 m_prevFile = file;
386 pubIfs.close();
387}
388
389
390void DIALOG_GIT_REPOSITORY::OnOKClick( wxCommandEvent& event )
391{
392 // Save the repository details
393
394 if( m_txtURL->GetValue().IsEmpty() )
395 {
396 DisplayErrorMessage( this, _( "Missing information" ),
397 _( "Please enter a URL for the repository" ) );
398 return;
399 }
400
401 EndModal( wxID_OK );
402}
403
404
406{
407 if( m_ConnType->GetSelection() == static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL ) )
408 {
409 m_panelAuth->Enable( false );
410 }
411 else
412 {
413 m_panelAuth->Enable( true );
414
415 if( m_ConnType->GetSelection() == static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH ) )
416 {
417 m_cbCustom->Enable( true );
418 m_fpSSHKey->Enable( m_cbCustom->IsChecked() );
419 m_txtUsername->Enable( m_cbCustom->IsChecked() );
420 m_txtPassword->Enable( m_cbCustom->IsChecked() );
421 m_labelPass1->SetLabel( _( "SSH key password:" ) );
422 }
423 else
424 {
425 m_cbCustom->Enable( false );
426 m_fpSSHKey->Enable( false );
427 m_txtUsername->Enable( true );
428 m_txtPassword->Enable( true );
429 m_labelPass1->SetLabel( _( "Password:" ) );
430 }
431 }
432}
433
434
435void DIALOG_GIT_REPOSITORY::OnSelectConnType( wxCommandEvent& event )
436{
438}
Class DIALOG_GIT_REPOSITORY_BASE.
void OnSelectConnType(wxCommandEvent &event) override
std::tuple< bool, wxString, wxString > isValidSSH(const wxString &url)
void SetRepoSSHPath(const wxString &aPath)
void OnFileUpdated(wxFileDirPickerEvent &event) override
void SetPassword(const wxString &aPassword)
void onCbCustom(wxCommandEvent &event) override
void SetEncrypted(bool aEncrypted=true)
void OnLocationExit(wxFocusEvent &event) override
DIALOG_GIT_REPOSITORY(wxWindow *aParent, git_repository *aRepository, wxString aURL=wxEmptyString)
void OnTestClick(wxCommandEvent &event) override
void OnOKClick(wxCommandEvent &event) override
void SetUsername(const wxString &aUsername)
std::tuple< bool, wxString, wxString, wxString > isValidHTTPS(const wxString &url)
void OnUpdateUI(wxUpdateUIEvent &event) override
void SetupStandardButtons(std::map< int, wxString > aLabels={})
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
static wxString GetLastGitError()
void SetSSHKey(const wxString &aSSHKey)
void SetUsername(const wxString &aUsername)
void SetPassword(const wxString &aPassword)
void SetRemote(const wxString &aRemote)
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:194
This file is part of the common library.
static wxString get_repo_name(wxString &aRepoAddr)
#define _(s)
bool RmDirRecursive(const wxString &aFileName, wxString *aErrors)
Remove the directory aDirName and all its contents including subdirectories and their files.
Definition: gestfich.cpp:311
int credentials_cb(git_cred **aOut, const char *aUrl, const char *aUsername, unsigned int aAllowedTypes, void *aPayload)
std::unique_ptr< git_remote, decltype([](git_remote *aRemote) { git_remote_free(aRemote) GitRemotePtr
A unique pointer for git_remote objects with automatic cleanup.