Repository: gitpython-developers/GitPython
Pull request #1853 (2024-03-01 20:00:59)

Updating regex pattern to handle unicode whitespaces.
Replacing the \s whitespace characters with normal spaces (" ") to prevent breaking on unicode whitespace characters (e.g., NBSP). Without this, if a branch name contains a unicode whitespace character that falls under \s, then the branch name will be truncated.


Patch:
diff --git a/git/remote.py b/git/remote.py
index 6ce720ee..bbe1aea9 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -325,7 +325,7 @@ class FetchInfo(IterableObj):
         ERROR,
     ) = [1 << x for x in range(8)]
 
-    _re_fetch_result = re.compile(r"^\s*(.) (\[[\w\s\.$@]+\]|[\w\.$@]+)\s+(.+) -> ([^\s]+)(    \(.*\)?$)?")
+    _re_fetch_result = re.compile(r"^ *(.) (\[[\w \.$@]+\]|[\w\.$@]+) +(.+) -> ([^ ]+)(    \(.*\)?$)?")
 
     _flag_map: Dict[flagKeyLiteral, int] = {
         "!": ERROR,
@@ -891,7 +891,7 @@ class Remote(LazyMixin, IterableObj):
             None,
             progress_handler,
             finalizer=None,
-            decode_streams=False,
+            decode_streams=True,
             kill_after_timeout=kill_after_timeout,
         )
 
@@ -1068,7 +1068,7 @@ class Remote(LazyMixin, IterableObj):
             Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_fetch_options)
 
         proc = self.repo.git.fetch(
-            "--", self, *args, as_process=True, with_stdout=False, universal_newlines=True, v=verbose, **kwargs
+            "--", self, *args, as_process=True, with_stdout=False, universal_newlines=False, v=verbose, **kwargs
         )
         res = self._get_fetch_info_from_stderr(proc, progress, kill_after_timeout=kill_after_timeout)
         if hasattr(self.repo.odb, "update_cache"):
@@ -1122,7 +1122,7 @@ class Remote(LazyMixin, IterableObj):
             Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_pull_options)
 
         proc = self.repo.git.pull(
-            "--", self, refspec, with_stdout=False, as_process=True, universal_newlines=True, v=True, **kwargs
+            "--", self, refspec, with_stdout=False, as_process=True, universal_newlines=False, v=True, **kwargs
         )
         res = self._get_fetch_info_from_stderr(proc, progress, kill_after_timeout=kill_after_timeout)
         if hasattr(self.repo.odb, "update_cache"):
diff --git a/git/util.py b/git/util.py
index 52f9dbda..63c9b1e9 100644
--- a/git/util.py
+++ b/git/util.py
@@ -611,20 +611,6 @@ class RemoteProgress:
             self.error_lines.append(self._cur_line)
             return
 
-        # Find escape characters and cut them away - regex will not work with
-        # them as they are non-ASCII. As git might expect a tty, it will send them.
-        last_valid_index = None
-        for i, c in enumerate(reversed(line_str)):
-            if ord(c) < 32:
-                # its a slice index
-                last_valid_index = -i - 1
-            # END character was non-ASCII
-        # END for each character in line
-        if last_valid_index is not None:
-            line_str = line_str[:last_valid_index]
-        # END cut away invalid part
-        line_str = line_str.rstrip()
-
         cur_count, max_count = None, None
         match = self.re_op_relative.match(line_str)
         if match is None:


Test patch:
diff --git a/test/test_remote.py b/test/test_remote.py
index c0bd11f8..35af8172 100644
--- a/test/test_remote.py
+++ b/test/test_remote.py
@@ -1002,6 +1002,22 @@ class TestRemote(TestBase):
                 assert tmp_file.exists()
                 tmp_file.unlink()
 
+    @with_rw_and_rw_remote_repo("0.1.6")
+    def test_fetch_unsafe_branch_name(self, rw_repo, remote_repo):
+        # Create branch with a name containing a NBSP
+        bad_branch_name = f"branch_with_{chr(160)}_nbsp"
+        Head.create(remote_repo, bad_branch_name)
+
+        # Fetch and get branches
+        remote = rw_repo.remote("origin")
+        branches = remote.fetch()
+
+        # Test for truncated branch name in branches
+        assert f"origin/{bad_branch_name}" in [b.name for b in branches]
+
+        # Cleanup branch
+        Head.delete(remote_repo, bad_branch_name)
+
 
 class TestTimeouts(TestBase):
     @with_rw_repo("HEAD", bare=False)