KiCad PCB EDA Suite
Loading...
Searching...
No Matches
kigit_failure.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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "kigit_failure.h"
21#include "git_repo_mixin.h"
22#include "kicad_git_compat.h"
23#include "kicad_git_memory.h"
24
25#include <trace_helpers.h>
26
27#include <git2.h>
28#include <wx/ffile.h>
29#include <wx/filename.h>
30#include <wx/log.h>
31#include <wx/regex.h>
32#include <wx/tokenzr.h>
33#include <wx/translation.h>
34
35#include <cstdlib>
36#include <memory>
37
38
39namespace
40{
41
42struct TEXT_PATTERN
43{
44 const char* text;
45 KIGIT_FAILURE kind;
46};
47
48
49// Resolver and connection texts libgit2 and libssh2 produce themselves, as captured from real failures. They are
50// English in every locale and never translated. Nothing a server sends is matched, only the codes it returns.
51const TEXT_PATTERN c_textPatterns[] = {
52 { "failed to resolve address for", KIGIT_FAILURE::SERVER_NOT_FOUND },
53 { "failed to connect to", KIGIT_FAILURE::HOST_UNREACHABLE },
54 { "failed getting banner", KIGIT_FAILURE::HOST_UNREACHABLE },
55 // WinHTTP's failures to reach a server. The system text after the prefix is localized and is not matched.
56 { "failed to send request", KIGIT_FAILURE::HOST_UNREACHABLE },
57 { "failed to receive response", KIGIT_FAILURE::HOST_UNREACHABLE },
58 { "error loading known_hosts", KIGIT_FAILURE::KNOWN_HOSTS_UNREADABLE },
59 { "unable to open public key file", KIGIT_FAILURE::KEY_LOAD_FAILED },
60 { "proxy returned unexpected status", KIGIT_FAILURE::PROXY },
61};
62
63
64// wxString::Lower follows the C library locale, which turns I into a dotless i in Turkish. The texts matched here are
65// ASCII, so only ASCII is folded.
66wxString asciiLower( const wxString& aText )
67{
68 wxString lower;
69
70 for( wxUniChar c : aText )
71 {
72 const wxUniChar::value_type value = c.GetValue();
73
74 lower += ( value >= 'A' && value <= 'Z' ) ? wxUniChar( value + ( 'a' - 'A' ) ) : c;
75 }
76
77 return lower;
78}
79
80
81// libgit2 and its WinHTTP transport word a refused request differently, but both end in the status
82int httpStatusOf( const KIGIT_ERROR_INFO& aError, const wxString& aLower )
83{
84 static const wxString marker = wxS( "status code: " );
85 const int at = aLower.Find( marker );
86
87 return ( aError.klass == GIT_ERROR_HTTP && at != wxNOT_FOUND ) ? wxAtoi( aLower.Mid( at + marker.length() ) ) : 0;
88}
89
90
91KIGIT_COMMON::GIT_CONN_TYPE connTypeOf( const KIGIT_AUTH_ATTEMPT& aAuth, const KIGIT_FAILURE_CONTEXT& aContext )
92{
94
95 // The prompt can choose a transport for an address that names none
97 connType = aAuth.connType;
98
99 return connType;
100}
101
102
103bool lastKeyEncrypted( const KIGIT_AUTH_ATTEMPT& aAuth )
104{
105 // wxFFile reports an unopenable file to the user, and a key that is gone is simply not encrypted
106 if( aAuth.keysTried.empty() || !wxFileName::IsFileReadable( aAuth.keysTried.back() ) )
107 return false;
108
109 wxFFile file( aAuth.keysTried.back(), wxS( "rb" ) );
110 wxString text;
111
112 return file.IsOpened() && file.ReadAll( &text ) && KIGIT::IsPrivateKeyEncrypted( text );
113}
114
115
116// Lines kept for the details, without git's own transfer progress, bounded to the end where a refusal explains itself.
117// Server text arrives framed by blank "error:" lines and progress carriage returns.
118wxString serverText( const wxString& aText )
119{
120 // The titles git's progress.c and pack-objects print, so a server's own "Error code: 403" survives
121 static thread_local const wxRegEx progress( wxS( "^(((Enumerating|Counting|Compressing|Writing|Receiving) objects"
122 "|Resolving deltas): .*|Total [0-9]+ \\(delta [0-9]+\\).*)$" ),
123 wxRE_ADVANCED );
124
125 wxArrayString lines;
126 wxStringTokenizer tokens( KIGIT::PrintableServerText( aText ), wxS( "\r\n" ), wxTOKEN_STRTOK );
127
128 while( tokens.HasMoreTokens() )
129 {
130 const wxString line = tokens.GetNextToken().Trim().Trim( false );
131
132 if( !line.IsEmpty() && line != wxS( "error:" ) && line != wxS( "remote:" ) && !progress.Matches( line ) )
133 lines.Add( line );
134 }
135
136 // Already scrubbed, so the cut cannot leave half a URL that still names its password
137 return wxJoin( lines, '\n', 0 ).Right( KIGIT::MAX_SERVER_TEXT );
138}
139
140} // namespace
141
142
144 const KIGIT_FAILURE_CONTEXT& aContext )
145{
146 const KIGIT_COMMON::GIT_CONN_TYPE connType = connTypeOf( aAuth, aContext );
147 const wxString lower = asciiLower( aError.message );
148
149 if( aError.code == GIT_EUSER )
151
152 if( aError.code == GIT_ENONFASTFORWARD )
154
155 if( aError.code == GIT_ECERTIFICATE )
156 {
157 if( aContext.hostKey )
158 {
159 KIGIT::KNOWN_HOST_STATE state = aContext.hostKey->state;
160
163
165 }
166
167 if( !aContext.hostKeyRefusal.IsEmpty() )
169
172 }
173
174 // git_remote_push reports a refused branch only through the callback, which fails the push with its status
175 if( !aContext.pushRefusal.IsEmpty() )
177
178 if( aError.code == GIT_EAUTH )
179 {
180 if( aError.klass == GIT_ERROR_HTTP && lower.Contains( wxS( "proxy" ) ) )
182
185
186 // An empty or absent agent fails exactly as a rejected agent key does, so neither is claimed
189
191 }
192
193 // libgit2 returns any SSH stderr as GIT_EEOF when stdout is empty. The whole message is then the server's text,
194 // which says that the server refused and is never matched for its wording.
195 if( aError.code == GIT_EEOF && aError.klass == GIT_ERROR_SSH )
196 {
197 return ( connType == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH && !aContext.authFailed
198 && !serverText( aError.message ).IsEmpty() )
201 }
202
203 // mbedTLS fails an untrusted chain with -1 rather than GIT_ECERTIFICATE, and WinHTTP reports it as an HTTP error
204 if( ( aError.klass == GIT_ERROR_SSL || aError.klass == GIT_ERROR_HTTP ) && lower.Contains( wxS( "certificate" ) ) )
206
207 const int httpStatus = httpStatusOf( aError, lower );
208
209 if( httpStatus == 403 )
210 {
213 }
214
215 if( httpStatus == 404 )
217
218 for( const TEXT_PATTERN& pattern : c_textPatterns )
219 {
220 if( lower.Contains( wxString::FromUTF8( pattern.text ) ) )
221 {
222 // A key that failed to load was the last one the callback handed out
223 if( pattern.kind == KIGIT_FAILURE::KEY_LOAD_FAILED && aAuth.keysTried.empty() )
225
226 return pattern.kind;
227 }
228 }
229
231}
232
233
234namespace
235{
236
237wxString summaryFor( KIGIT_FAILURE aKind, const KIGIT_AUTH_ATTEMPT& aAuth, const KIGIT_FAILURE_CONTEXT& aContext )
238{
240 const wxString key = aAuth.keysTried.empty() ? wxString() : aAuth.keysTried.back();
241 wxString host = parsed.hostname;
242
243 // A lab or company server often listens on another port, and the bare host would name the wrong service
244 const int defaultPort = parsed.connType == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH
245 ? 22
246 : ( aContext.url.Lower().StartsWith( wxS( "http://" ) ) ? 80 : 443 );
247
248 if( !host.IsEmpty() && parsed.port > 0 && parsed.port != defaultPort )
249 host = wxString::Format( host.Contains( wxS( ":" ) ) ? wxS( "[%s]:%d" ) : wxS( "%s:%d" ), host, parsed.port );
250
251 if( host.IsEmpty() )
252 host = aContext.remoteName.IsEmpty() ? KIGIT::StripEmbeddedUserinfo( aContext.url ) : aContext.remoteName;
253
254 switch( aKind )
255 {
256 case KIGIT_FAILURE::AUTH_SSH_KEY_REJECTED:
257 // An encrypted key with a wrong passphrase fails exactly as an unregistered one does
258 if( lastKeyEncrypted( aAuth ) )
259 {
260 return wxString::Format( _( "%s did not accept the SSH key %s. Check the key's passphrase, and that "
261 "%s.pub is added to your account on %s." ),
262 host, key, key, host );
263 }
264
265 return wxString::Format( _( "%s did not accept the SSH key %s. Check that %s.pub is added to your account "
266 "on %s, or choose another key." ),
267 host, key, key, host );
268
270 return wxString::Format( _( "%s did not accept any SSH key. KiCad found no key files, and your SSH agent "
271 "offered none that %s accepts. Create or choose a key, and add its .pub to your "
272 "account on %s." ),
273 host, host, host );
274
276 return wxString::Format( _( "%s did not accept the username and password. Many servers require a personal "
277 "access token in place of a password." ),
278 host );
279
281 return wxString::Format( _( "The SSH key %s could not be opened. Check the passphrase, and that you chose "
282 "the private key file rather than the .pub file." ),
283 key );
284
286 return wxString::Format( _( "%s is not a known server yet. Use Get Changes to check its key and remember "
287 "it." ),
288 host );
289
291 return KIGIT::HostKeyRefusalMessage( *aContext.hostKey );
292
294 return wxString::Format( _( "The known hosts file %s has a line that could not be read. Fix or remove that "
295 "line, then try again." ),
297
299 return wxString::Format( _( "Could not find the server %s. Check the address, and that you are connected to "
300 "the network or VPN that reaches it." ),
301 host );
302
304 return wxString::Format( _( "%s did not answer. Check your network or VPN connection, then try again." ),
305 host );
306
308 return wxString::Format( _( "This computer does not trust the security certificate of %s. Ask your "
309 "administrator to install your organization's certificate." ),
310 host );
311
313 return wxString::Format( _( "The proxy refused the connection to %s. Check the proxy configured for Git "
314 "(http.proxy) or in your system settings." ),
315 host );
316
318 // Clone and Test Connection show the address in their own Location field
320 {
321 return wxString::Format( _( "%s has no repository at this address, or your account cannot see it. "
322 "Check the repository address." ),
323 host );
324 }
325
326 return wxString::Format( _( "%s has no repository at this address, or your account cannot see it. Check the "
327 "remote's address in the Advanced Git window, Remotes page." ),
328 host );
329
331 return wxString::Format( _( "%s refused the push. Your account may not have write access, or the branch is "
332 "protected." ),
333 host );
334
336 if( aContext.operation == KIGIT_OPERATION::PUSH )
337 return wxString::Format( _( "%s refused the push. The server's reason is shown in the details." ), host );
338
339 return wxString::Format( _( "%s refused the request. The server's reason is shown in the details." ), host );
340
342 return _( "The remote has changes you do not have yet. Get changes first, then push again." );
343
345 return _( "Cancelled." );
346
348 return aContext.hostKeyRefusal;
349
351 break;
352 }
353
354 const wxString remote = aContext.remoteName.IsEmpty() ? host : aContext.remoteName;
355
356 // Whole sentences, so translators never assemble a verb and its object
357 switch( aContext.operation )
358 {
360 return wxString::Format( _( "Could not clone from %s. The details below may help your administrator." ),
361 remote );
362
364 return wxString::Format( _( "Could not get changes from %s. The details below may help your administrator." ),
365 remote );
366
368 return wxString::Format( _( "Could not push to %s. The details below may help your administrator." ), remote );
369
371 return wxString::Format( _( "Could not set up the remote %s. The details below may help your administrator." ),
372 remote );
373
375 return wxString::Format( _( "Could not connect to %s. The details below may help your administrator." ),
376 remote );
377 }
378
379 return wxString();
380}
381
382
383wxString detailsFor( const KIGIT_ERROR_INFO& aError, const KIGIT_AUTH_ATTEMPT& aAuth,
384 const KIGIT_FAILURE_CONTEXT& aContext )
385{
386 // An SSH server's stderr arrives as libgit2's message, so it is treated as server text too
387 wxString details = serverText( aError.message );
388
389 auto addParagraph = [&]( const wxString& aText )
390 {
391 if( aText.IsEmpty() )
392 return;
393
394 if( !details.IsEmpty() )
395 details << wxS( "\n\n" );
396
397 details << aText;
398 };
399
400 const wxString pushRefusal = serverText( aContext.pushRefusal );
401
402 if( !pushRefusal.IsEmpty() && !details.Contains( pushRefusal ) )
403 addParagraph( pushRefusal );
404
405 addParagraph( serverText( aContext.serverMessage ) );
406
407 wxString offered = aAuth.agentTried ? _( "SSH agent" ) : wxString();
408
409 for( const wxString& key : aAuth.keysTried )
410 offered << ( offered.IsEmpty() ? wxString() : wxString( wxS( ", " ) ) ) << key;
411
412 if( !offered.IsEmpty() )
413 addParagraph( wxString::Format( _( "Keys offered: %s" ), offered ) );
414
415 if( !aContext.url.IsEmpty() )
416 addParagraph( wxString::Format( _( "Address: %s" ), KIGIT::StripEmbeddedUserinfo( aContext.url ) ) );
417
418 return KIGIT::StripEmbeddedUserinfo( details );
419}
420
421} // namespace
422
423
425 const KIGIT_FAILURE_CONTEXT& aContext )
426{
428 report.kind = classifyGitFailure( aError, aAuth, aContext );
429 report.summary = summaryFor( report.kind, aAuth, aContext );
430 report.details = detailsFor( aError, aAuth, aContext );
431 return report;
432}
433
434
435wxString KIGIT::UnexplainedFailure( KIGIT_OPERATION aOperation, const wxString& aUrl )
436{
437 KIGIT_FAILURE_CONTEXT context;
438 context.operation = aOperation;
439 context.url = aUrl;
440 return summaryFor( KIGIT_FAILURE::OTHER, {}, context );
441}
442
443
449
450
451wxString KIGIT::DecodeServerText( const char* aData, size_t aLength )
452{
453 wxString text = wxString::FromUTF8( aData, aLength );
454
455 // An empty result would drop the line, and with it the scheme the scrub looks for
456 if( text.IsEmpty() && aLength > 0 )
457 text = wxString::From8BitData( aData, aLength );
458
459 return text;
460}
461
462
463wxString KIGIT::StripEmbeddedUserinfo( const wxString& aText )
464{
465 wxString result;
466 size_t lineStart = 0;
467
468 result.reserve( aText.length() );
469
470 while( true )
471 {
472 size_t lineEnd = aText.find_first_of( wxS( "\r\n" ), lineStart );
473
474 if( lineEnd == wxString::npos )
475 lineEnd = aText.length();
476
477 // A password can hold anything but a line break, even what looks like another URL, so the whole line from
478 // its first "://" to its last "@" goes
479 wxString line = aText.Mid( lineStart, lineEnd - lineStart );
480 const size_t sep = line.find( wxS( "://" ) );
481 const size_t at = line.rfind( '@' );
482
483 if( sep != wxString::npos && at != wxString::npos && at >= sep + 3 )
484 line.erase( sep + 3, at - sep - 2 );
485
486 result << line;
487
488 if( lineEnd == aText.length() )
489 return result;
490
491 result << aText[lineEnd];
492 lineStart = lineEnd + 1;
493 }
494}
495
496
497wxString KIGIT::PrintableServerText( const wxString& aText )
498{
499 static thread_local const wxRegEx escapes( wxS( "\x1B\\[[0-?]*[ -/]*[@-~]" ), wxRE_ADVANCED );
500
501 wxString text = aText;
502
503 // wxRegEx stops replacing at a NUL, which would leave the rest of a password in place
504 text.Replace( wxString( wxUniChar( 0 ) ), wxEmptyString );
505
506 // An escape can end in the "@" that closes a userinfo, so the raw text is scrubbed first
508 escapes.ReplaceAll( &text, wxEmptyString );
509
510 wxString printable;
511
512 for( wxUniChar c : text )
513 {
514 const wxUint32 code = c.GetValue();
515 const bool control = code < 0x20 || code == 0x7F || ( code >= 0x80 && code <= 0x9F );
516 const bool bidi = ( code >= 0x202A && code <= 0x202E ) || ( code >= 0x2066 && code <= 0x2069 );
517
518 if( code == '\t' )
519 printable << wxS( " " );
520 else if( ( !control && !bidi ) || code == '\n' || code == '\r' )
521 printable << c;
522 }
523
524 // Removing escapes and controls can join a "://" the raw text split
525 return StripEmbeddedUserinfo( printable );
526}
527
528
530static wxFileName resolveLikeLibgit2( const wxString& aDir )
531{
532#ifdef __WINDOWS__
533 // win32 p_realpath is GetFullPathNameW then GetLongPathNameW, so links and junctions stay as written
534 wxFileName dir = wxFileName::DirName( aDir );
535 dir.Normalize( wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG );
536 return dir;
537#else
538 std::unique_ptr<char, decltype( &std::free )> resolved( realpath( aDir.utf8_str(), nullptr ), &std::free );
539 return wxFileName::DirName( resolved ? wxString::FromUTF8( resolved.get() ) : aDir );
540#endif
541}
542
543
545static wxString quoteForShell( const wxString& aPath )
546{
547#ifdef __WINDOWS__
548 // cmd and PowerShell each expand or end a quote on different characters, so only known-inert ones pass
549 static const wxString inert = wxS( " -_.,:/\\'()[]{}+=#@~" );
550
551 for( wxUniChar c : aPath )
552 {
553 if( !wxIsalnum( c ) && inert.Find( c ) == wxNOT_FOUND )
554 return wxEmptyString;
555 }
556
557 return wxS( "\"" ) + aPath + wxS( "\"" );
558#else
559 // Single quotes stop sh expanding $, `, " and \ in a folder name
560 wxString quoted = aPath;
561 quoted.Replace( wxS( "'" ), wxS( "'\\''" ) );
562 return wxS( "'" ) + quoted + wxS( "'" );
563#endif
564}
565
566
568static bool isRootedLikeLibgit2( const wxString& aPath )
569{
570 // libgit2 skips a drive prefix on every platform, not only Windows
571 const size_t offset = aPath.length() > 1 && aPath[1] == ':' ? 2 : 0;
572
573 if( offset >= aPath.length() )
574 return false;
575
576#ifdef __WINDOWS__
577 return aPath[offset] == '/' || aPath[offset] == '\\';
578#else
579 return aPath[offset] == '/';
580#endif
581}
582
583
584KIGIT_OWNERSHIP_REFUSAL KIGIT::DescribeOwnershipRefusal( const wxString& aStartDir, const wxString& aGitDir )
585{
586 const wxFileName gitDir = wxFileName::DirName( aGitDir );
587 wxFileName trusted = gitDir;
588 wxFileName dir = resolveLikeLibgit2( aStartDir );
589
590 // Reaching the git directory first means a bare repository, which is trusted by its own path
591 while( !dir.SameAs( gitDir ) )
592 {
593 if( wxFileName::Exists( dir.GetPathWithSep() + wxS( ".git" ) ) )
594 {
595 trusted = dir;
596 break;
597 }
598
599 if( dir.GetDirCount() == 0 )
600 break;
601
602 dir.RemoveLastDir();
603 }
604
605 // core.worktree moves the working folder git checks, except in a bare repository or linked worktree
606 git_config* rawConfig = nullptr;
607
608 if( !wxFileName::FileExists( gitDir.GetPathWithSep() + wxS( "commondir" ) )
609 && git_config_open_ondisk( &rawConfig, ( gitDir.GetPathWithSep() + wxS( "config" ) ).utf8_str() ) == GIT_OK )
610 {
611 KIGIT::GitConfigPtr config( rawConfig );
612 int error = GIT_OK;
613 int bare = 0;
614 git_buf worktree = GIT_BUF_INIT;
615
616#if KIGIT_HAS_WORKTREE_CONFIG
617 int worktreeConfig = 0;
618
619 // With the extension on, libgit2 lets config.worktree override config
620 if( git_config_get_bool( &worktreeConfig, config.get(), "extensions.worktreeConfig" ) == GIT_OK
621 && worktreeConfig )
622 {
623 error = git_config_add_file_ondisk( config.get(),
624 ( gitDir.GetPathWithSep() + wxS( "config.worktree" ) ).utf8_str(),
625 GIT_CONFIG_LEVEL_WORKTREE, nullptr, 0 );
626 }
627#endif
628
629 if( ( error == GIT_OK || error == GIT_ENOTFOUND )
630 && ( git_config_get_bool( &bare, config.get(), "core.bare" ) != GIT_OK || !bare )
631 && git_config_get_string_buf( &worktree, config.get(), "core.worktree" ) == GIT_OK )
632 {
633 KIGIT::GitBufPtr worktreePtr( &worktree );
634 wxString configured = wxString::FromUTF8( worktree.ptr );
635
636 // libgit2 joins a relative value onto the git directory and leaves .. and ~ to realpath
637 if( !isRootedLikeLibgit2( configured ) )
638 configured = gitDir.GetPathWithSep() + configured;
639
640 trusted = resolveLikeLibgit2( configured );
641 }
642 }
643
644 wxString path = trusted.GetPath();
645
646#ifdef __WINDOWS__
647 // git compares safe.directory against a forward-slash path, on Windows too
648 path.Replace( wxS( "\\" ), wxS( "/" ) );
649#endif
650
651 // git reads a trailing /* as every repository below the folder, so trusting this one would trust them all
652 if( path.EndsWith( wxS( "/*" ) ) )
653 {
654 return { wxString::Format( _( "The repository at '%s' belongs to another user, so git will not open it. "
655 "git reads a folder named '*' as a wildcard, so rename the folder before "
656 "trusting it." ),
657 trusted.GetPath() ),
658 wxEmptyString };
659 }
660
661 const wxString quoted = quoteForShell( path );
662
663 if( quoted.IsEmpty() )
664 {
665 return { wxString::Format( _( "The repository at '%s' belongs to another user, so git will not open it. To "
666 "trust it, add its folder to git's safe.directory setting and reopen the "
667 "project." ),
668 trusted.GetPath() ),
669 wxEmptyString };
670 }
671
672 return { wxString::Format( _( "The repository at '%s' belongs to another user, so git will not open it. To "
673 "trust it, run the command below and reopen the project." ),
674 trusted.GetPath() ),
675 wxS( "git config --global --add safe.directory " ) + quoted };
676}
static REMOTE_URL ParseRemoteURL(const wxString &aURL)
#define _(s)
#define GIT_BUF_INIT
static wxFileName resolveLikeLibgit2(const wxString &aDir)
aDir spelled as libgit2 1.9's p_realpath spells the working folder it checks safe....
static wxString quoteForShell(const wxString &aPath)
aPath quoted for the shell the trust command is pasted into, or empty when it cannot be done safely
static bool isRootedLikeLibgit2(const wxString &aPath)
True when libgit2's git_fs_path_root takes aPath as rooted rather than joining it onto a base folder.
static KIGIT_FAILURE classifyGitFailure(const KIGIT_ERROR_INFO &aError, const KIGIT_AUTH_ATTEMPT &aAuth, const KIGIT_FAILURE_CONTEXT &aContext)
KIGIT_FAILURE
Why a transfer failed, in terms of what the user can do about it.
@ SERVER_REFUSED
The server answered with text alone. Its wording is shown but never interpreted.
@ HOST_KEY_UNVERIFIED
A key KiCad could not check, so there is nothing to offer to trust.
KIGIT_OPERATION
The transfer that failed, so a message nothing more specific explains can say what did not happen.
APIEXPORT wxString StripEmbeddedUserinfo(const wxString &aText)
Remove the user and password from every URL quoted in a message, since the remote's can carry a passw...
bool IsPrivateKeyEncrypted(const wxString &aKeyText)
Report whether an SSH private key is protected by a passphrase.
APIEXPORT wxString PrintableServerText(const wxString &aText)
Text a server sent, made safe to show: no terminal escapes, control or bidi characters,...
constexpr size_t MAX_SERVER_TEXT
The longest server text a failure keeps. A refusal's explanation comes last, after any transfer progr...
KNOWN_HOST_STATE
How a server's host key stands against known_hosts.
@ MISMATCH
Entries name the host with another key of this type, and none with this one.
@ REVOKED
A @revoked entry carries this key.
std::unique_ptr< git_buf, decltype([](git_buf *aBuf) { git_buf_free(aBuf); })> GitBufPtr
A unique pointer for git_buf objects with automatic cleanup.
APIEXPORT KIGIT_OWNERSHIP_REFUSAL DescribeOwnershipRefusal(const wxString &aStartDir, const wxString &aGitDir)
The message for a repository git refuses to open because another user owns it.
std::unique_ptr< git_config, decltype([](git_config *aConfig) { git_config_free(aConfig); })> GitConfigPtr
A unique pointer for git_config objects with automatic cleanup.
APIEXPORT wxString DecodeServerText(const char *aData, size_t aLength)
Bytes a server sent, as UTF-8 or, when they are not valid UTF-8, one character for each byte.
APIEXPORT bool IsCredentialFailure(KIGIT_FAILURE aKind)
True for the failures another login, key or passphrase can fix, which the credential prompt answers.
APIEXPORT KIGIT_FAILURE_REPORT DescribeGitFailure(const KIGIT_ERROR_INFO &aError, const KIGIT_AUTH_ATTEMPT &aAuth, const KIGIT_FAILURE_CONTEXT &aContext)
Turn a failed transfer into a message the user can act on.
APIEXPORT wxString HostKeyRefusalMessage(const HOST_KEY_FAILURE &aFailure)
Why a key the user may not trust was refused, and what to do next.
APIEXPORT wxString KnownHostsPath()
The known_hosts file libgit2 checks host keys against. Empty when it has no home folder.
APIEXPORT wxString UnexplainedFailure(KIGIT_OPERATION aOperation, const wxString &aUrl)
The summary for an operation that failed without saying why, naming the server it was talking to.
What the credential callback handed out during one operation, kept to explain a rejection.
KIGIT_COMMON::GIT_CONN_TYPE connType
std::vector< wxString > keysTried
Key files offered, in order.
bool agentTried
The SSH agent was offered before any key file.
A transport address with any embedded credentials extracted.
int port
0 when the address names none.
What libgit2 reported for a failed call, read before anything else can replace it.
int klass
git_error_t
int code
The failed call's return value.
What the failed operation was doing, as far as the message needs it.
KIGIT_OPERATION operation
std::optional< KIGIT::HOST_KEY_FAILURE > hostKey
The host key the operation refused, if any.
wxString hostKeyRefusal
Why a key that cannot be trusted was refused.
bool authFailed
The credential callback had nothing left to offer.
wxString url
The address connected to. Messages show only its host and its stripped form.
wxString remoteName
Empty when the operation has no named remote, as for a clone.
wxString pushRefusal
The status the server gave a refused branch.
wxString serverMessage
Text the server sent for the user.
wxString summary
What happened and what to do next, in the user's terms. Never raw text.
wxString details
The raw error, the server's text and the address without its password.
Why git will not open a repository another user owns, and the command that trusts it.
std::string path
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.