def _parse_progress_line(self, line: AnyStr) -> None:
        """Parse progress information from the given line as retrieved by
        :manpage:`git-push(1)` or :manpage:`git-fetch(1)`.

        - Lines that do not contain progress info are stored in :attr:`other_lines`.
        - Lines that seem to contain an error (i.e. start with ``error:`` or ``fatal:``)
          are stored in :attr:`error_lines`.
        """
        # handle
        # Counting objects: 4, done.
        # Compressing objects:  50% (1/2)
        # Compressing objects: 100% (2/2)
        # Compressing objects: 100% (2/2), done.
        if isinstance(line, bytes):  # mypy argues about ternary assignment.
            line_str = line.decode("utf-8")
        else:
            line_str = line
        self._cur_line = line_str

        if self._cur_line.startswith(("error:", "fatal:")):
            self.error_lines.append(self._cur_line)
            return

        cur_count, max_count = None, None
        match = self.re_op_relative.match(line_str)
        if match is None:
            match = self.re_op_absolute.match(line_str)

        if not match:
            self.line_dropped(line_str)
            self.other_lines.append(line_str)
            return
        # END could not get match

        op_code = 0
        _remote, op_name, _percent, cur_count, max_count, message = match.groups()

        # Get operation ID.
        if op_name == "Counting objects":
            op_code |= self.COUNTING
        elif op_name == "Compressing objects":
            op_code |= self.COMPRESSING
        elif op_name == "Writing objects":
            op_code |= self.WRITING
        elif op_name == "Receiving objects":
            op_code |= self.RECEIVING
        elif op_name == "Resolving deltas":
            op_code |= self.RESOLVING
        elif op_name == "Finding sources":
            op_code |= self.FINDING_SOURCES
        elif op_name == "Checking out files":
            op_code |= self.CHECKING_OUT
        else:
            # Note: On Windows it can happen that partial lines are sent.
            # Hence we get something like "CompreReceiving objects", which is
            # a blend of "Compressing objects" and "Receiving objects".
            # This can't really be prevented, so we drop the line verbosely
            # to make sure we get informed in case the process spits out new
            # commands at some point.
            self.line_dropped(line_str)
            # Note: Don't add this line to the other lines, as we have to silently
            # drop it.
            return
        # END handle op code

        # Figure out stage.
        if op_code not in self._seen_ops:
            self._seen_ops.append(op_code)
            op_code |= self.BEGIN
        # END begin opcode

        if message is None:
            message = ""
        # END message handling

        message = message.strip()
        if message.endswith(self.DONE_TOKEN):
            op_code |= self.END
            message = message[: -len(self.DONE_TOKEN)]
        # END end message handling
        message = message.strip(self.TOKEN_SEPARATOR)

        self.update(
            op_code,
            cur_count and float(cur_count),
            max_count and float(max_count),
            message,
        )
Parse progress information from the given line as retrieved by
        :manpage:`git-push(1)` or :manpage:`git-fetch(1)`.

        - Lines that do not contain progress info are stored in :attr:`other_lines`.
        - Lines that seem to contain an error (i.e. start with ``error:`` or ``fatal:``)
          are stored in :attr:`error_lines`.
        
git/util.py