Repository: Cog-Creators/Red-DiscordBot

Issue:
[Downloader] UnicodeDecodeError in `[p]cog update`
# Other bugs

<!-- 
Did you find a bug with something other than a command? Fill out the following:
-->

#### What were you trying to do?

`cog update` (for all cogs)

#### What were you expecting to happen?

All cogs updated and available to reload.

#### What actually happened?

`Error in command 'cog update'. Check your console or logs for details.`

#### How can we reproduce this issue?

I have no clue sadly.

#### Debug Info for Red

Red version
3.3.2

Python version
3.8.1 (64bit)

Discord.py version
1.3.2

Pip version
20.0.2

System arch
AMD64

User
Chris

OS version
Windows 7 (version 6.1.7601)

Python executable
`c:\__\coding\redenv\scripts\python.exe`

#### Traceback

```
[ERROR] red: Exception in command 'cog update'
Traceback (most recent call last):
  File "c:\__\coding\redenv\lib\site-packages\discord\ext\commands\core.py", line 83, in wrapped
    ret = await coro(*args, **kwargs)
  File "c:\__\coding\redenv\lib\site-packages\redbot\cogs\downloader\downloader.py", line 885, in _cog_update
    await self._cog_update_logic(ctx, cogs=cogs)
  File "c:\__\coding\redenv\lib\site-packages\redbot\cogs\downloader\downloader.py", line 979, in _cog_update_logic
    updated_cognames, message = await self._update_cogs_and_libs(
  File "c:\__\coding\redenv\lib\site-packages\redbot\cogs\downloader\downloader.py", line 1237, in _update_cogs_and_libs
    failed_reqs = await self._install_requirements(cogs_to_update)
  File "c:\__\coding\redenv\lib\site-packages\redbot\cogs\downloader\downloader.py", line 434, in _install_requirements
    if not await repo.install_raw_requirements([req], self.LIB_PATH):
  File "c:\__\coding\redenv\lib\site-packages\redbot\cogs\downloader\repo_manager.py", line 931, in install_raw_requirements
    p = await self._run(
  File "c:\__\coding\redenv\lib\site-packages\redbot\cogs\downloader\repo_manager.py", line 533, in _run
    stderr = p.stderr.decode().strip()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc5 in position 936: invalid continuation byte
```


Discussion:
One question, is `c:\__\coding\redenv` actual path of your venv or is `__` a placeholder?

Also information to others looking at this issue, I haven't tried reproducing this yet. 
It's the actual path.

Resolving patch:
diff --git a/redbot/cogs/downloader/repo_manager.py b/redbot/cogs/downloader/repo_manager.py
--- a/redbot/cogs/downloader/repo_manager.py
+++ b/redbot/cogs/downloader/repo_manager.py
@@ -39,6 +39,12 @@
 _ = Translator("RepoManager", __file__)
 
 
+DECODE_PARAMS = {
+    "encoding": "utf-8",
+    "errors": "surrogateescape",
+}
+
+
 class Candidate(NamedTuple):
     rev: str
     object_type: str
@@ -259,7 +265,7 @@ async def _get_file_update_statuses(
                 f"Git diff failed for repo at path: {self.folder_path}", git_command
             )
 
-        stdout = p.stdout.strip(b"\t\n\x00 ").decode().split("\x00\t")
+        stdout = p.stdout.strip(b"\t\n\x00 ").decode(**DECODE_PARAMS).split("\x00\t")
         ret = {}
 
         for filename in stdout:
@@ -318,7 +324,7 @@ async def get_last_module_occurrence(
                 f"Git log failed for repo at path: {self.folder_path}", git_command
             )
 
-        commit = p.stdout.decode().strip()
+        commit = p.stdout.decode(**DECODE_PARAMS).strip()
         if commit:
             async with self.checkout(f"{commit}~"):
                 return discord.utils.get(self.available_modules, name=module_name)
@@ -426,7 +432,7 @@ async def _get_commit_notes(self, old_rev: str, relative_file_path: str) -> str:
                 git_command,
             )
 
-        return p.stdout.decode().strip()
+        return p.stdout.decode(**DECODE_PARAMS).strip()
 
     async def get_full_sha1(self, rev: str) -> str:
         """
@@ -456,7 +462,7 @@ async def get_full_sha1(self, rev: str) -> str:
         p = await self._run(git_command)
 
         if p.returncode != 0:
-            stderr = p.stderr.decode().strip()
+            stderr = p.stderr.decode(**DECODE_PARAMS).strip()
             ambiguous_error = f"error: short SHA1 {rev} is ambiguous\nhint: The candidates are:\n"
             if not stderr.startswith(ambiguous_error):
                 raise errors.UnknownRevision(f"Revision {rev} cannot be found.", git_command)
@@ -469,7 +475,7 @@ async def get_full_sha1(self, rev: str) -> str:
                 )
             raise errors.UnknownRevision(f"Revision {rev} cannot be found.", git_command)
 
-        return p.stdout.decode().strip()
+        return p.stdout.decode(**DECODE_PARAMS).strip()
 
     def _update_available_modules(self) -> Tuple[Installable, ...]:
         """
@@ -530,7 +536,8 @@ async def _run(
                 self._executor,
                 functools.partial(sp_run, *args, stdout=PIPE, stderr=PIPE, **kwargs),
             )
-            stderr = p.stderr.decode().strip()
+            # logging can't use surrogateescape
+            stderr = p.stderr.decode(encoding="utf-8", errors="replace").strip()
             if stderr:
                 if debug_only or p.returncode in valid_exit_codes:
                     log.debug(stderr)
@@ -657,7 +664,7 @@ async def current_branch(self) -> str:
                 f"Could not determine current branch at path: {self.folder_path}", git_command
             )
 
-        return p.stdout.decode().strip()
+        return p.stdout.decode(**DECODE_PARAMS).strip()
 
     async def current_commit(self) -> str:
         """Determine the current commit hash of the repo.
@@ -678,7 +685,7 @@ async def current_commit(self) -> str:
         if p.returncode != 0:
             raise errors.CurrentHashError("Unable to determine commit hash.", git_command)
 
-        return p.stdout.decode().strip()
+        return p.stdout.decode(**DECODE_PARAMS).strip()
 
     async def latest_commit(self, branch: Optional[str] = None) -> str:
         """Determine the latest commit hash of the repo.
@@ -709,7 +716,7 @@ async def latest_commit(self, branch: Optional[str] = None) -> str:
         if p.returncode != 0:
             raise errors.CurrentHashError("Unable to determine latest commit hash.", git_command)
 
-        return p.stdout.decode().strip()
+        return p.stdout.decode(**DECODE_PARAMS).strip()
 
     async def current_url(self, folder: Optional[Path] = None) -> str:
         """
@@ -740,7 +747,7 @@ async def current_url(self, folder: Optional[Path] = None) -> str:
         if p.returncode != 0:
             raise errors.NoRemoteURL("Unable to discover a repo URL.", git_command)
 
-        return p.stdout.decode().strip()
+        return p.stdout.decode(**DECODE_PARAMS).strip()
 
     async def hard_reset(self, branch: Optional[str] = None) -> None:
         """Perform a hard reset on the current repo.


Test patch: