diff --git a/redbot/cogs/downloader/downloader.py b/redbot/cogs/downloader/downloader.py index 29a9d126e87..267d5e8d1c7 100644 --- a/redbot/cogs/downloader/downloader.py +++ b/redbot/cogs/downloader/downloader.py @@ -566,6 +566,20 @@ async def _repo_add( await ctx.send( _("The repo name you provided is already in use. Please choose another name.") ) + except errors.AuthenticationError as err: + await ctx.send( + _( + "Failed to authenticate or repository does not exist." + " See logs for more information." + ) + ) + log.exception( + "Something went wrong whilst cloning %s (to revision: %s)", + repo_url, + branch, + exc_info=err, + ) + except errors.CloningError as err: await ctx.send( _( diff --git a/redbot/cogs/downloader/errors.py b/redbot/cogs/downloader/errors.py index ee0c6ab2ebe..412885b97c1 100644 --- a/redbot/cogs/downloader/errors.py +++ b/redbot/cogs/downloader/errors.py @@ -13,6 +13,7 @@ "CopyingError", "ExistingGitRepo", "MissingGitRepo", + "AuthenticationError", "CloningError", "CurrentHashError", "HardResetError", @@ -79,6 +80,15 @@ class MissingGitRepo(DownloaderException): pass +class AuthenticationError(GitException): + """ + Thrown when git failed to authenticate with + the server + """ + + pass + + class CloningError(GitException): """ Thrown when git clone returns a non zero exit code. diff --git a/redbot/cogs/downloader/repo_manager.py b/redbot/cogs/downloader/repo_manager.py index e0e404d1593..8d9b3fa06cc 100644 --- a/redbot/cogs/downloader/repo_manager.py +++ b/redbot/cogs/downloader/repo_manager.py @@ -188,6 +188,17 @@ def _existing_git_repo(self) -> Tuple[bool, Path]: git_path = self.folder_path / ".git" return git_path.exists(), git_path + def _parse_git_error(self, git_command: str, stderr: str) -> errors.GitException: + stderr = stderr.lower() + # Expected to catch: + # Could not read from remote repository + # could not read X for 'URL': terminal prompts disabled + # Authentication failed + if "could not read" in stderr or "authentication failed" in stderr: + return errors.AuthenticationError("Failed to Authenticate", git_command) + + return errors.CloningError("Error when running git clone.", git_command) + async def is_ancestor(self, maybe_ancestor_rev: str, descendant_rev: str) -> bool: """ Check if the first is an ancestor of the second. @@ -548,13 +559,20 @@ async def _run( """ env = os.environ.copy() env["GIT_TERMINAL_PROMPT"] = "0" + env["GIT_TRACE"] = "0" + # make sure we never enter an askpass routine + # https://github.com/git/git/blob/1424303/prompt.c#L56 env.pop("GIT_ASKPASS", None) + env.pop("SSH_ASKPASS", None) # attempt to force all output to plain ascii english # some methods that parse output may expect it # according to gettext manual both variables have to be set: # https://www.gnu.org/software/gettext/manual/gettext.html#Locale-Environment-Variables env["LC_ALL"] = "C" env["LANGUAGE"] = "C" + # Make sure git does not consider us smart + # https://github.com/git/git/blob/a7312d1a2/editor.c#L11-L15 + env["TERM"] = "dumb" kwargs["env"] = env async with self._repo_lock: p: CompletedProcess = await asyncio.get_running_loop().run_in_executor( @@ -659,7 +677,7 @@ async def clone(self) -> Tuple[Installable, ...]: if p.returncode: # Try cleaning up folder shutil.rmtree(str(self.folder_path), ignore_errors=True) - raise errors.CloningError("Error when running git clone.", git_command) + raise self._parse_git_error(git_command, p.stderr.decode(**DECODE_PARAMS)) if self.branch is None: self.branch = await self.current_branch()