29#include <wx/filename.h>
32#include <wx/tokenzr.h>
33#include <wx/translation.h>
51const TEXT_PATTERN c_textPatterns[] = {
66wxString asciiLower(
const wxString& aText )
70 for( wxUniChar c : aText )
72 const wxUniChar::value_type value = c.GetValue();
74 lower += ( value >=
'A' && value <=
'Z' ) ? wxUniChar( value + (
'a' -
'A' ) ) : c;
84 static const wxString marker = wxS(
"status code: " );
85 const int at = aLower.Find( marker );
87 return ( aError.
klass == GIT_ERROR_HTTP && at != wxNOT_FOUND ) ? wxAtoi( aLower.Mid( at + marker.length() ) ) : 0;
106 if( aAuth.
keysTried.empty() || !wxFileName::IsFileReadable( aAuth.
keysTried.back() ) )
109 wxFFile file( aAuth.
keysTried.back(), wxS(
"rb" ) );
118wxString serverText(
const wxString& aText )
121 static thread_local const wxRegEx progress( wxS(
"^(((Enumerating|Counting|Compressing|Writing|Receiving) objects"
122 "|Resolving deltas): .*|Total [0-9]+ \\(delta [0-9]+\\).*)$" ),
128 while( tokens.HasMoreTokens() )
130 const wxString line = tokens.GetNextToken().Trim().Trim(
false );
132 if( !line.IsEmpty() && line != wxS(
"error:" ) && line != wxS(
"remote:" ) && !progress.Matches( line ) )
147 const wxString lower = asciiLower( aError.
message );
149 if( aError.
code == GIT_EUSER )
152 if( aError.
code == GIT_ENONFASTFORWARD )
155 if( aError.
code == GIT_ECERTIFICATE )
178 if( aError.
code == GIT_EAUTH )
180 if( aError.
klass == GIT_ERROR_HTTP && lower.Contains( wxS(
"proxy" ) ) )
195 if( aError.
code == GIT_EEOF && aError.
klass == GIT_ERROR_SSH )
198 && !serverText( aError.
message ).IsEmpty() )
204 if( ( aError.
klass == GIT_ERROR_SSL || aError.
klass == GIT_ERROR_HTTP ) && lower.Contains( wxS(
"certificate" ) ) )
207 const int httpStatus = httpStatusOf( aError, lower );
209 if( httpStatus == 403 )
215 if( httpStatus == 404 )
218 for(
const TEXT_PATTERN& pattern : c_textPatterns )
220 if( lower.Contains( wxString::FromUTF8( pattern.text ) ) )
240 const wxString key = aAuth.
keysTried.empty() ? wxString() : aAuth.keysTried.back();
246 : ( aContext.
url.Lower().StartsWith( wxS(
"http://" ) ) ? 80 : 443 );
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 );
256 case KIGIT_FAILURE::AUTH_SSH_KEY_REJECTED:
258 if( lastKeyEncrypted( aAuth ) )
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 );
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 );
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 "
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." ),
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." ),
286 return wxString::Format(
_(
"%s is not a known server yet. Use Get Changes to check its key and remember "
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." ),
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." ),
304 return wxString::Format(
_(
"%s did not answer. Check your network or VPN connection, then try again." ),
308 return wxString::Format(
_(
"This computer does not trust the security certificate of %s. Ask your "
309 "administrator to install your organization's certificate." ),
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." ),
321 return wxString::Format(
_(
"%s has no repository at this address, or your account cannot see it. "
322 "Check the repository address." ),
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." ),
331 return wxString::Format(
_(
"%s refused the push. Your account may not have write access, or the branch is "
337 return wxString::Format(
_(
"%s refused the push. The server's reason is shown in the details." ), host );
339 return wxString::Format(
_(
"%s refused the request. The server's reason is shown in the details." ), host );
342 return _(
"The remote has changes you do not have yet. Get changes first, then push again." );
345 return _(
"Cancelled." );
360 return wxString::Format(
_(
"Could not clone from %s. The details below may help your administrator." ),
364 return wxString::Format(
_(
"Could not get changes from %s. The details below may help your administrator." ),
368 return wxString::Format(
_(
"Could not push to %s. The details below may help your administrator." ), remote );
371 return wxString::Format(
_(
"Could not set up the remote %s. The details below may help your administrator." ),
375 return wxString::Format(
_(
"Could not connect to %s. The details below may help your administrator." ),
387 wxString details = serverText( aError.
message );
389 auto addParagraph = [&](
const wxString& aText )
391 if( aText.IsEmpty() )
394 if( !details.IsEmpty() )
395 details << wxS(
"\n\n" );
400 const wxString pushRefusal = serverText( aContext.
pushRefusal );
402 if( !pushRefusal.IsEmpty() && !details.Contains( pushRefusal ) )
403 addParagraph( pushRefusal );
407 wxString offered = aAuth.
agentTried ?
_(
"SSH agent" ) : wxString();
409 for(
const wxString& key : aAuth.
keysTried )
410 offered << ( offered.IsEmpty() ? wxString() : wxString( wxS(
", " ) ) ) << key;
412 if( !offered.IsEmpty() )
413 addParagraph( wxString::Format(
_(
"Keys offered: %s" ), offered ) );
415 if( !aContext.
url.IsEmpty() )
429 report.
summary = summaryFor( report.
kind, aAuth, aContext );
430 report.
details = detailsFor( aError, aAuth, aContext );
453 wxString
text = wxString::FromUTF8( aData, aLength );
456 if(
text.IsEmpty() && aLength > 0 )
457 text = wxString::From8BitData( aData, aLength );
466 size_t lineStart = 0;
468 result.reserve( aText.length() );
472 size_t lineEnd = aText.find_first_of( wxS(
"\r\n" ), lineStart );
474 if( lineEnd == wxString::npos )
475 lineEnd = aText.length();
479 wxString line = aText.Mid( lineStart, lineEnd - lineStart );
480 const size_t sep = line.find( wxS(
"://" ) );
481 const size_t at = line.rfind(
'@' );
483 if( sep != wxString::npos && at != wxString::npos && at >= sep + 3 )
484 line.erase( sep + 3, at - sep - 2 );
488 if( lineEnd == aText.length() )
492 lineStart = lineEnd + 1;
499 static thread_local const wxRegEx escapes( wxS(
"\x1B\\[[0-?]*[ -/]*[@-~]" ), wxRE_ADVANCED );
501 wxString
text = aText;
504 text.Replace( wxString( wxUniChar( 0 ) ), wxEmptyString );
508 escapes.ReplaceAll( &
text, wxEmptyString );
512 for( wxUniChar c :
text )
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 );
519 printable << wxS(
" " );
520 else if( ( !control && !bidi ) || code ==
'\n' || code ==
'\r' )
534 wxFileName dir = wxFileName::DirName( aDir );
535 dir.Normalize( wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG );
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 );
549 static const wxString inert = wxS(
" -_.,:/\\'()[]{}+=#@~" );
551 for( wxUniChar c : aPath )
553 if( !wxIsalnum( c ) && inert.Find( c ) == wxNOT_FOUND )
554 return wxEmptyString;
557 return wxS(
"\"" ) + aPath + wxS(
"\"" );
560 wxString quoted = aPath;
561 quoted.Replace( wxS(
"'" ), wxS(
"'\\''" ) );
562 return wxS(
"'" ) + quoted + wxS(
"'" );
571 const size_t offset = aPath.length() > 1 && aPath[1] ==
':' ? 2 : 0;
573 if( offset >= aPath.length() )
577 return aPath[offset] ==
'/' || aPath[offset] ==
'\\';
579 return aPath[offset] ==
'/';
586 const wxFileName gitDir = wxFileName::DirName( aGitDir );
587 wxFileName trusted = gitDir;
591 while( !dir.SameAs( gitDir ) )
593 if( wxFileName::Exists( dir.GetPathWithSep() + wxS(
".git" ) ) )
599 if( dir.GetDirCount() == 0 )
606 git_config* rawConfig =
nullptr;
608 if( !wxFileName::FileExists( gitDir.GetPathWithSep() + wxS(
"commondir" ) )
609 && git_config_open_ondisk( &rawConfig, ( gitDir.GetPathWithSep() + wxS(
"config" ) ).utf8_str() ) == GIT_OK )
616#if KIGIT_HAS_WORKTREE_CONFIG
617 int worktreeConfig = 0;
620 if( git_config_get_bool( &worktreeConfig,
config.get(),
"extensions.worktreeConfig" ) == GIT_OK
623 error = git_config_add_file_ondisk(
config.get(),
624 ( gitDir.GetPathWithSep() + wxS(
"config.worktree" ) ).utf8_str(),
625 GIT_CONFIG_LEVEL_WORKTREE,
nullptr, 0 );
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 )
634 wxString configured = wxString::FromUTF8( worktree.ptr );
638 configured = gitDir.GetPathWithSep() + configured;
644 wxString
path = trusted.GetPath();
648 path.Replace( wxS(
"\\" ), wxS(
"/" ) );
652 if(
path.EndsWith( wxS(
"/*" ) ) )
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 "
663 if( quoted.IsEmpty() )
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 "
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." ),
675 wxS(
"git config --global --add safe.directory " ) + quoted };
static REMOTE_URL ParseRemoteURL(const wxString &aURL)
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 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.
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.