Przeglądaj źródła

Upgrade to >= python 3.7

Jelmer Vernooij 1 rok temu
rodzic
commit
2fab3300ab

+ 7 - 7
dulwich/cli.py

@@ -132,7 +132,7 @@ class cmd_fetch(Command):
         refs = client.fetch(path, r, progress=sys.stdout.write)
         print("Remote refs:")
         for item in refs.items():
-            print("%s -> %s" % item)
+            print("{} -> {}".format(*item))
 
 
 class cmd_fsck(Command):
@@ -140,7 +140,7 @@ class cmd_fsck(Command):
         opts, args = getopt(args, "", [])
         opts = dict(opts)
         for (obj, msg) in porcelain.fsck("."):
-            print("{}: {}".format(obj, msg))
+            print(f"{obj}: {msg}")
 
 
 class cmd_log(Command):
@@ -203,9 +203,9 @@ class cmd_dump_pack(Command):
             try:
                 print("\t%s" % x[name])
             except KeyError as k:
-                print("\t{}: Unable to resolve base {}".format(name, k))
+                print(f"\t{name}: Unable to resolve base {k}")
             except ApplyDeltaError as e:
-                print("\t{}: Unable to apply delta: {!r}".format(name, e))
+                print(f"\t{name}: Unable to apply delta: {e!r}")
 
 
 class cmd_dump_index(Command):
@@ -488,7 +488,7 @@ class cmd_status(Command):
             for kind, names in status.staged.items():
                 for name in names:
                     sys.stdout.write(
-                        "\t{}: {}\n".format(kind, name.decode(sys.getfilesystemencoding()))
+                        f"\t{kind}: {name.decode(sys.getfilesystemencoding())}\n"
                     )
             sys.stdout.write("\n")
         if status.unstaged:
@@ -511,7 +511,7 @@ class cmd_ls_remote(Command):
             sys.exit(1)
         refs = porcelain.ls_remote(args[0])
         for ref in sorted(refs):
-            sys.stdout.write("{}\t{}\n".format(ref, refs[ref]))
+            sys.stdout.write(f"{ref}\t{refs[ref]}\n")
 
 
 class cmd_ls_tree(Command):
@@ -635,7 +635,7 @@ class cmd_submodule_list(Command):
         parser = argparse.ArgumentParser()
         parser.parse_args(argv)
         for path, sha in porcelain.submodule_list("."):
-            sys.stdout.write(' {} {}\n'.format(sha, path))
+            sys.stdout.write(f' {sha} {path}\n')
 
 
 class cmd_submodule_init(Command):

+ 9 - 26
dulwich/client.py

@@ -49,7 +49,6 @@ from io import BufferedReader, BytesIO
 from typing import (
     IO,
     TYPE_CHECKING,
-    Any,
     Callable,
     Dict,
     Iterable,
@@ -407,7 +406,7 @@ class SendPackResult:
         return super().__getattribute__(name)
 
     def __repr__(self) -> str:
-        return "{}({!r}, {!r})".format(self.__class__.__name__, self.refs, self.agent)
+        return f"{self.__class__.__name__}({self.refs!r}, {self.agent!r})"
 
 
 def _read_shallow_updates(pkt_seq):
@@ -454,12 +453,12 @@ class _v1ReceivePackHeader:
             old_sha1 = old_refs.get(refname, ZERO_SHA)
             if not isinstance(old_sha1, bytes):
                 raise TypeError(
-                    "old sha1 for {!r} is not a bytestring: {!r}".format(refname, old_sha1)
+                    f"old sha1 for {refname!r} is not a bytestring: {old_sha1!r}"
                 )
             new_sha1 = new_refs.get(refname, ZERO_SHA)
             if not isinstance(new_sha1, bytes):
                 raise TypeError(
-                    "old sha1 for {!r} is not a bytestring {!r}".format(refname, new_sha1)
+                    f"old sha1 for {refname!r} is not a bytestring {new_sha1!r}"
                 )
 
             if old_sha1 != new_sha1:
@@ -510,9 +509,6 @@ def _handle_upload_pack_head(
       can_read: function that returns a boolean that indicates
     whether there is extra graph data to read on proto
       depth: Depth for request
-
-    Returns:
-
     """
     assert isinstance(wants, list) and isinstance(wants[0], bytes)
     proto.write_pkt_line(
@@ -582,9 +578,6 @@ def _handle_upload_pack_tail(
       pack_data: Function to call with pack data
       progress: Optional progress reporting function
       rbufsize: Read buffer size
-
-    Returns:
-
     """
     pkt = proto.read_pkt_line()
     while pkt:
@@ -866,9 +859,6 @@ class GitClient:
 
         Args:
           path: Path to the repo to fetch from. (as bytestring)
-
-        Returns:
-
         """
         raise NotImplementedError(self.get_refs)
 
@@ -975,9 +965,6 @@ def check_wants(wants, refs):
     Args:
       wants: Set of object SHAs to fetch
       refs: Refs dictionary to check against
-
-    Returns:
-
     """
     missing = set(wants) - {
         v for (k, v) in refs.items() if not k.endswith(PEELED_TAG_SUFFIX)
@@ -1269,7 +1256,7 @@ class TCPGitClient(TraditionalGitClient):
             self._host, self._port, socket.AF_UNSPEC, socket.SOCK_STREAM
         )
         s = None
-        err = socket.error("no address found for %s" % self._host)
+        err = OSError("no address found for %s" % self._host)
         for (family, socktype, proto, canonname, sockaddr) in sockaddrs:
             s = socket.socket(family, socktype, proto)
             s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
@@ -1465,7 +1452,7 @@ class LocalGitClient(GitClient):
                 old_sha1 = old_refs.get(refname, ZERO_SHA)
                 if new_sha1 != ZERO_SHA:
                     if not target.refs.set_if_equals(refname, old_sha1, new_sha1):
-                        msg = "unable to set {} to {}".format(refname, new_sha1)
+                        msg = f"unable to set {refname} to {new_sha1}"
                         progress(msg)
                         ref_status[refname] = msg
                 else:
@@ -1577,9 +1564,6 @@ class SSHVendor:
           password: Optional ssh password for login or private key
           key_filename: Optional path to private keyfile
           ssh_command: Optional SSH command
-
-        Returns:
-
         """
         raise NotImplementedError(self.run_command)
 
@@ -1624,7 +1608,7 @@ class SubprocessSSHVendor(SSHVendor):
             args.extend(["-i", str(key_filename)])
 
         if username:
-            host = "{}@{}".format(username, host)
+            host = f"{username}@{host}"
         if host.startswith("-"):
             raise StrangeHostname(hostname=host)
         args.append(host)
@@ -1678,7 +1662,7 @@ class PLinkSSHVendor(SSHVendor):
             args.extend(["-i", str(key_filename)])
 
         if username:
-            host = "{}@{}".format(username, host)
+            host = f"{username}@{host}"
         if host.startswith("-"):
             raise StrangeHostname(hostname=host)
         args.append(host)
@@ -1981,8 +1965,7 @@ class AbstractHttpGitClient(GitClient):
             # Something changed (redirect!), so let's update the base URL
             if not resp.redirect_location.endswith(tail):
                 raise GitProtocolError(
-                    "Redirected from URL %s to URL %s without %s"
-                    % (url, resp.redirect_location, tail)
+                    f"Redirected from URL {url} to URL {resp.redirect_location} without {tail}"
                 )
             base_url = urljoin(url, resp.redirect_location[: -len(tail)])
 
@@ -2374,7 +2357,7 @@ def get_transport_and_path(
     location: str,
     config: Optional[Config] = None,
     operation: Optional[str] = None,
-    **kwargs: Any
+    **kwargs
 ) -> Tuple[GitClient, str]:
     """Obtain a git client from a URL.
 

+ 4 - 4
dulwich/config.py

@@ -30,7 +30,9 @@ import os
 import sys
 from contextlib import suppress
 from typing import (
+    Any,
     BinaryIO,
+    Dict,
     Iterable,
     Iterator,
     KeysView,
@@ -40,8 +42,6 @@ from typing import (
     Tuple,
     Union,
     overload,
-    Any,
-    Dict,
 )
 
 from .file import GitFile
@@ -268,7 +268,7 @@ class ConfigDict(Config, MutableMapping[Section, MutableMapping[Name, Value]]):
         self._values = CaseInsensitiveOrderedMultiDict.make(values)
 
     def __repr__(self) -> str:
-        return "{}({!r})".format(self.__class__.__name__, self._values)
+        return f"{self.__class__.__name__}({self._values!r})"
 
     def __eq__(self, other: object) -> bool:
         return isinstance(other, self.__class__) and other._values == self._values
@@ -688,7 +688,7 @@ class StackedConfig(Config):
         self.writable = writable
 
     def __repr__(self) -> str:
-        return "<{} for {!r}>".format(self.__class__.__name__, self.backends)
+        return f"<{self.__class__.__name__} for {self.backends!r}>"
 
     @classmethod
     def default(cls) -> "StackedConfig":

+ 3 - 5
dulwich/contrib/swift.py

@@ -258,8 +258,7 @@ class SwiftConnector:
         if ret.status_code < 200 or ret.status_code >= 300:
             raise SwiftException(
                 "AUTH v1.0 request failed on "
-                + "%s with error code %s (%s)"
-                % (
+                + "{} with error code {} ({})".format(
                     str(auth_httpclient.get_base_url()) + path,
                     ret.status_code,
                     str(ret.items()),
@@ -294,8 +293,7 @@ class SwiftConnector:
         if ret.status_code < 200 or ret.status_code >= 300:
             raise SwiftException(
                 "AUTH v2.0 request failed on "
-                + "%s with error code %s (%s)"
-                % (
+                + "{} with error code {} ({})".format(
                     str(auth_httpclient.get_base_url()) + path,
                     ret.status_code,
                     str(ret.items()),
@@ -495,7 +493,7 @@ class SwiftPackReader:
             self.buff_length = self.buff_length * 2
         offset = self.base_offset
         r = min(self.base_offset + self.buff_length, self.pack_length)
-        ret = self.scon.get_object(self.filename, range="{}-{}".format(offset, r))
+        ret = self.scon.get_object(self.filename, range=f"{offset}-{r}")
         self.buff = ret
 
     def read(self, length):

+ 1 - 1
dulwich/contrib/test_swift.py

@@ -171,7 +171,7 @@ def create_commit(data, marker=b"Default", blob=None):
 def create_commits(length=1, marker=b"Default"):
     data = []
     for i in range(0, length):
-        _marker = ("{}_{}".format(marker, i)).encode()
+        _marker = (f"{marker}_{i}").encode()
         blob, tree, tag, cmt = create_commit(data, _marker)
         data.extend([blob, tree, tag, cmt])
     return data

+ 3 - 3
dulwich/errors.py

@@ -43,12 +43,12 @@ class ChecksumMismatch(Exception):
         if self.extra is None:
             Exception.__init__(
                 self,
-                "Checksum mismatch: Expected {}, got {}".format(expected, got),
+                f"Checksum mismatch: Expected {expected}, got {got}",
             )
         else:
             Exception.__init__(
                 self,
-                "Checksum mismatch: Expected {}, got {}; {}".format(expected, got, extra),
+                f"Checksum mismatch: Expected {expected}, got {got}; {extra}",
             )
 
 
@@ -64,7 +64,7 @@ class WrongObjectException(Exception):
     type_name: str
 
     def __init__(self, sha, *args, **kwargs) -> None:
-        Exception.__init__(self, "{} is not a {}".format(sha, self.type_name))
+        Exception.__init__(self, f"{sha} is not a {self.type_name}")
 
 
 class NotCommitError(WrongObjectException):

+ 3 - 3
dulwich/greenthreads.py

@@ -22,17 +22,17 @@
 
 """Utility module for querying an ObjectStore with gevent."""
 
+from typing import FrozenSet, Optional, Set, Tuple
+
 import gevent
 from gevent import pool
 
-from typing import Set, Tuple, Optional, FrozenSet
-
 from .object_store import (
     MissingObjectFinder,
     _collect_ancestors,
     _collect_filetree_revs,
 )
-from .objects import Commit, Tag, ObjectID
+from .objects import Commit, ObjectID, Tag
 
 
 def _split_commits_and_tags(obj_store, lst, *, ignore_unknown=False, pool=None):

+ 1 - 1
dulwich/ignore.py

@@ -240,7 +240,7 @@ class IgnoreFilter:
     def __repr__(self) -> str:
         path = getattr(self, "_path", None)
         if path is not None:
-            return "{}.from_path({!r})".format(type(self).__name__, path)
+            return f"{type(self).__name__}.from_path({path!r})"
         else:
             return "<%s>" % (type(self).__name__)
 

+ 1 - 1
dulwich/index.py

@@ -330,7 +330,7 @@ class Index:
         return self._filename
 
     def __repr__(self) -> str:
-        return "{}({!r})".format(self.__class__.__name__, self._filename)
+        return f"{self.__class__.__name__}({self._filename!r})"
 
     def write(self) -> None:
         """Write current contents of index to disk."""

+ 6 - 6
dulwich/line_ending.py

@@ -17,7 +17,7 @@
 # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
 # License, Version 2.0.
 #
-"""All line-ending related functions, from conversions to config processing.
+r"""All line-ending related functions, from conversions to config processing.
 
 Line-ending normalization is a complex beast. Here is some notes and details
 about how it seems to work.
@@ -61,23 +61,23 @@ There is multiple variables that impact the normalization.
 First, a repository can contains a ``.gitattributes`` file (or more than one...)
 that can further customize the operation on some file patterns, for example:
 
-    \\*.txt text
+    \*.txt text
 
 Force all ``.txt`` files to be treated as text files and to have their lines
 endings normalized.
 
-    \\*.jpg -text
+    \*.jpg -text
 
 Force all ``.jpg`` files to be treated as binary files and to not have their
 lines endings converted.
 
-    \\*.vcproj text eol=crlf
+    \*.vcproj text eol=crlf
 
 Force all ``.vcproj`` files to be treated as text files and to have their lines
 endings converted into ``CRLF`` in working directory no matter the native EOL of
 the platform.
 
-    \\*.sh text eol=lf
+    \*.sh text eol=lf
 
 Force all ``.sh`` files to be treated as text files and to have their lines
 endings converted into ``LF`` in working directory no matter the native EOL of
@@ -86,7 +86,7 @@ the platform.
 If the ``eol`` attribute is not defined, Git uses the ``core.eol`` configuration
 value described later.
 
-    \\* text=auto
+    \* text=auto
 
 Force all files to be scanned by the text file heuristic detection and to have
 their line endings normalized in case they are detected as text files.

+ 5 - 5
dulwich/lru_cache.py

@@ -131,31 +131,31 @@ class LRUCache(Generic[K, V]):
                 raise AssertionError(
                     "the _most_recently_used entry is not"
                     " supposed to have a previous entry"
-                    " %s" % (node,)
+                    " {}".format(node)
                 )
         while node is not None:
             if node.next_key is _null_key:
                 if node is not self._least_recently_used:
                     raise AssertionError(
-                        "only the last node should have" " no next value: %s" % (node,)
+                        "only the last node should have" " no next value: {}".format(node)
                     )
                 node_next = None
             else:
                 node_next = self._cache[node.next_key]
                 if node_next.prev is not node:
                     raise AssertionError(
-                        "inconsistency found, node.next.prev" " != node: %s" % (node,)
+                        "inconsistency found, node.next.prev" " != node: {}".format(node)
                     )
             if node.prev is None:
                 if node is not self._most_recently_used:
                     raise AssertionError(
                         "only the _most_recently_used should"
-                        " not have a previous node: %s" % (node,)
+                        " not have a previous node: {}".format(node)
                     )
             else:
                 if node.prev.next_key != node.key:
                     raise AssertionError(
-                        "inconsistency found, node.prev.next" " != node: %s" % (node,)
+                        "inconsistency found, node.prev.next" " != node: {}".format(node)
                     )
             yield node
             node = node_next

+ 1 - 1
dulwich/mailmap.py

@@ -20,7 +20,7 @@
 
 """Mailmap file reader."""
 
-from typing import Dict, Tuple, Optional
+from typing import Dict, Optional, Tuple
 
 
 def parse_identity(text):

+ 4 - 4
dulwich/object_store.py

@@ -570,7 +570,7 @@ class PackBasedObjectStore(BaseObjectStore):
             sha = name
             hexsha = None
         else:
-            raise AssertionError("Invalid object name {!r}".format(name))
+            raise AssertionError(f"Invalid object name {name!r}")
         for pack in self._iter_cached_packs():
             try:
                 return pack.get_raw(sha)
@@ -653,7 +653,7 @@ class PackBasedObjectStore(BaseObjectStore):
             sha = sha1
             hexsha = None
         else:
-            raise AssertionError("Invalid object sha1 {!r}".format(sha1))
+            raise AssertionError(f"Invalid object sha1 {sha1!r}")
         for pack in self._iter_cached_packs():
             try:
                 return pack.get_unpacked_object(sha, include_comp=include_comp)
@@ -711,7 +711,7 @@ class DiskObjectStore(PackBasedObjectStore):
         self.pack_compression_level = pack_compression_level
 
     def __repr__(self) -> str:
-        return "<{}({!r})>".format(self.__class__.__name__, self.path)
+        return f"<{self.__class__.__name__}({self.path!r})>"
 
     @classmethod
     def from_config(cls, path, config):
@@ -1005,7 +1005,7 @@ class MemoryObjectStore(BaseObjectStore):
         elif len(sha) == 20:
             return sha_to_hex(sha)
         else:
-            raise ValueError("Invalid sha {!r}".format(sha))
+            raise ValueError(f"Invalid sha {sha!r}")
 
     def contains_loose(self, sha):
         """Check if a particular object is present by SHA1 and is loose."""

+ 3 - 3
dulwich/objects.py

@@ -204,7 +204,7 @@ def check_hexsha(hex, error_msg):
       ObjectFormatException: Raised when the string is not valid
     """
     if not valid_hexsha(hex):
-        raise ObjectFormatException("{} {}".format(error_msg, hex))
+        raise ObjectFormatException(f"{error_msg} {hex}")
 
 
 def check_identity(identity: bytes, error_msg: str) -> None:
@@ -559,7 +559,7 @@ class ShaFile:
         return self.sha().hexdigest().encode("ascii")
 
     def __repr__(self) -> str:
-        return "<{} {}>".format(self.__class__.__name__, self.id)
+        return f"<{self.__class__.__name__} {self.id}>"
 
     def __ne__(self, other):
         """Check whether this object does not match the other."""
@@ -1237,7 +1237,7 @@ def parse_timezone(text):
     #  as an integer (using strtol), which could also be negative.
     #  We do the same for compatibility. See #697828.
     if text[0] not in b"+-":
-        raise ValueError("Timezone must start with + or - (%(text)s)" % vars())
+        raise ValueError("Timezone must start with + or - ({text})".format(**vars()))
     sign = text[:1]
     offset = int(text[1:])
     if sign == b"-":

+ 2 - 2
dulwich/pack.py

@@ -259,7 +259,7 @@ class UnpackedObject:
         return not (self == other)
 
     def __repr__(self) -> str:
-        data = ["{}={!r}".format(s, getattr(self, s)) for s in self.__slots__]
+        data = [f"{s}={getattr(self, s)!r}" for s in self.__slots__]
         return "{}({})".format(self.__class__.__name__, ", ".join(data))
 
 
@@ -2346,7 +2346,7 @@ class Pack:
         return len(self.index)
 
     def __repr__(self) -> str:
-        return "{}({!r})".format(self.__class__.__name__, self._basename)
+        return f"{self.__class__.__name__}({self._basename!r})"
 
     def __iter__(self):
         """Iterate over all the sha1s of the objects in this pack."""

+ 1 - 1
dulwich/patch.py

@@ -102,7 +102,7 @@ def get_summary(commit):
 
 #  Unified Diff
 def _format_range_unified(start, stop):
-    'Convert range to the "ed" format.'
+    """Convert range to the "ed" format."""
     # Per the diff spec at http://www.unix.org/single_unix_specification/
     beginning = start + 1  # lines start numbering with one
     length = stop - start

+ 1 - 1
dulwich/porcelain.py

@@ -1029,7 +1029,7 @@ def tag_create(
     sign=False,
     encoding=DEFAULT_ENCODING
 ):
-    """Creates a tag in git via dulwich calls:
+    """Creates a tag in git via dulwich calls.
 
     Args:
       repo: Path to repository

+ 1 - 2
dulwich/protocol.py

@@ -228,8 +228,7 @@ class Protocol:
         else:
             if len(pkt_contents) + 4 != size:
                 raise GitProtocolError(
-                    "Length of pkt read %04x does not match length prefix %04x"
-                    % (len(pkt_contents) + 4, size)
+                    f"Length of pkt read {len(pkt_contents) + 4:04x} does not match length prefix {size:04x}"
                 )
             return pkt_contents
 

+ 3 - 4
dulwich/refs.py

@@ -23,7 +23,7 @@
 import os
 import warnings
 from contextlib import suppress
-from typing import Dict, Optional, Set, Any
+from typing import Any, Dict, Optional, Set
 
 from .errors import PackedRefsException, RefFormatError
 from .file import GitFile, ensure_dir_exists
@@ -628,7 +628,7 @@ class DiskRefsContainer(RefsContainer):
         self._peeled_refs = None
 
     def __repr__(self) -> str:
-        return "{}({!r})".format(self.__class__.__name__, self.path)
+        return f"{self.__class__.__name__}({self.path!r})"
 
     def subkeys(self, base):
         subkeys = set()
@@ -1279,8 +1279,7 @@ def serialize_refs(store, refs):
             unpeeled, peeled = peel_sha(store, sha)
         except KeyError:
             warnings.warn(
-                "ref %s points at non-present sha %s"
-                % (ref.decode("utf-8", "replace"), sha.decode("ascii")),
+                "ref {} points at non-present sha {}".format(ref.decode("utf-8", "replace"), sha.decode("ascii")),
                 UserWarning,
             )
             continue

+ 4 - 4
dulwich/repo.py

@@ -36,6 +36,7 @@ import warnings
 from io import BytesIO
 from typing import (
     TYPE_CHECKING,
+    Any,
     BinaryIO,
     Callable,
     Dict,
@@ -46,7 +47,6 @@ from typing import (
     Set,
     Tuple,
     Union,
-    Any
 )
 
 if TYPE_CHECKING:
@@ -649,7 +649,7 @@ class BaseRepo:
                 raise NotTagError(ret)
             else:
                 raise Exception(
-                    "Type invalid: {!r} != {!r}".format(ret.type_name, cls.type_name)
+                    f"Type invalid: {ret.type_name!r} != {cls.type_name!r}"
                 )
         return ret
 
@@ -1139,7 +1139,7 @@ class Repo(BaseRepo):
                 bare = True
             else:
                 raise NotGitRepository(
-                    "No git repository was found at %(path)s" % dict(path=root)
+                    "No git repository was found at {path}".format(**dict(path=root))
                 )
 
         self.bare = bare
@@ -1253,7 +1253,7 @@ class Repo(BaseRepo):
             except NotGitRepository:
                 path, remaining = os.path.split(path)
         raise NotGitRepository(
-            "No git repository was found at %(path)s" % dict(path=start)
+            "No git repository was found at {path}".format(**dict(path=start))
         )
 
     def controldir(self):

+ 2 - 2
dulwich/server.py

@@ -189,7 +189,7 @@ class DictBackend(Backend):
             return self.repos[path]
         except KeyError as exc:
             raise NotGitRepository(
-                "No git repository was found at %(path)s" % dict(path=path)
+                "No git repository was found at {path}".format(**dict(path=path))
             ) from exc
 
 
@@ -206,7 +206,7 @@ class FileSystemBackend(Backend):
         normcase_abspath = os.path.normcase(abspath)
         normcase_root = os.path.normcase(self.root)
         if not normcase_abspath.startswith(normcase_root):
-            raise NotGitRepository("Path {!r} not inside root {!r}".format(path, self.root))
+            raise NotGitRepository(f"Path {path!r} not inside root {self.root!r}")
         return Repo(abspath)
 
 

+ 2 - 2
dulwich/tests/compat/server_utils.py

@@ -78,12 +78,12 @@ class ServerTests:
         self._new_repo = self.import_repo("server_new.export")
 
     def url(self, port):
-        return "{}://localhost:{}/".format(self.protocol, port)
+        return f"{self.protocol}://localhost:{port}/"
 
     def branch_args(self, branches=None):
         if branches is None:
             branches = ["master", "branch"]
-        return ["{}:{}".format(b, b) for b in branches]
+        return [f"{b}:{b}" for b in branches]
 
     def test_push_to_dulwich(self):
         self.import_repos()

+ 1 - 1
dulwich/tests/compat/test_client.py

@@ -633,7 +633,7 @@ class HTTPGitServer(http.server.HTTPServer):
         self.server_name = "localhost"
 
     def get_url(self):
-        return "http://{}:{}/".format(self.server_name, self.server_port)
+        return f"http://{self.server_name}:{self.server_port}/"
 
 
 class DulwichHttpClientTest(CompatTestCase, DulwichClientTestBase):

+ 2 - 2
dulwich/tests/compat/utils.py

@@ -92,7 +92,7 @@ def require_git_version(required_version, git_path=_DEFAULT_GIT):
     found_version = git_version(git_path=git_path)
     if found_version is None:
         raise SkipTest(
-            "Test requires git >= {}, but c git not found".format(required_version)
+            f"Test requires git >= {required_version}, but c git not found"
         )
 
     if len(required_version) > _VERSION_LEN:
@@ -110,7 +110,7 @@ def require_git_version(required_version, git_path=_DEFAULT_GIT):
         required_version = ".".join(map(str, required_version))
         found_version = ".".join(map(str, found_version))
         raise SkipTest(
-            "Test requires git >= {}, found {}".format(required_version, found_version)
+            f"Test requires git >= {required_version}, found {found_version}"
         )
 
 

+ 4 - 4
dulwich/tests/test_client.py

@@ -23,9 +23,9 @@ import os
 import shutil
 import sys
 import tempfile
-from typing import Dict
 import warnings
 from io import BytesIO
+from typing import Dict
 from unittest.mock import patch
 from urllib.parse import quote as urlquote
 from urllib.parse import urlparse
@@ -1046,7 +1046,7 @@ class HttpGitClientTests(TestCase):
         self.assertEqual(original_password, c._password)
 
         basic_auth = c.pool_manager.headers["authorization"]
-        auth_string = "{}:{}".format(original_username, original_password)
+        auth_string = f"{original_username}:{original_password}"
         b64_credentials = base64.b64encode(auth_string.encode("latin1"))
         expected_basic_auth = "Basic %s" % b64_credentials.decode("latin1")
         self.assertEqual(basic_auth, expected_basic_auth)
@@ -1518,7 +1518,7 @@ class PLinkSSHVendorTests(TestCase):
                 break
         else:
             raise AssertionError(
-                "Expected warning {!r} not in {!r}".format(expected_warning, warnings_list)
+                f"Expected warning {expected_warning!r} not in {warnings_list!r}"
             )
 
         args = command.proc.args
@@ -1563,7 +1563,7 @@ class PLinkSSHVendorTests(TestCase):
                 break
         else:
             raise AssertionError(
-                "Expected warning {!r} not in {!r}".format(expected_warning, warnings_list)
+                f"Expected warning {expected_warning!r} not in {warnings_list!r}"
             )
 
         args = command.proc.args

+ 0 - 1
dulwich/tests/test_graph.py

@@ -1,5 +1,4 @@
 # test_index.py -- Tests for merge
-# encoding: utf-8
 # Copyright (c) 2020 Kevin B. Hendricks, Stratford Ontario Canada
 #
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU

+ 3 - 4
dulwich/tests/test_ignore.py

@@ -98,8 +98,7 @@ class TranslateTests(TestCase):
             self.assertEqual(
                 regex,
                 translate(pattern),
-                "orig pattern: %r, regex: %r, expected: %r"
-                % (pattern, translate(pattern), regex),
+                f"orig pattern: {pattern!r}, regex: {translate(pattern)!r}, expected: {regex!r}",
             )
 
 
@@ -133,14 +132,14 @@ class MatchPatternTests(TestCase):
         for (path, pattern) in POSITIVE_MATCH_TESTS:
             self.assertTrue(
                 match_pattern(path, pattern),
-                "path: {!r}, pattern: {!r}".format(path, pattern),
+                f"path: {path!r}, pattern: {pattern!r}",
             )
 
     def test_no_matches(self):
         for (path, pattern) in NEGATIVE_MATCH_TESTS:
             self.assertFalse(
                 match_pattern(path, pattern),
-                "path: {!r}, pattern: {!r}".format(path, pattern),
+                f"path: {path!r}, pattern: {pattern!r}",
             )
 
 

+ 1 - 2
dulwich/tests/test_index.py

@@ -1,5 +1,4 @@
 # test_index.py -- Tests for the git index
-# encoding: utf-8
 # Copyright (C) 2008-2009 Jelmer Vernooij <jelmer@jelmer.uk>
 #
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
@@ -222,7 +221,7 @@ class CommitTreeTests(TestCase):
 
 class CleanupModeTests(TestCase):
     def assertModeEqual(self, expected, got):
-        self.assertEqual(expected, got, "{:o} != {:o}".format(expected, got))
+        self.assertEqual(expected, got, f"{expected:o} != {got:o}")
 
     def test_file(self):
         self.assertModeEqual(0o100644, cleanup_mode(0o100000))

+ 0 - 1
dulwich/tests/test_line_ending.py

@@ -1,5 +1,4 @@
 # test_line_ending.py -- Tests for the line ending functions
-# encoding: utf-8
 # Copyright (C) 2018-2019 Boris Feld <boris.feld@comet.ml>
 #
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU

+ 2 - 2
dulwich/tests/test_missing_obj_finder.py

@@ -39,14 +39,14 @@ class MissingObjectFinderTest(TestCase):
             self.assertIn(
                 sha,
                 expected,
-                "({},{}) erroneously reported as missing".format(sha, path)
+                f"({sha},{path}) erroneously reported as missing"
             )
             expected.remove(sha)
 
         self.assertEqual(
             len(expected),
             0,
-            "some objects are not reported as missing: {}".format(expected),
+            f"some objects are not reported as missing: {expected}",
         )
 
 

+ 1 - 1
dulwich/tests/test_objects.py

@@ -1133,7 +1133,7 @@ class TagParseTests(ShaFileCheckTests):
 
     def test_check_tag_with_overflow_time(self):
         """Date with overflow should raise an ObjectFormatException when checked."""
-        author = "Some Dude <some@dude.org> {} +0000".format(MAX_TIME + 1)
+        author = f"Some Dude <some@dude.org> {MAX_TIME + 1} +0000"
         tag = Tag.from_string(self.make_tag_text(tagger=(author.encode())))
         with self.assertRaises(ObjectFormatException):
             tag.check()

+ 3 - 3
dulwich/tests/test_repository.py

@@ -450,7 +450,7 @@ class RepositoryRootTests(TestCase):
         o.close()
         bar_path = os.path.join(tmp_dir, 't', 'bar')
         if sys.platform == 'win32':
-            with open(bar_path, 'r') as f:
+            with open(bar_path) as f:
                 self.assertEqual('foo', f.read())
         else:
             self.assertEqual('foo', os.readlink(bar_path))
@@ -467,7 +467,7 @@ class RepositoryRootTests(TestCase):
         o.do_commit(b"add symlink")
 
         t = o.clone(os.path.join(tmp_dir, "t"), symlinks=False)
-        with open(os.path.join(tmp_dir, "t", 'bar'), 'r') as f:
+        with open(os.path.join(tmp_dir, "t", 'bar')) as f:
             self.assertEqual('foo', f.read())
 
         t.close()
@@ -846,7 +846,7 @@ exit 1
                 break
         else:
             raise AssertionError(
-                "Expected warning {!r} not in {!r}".format(expected_warning, warnings_list)
+                f"Expected warning {expected_warning!r} not in {warnings_list!r}"
             )
         self.assertEqual([commit_sha], r[commit_sha2].parents)
 

+ 1 - 1
dulwich/walk.py

@@ -24,7 +24,7 @@
 import collections
 import heapq
 from itertools import chain
-from typing import Deque, List, Optional, Set, Tuple, Dict
+from typing import Deque, Dict, List, Optional, Set, Tuple
 
 from .diff_tree import (
     RENAME_CHANGE_TYPES,

+ 1 - 1
examples/clone.py

@@ -19,7 +19,7 @@ _, args = getopt(sys.argv, "", [])
 
 
 if len(args) < 2:
-    print("usage: {} host:path path".format(args[0]))
+    print(f"usage: {args[0]} host:path path")
     sys.exit(1)
 
 elif len(args) < 3:

+ 1 - 1
examples/latest_change.py

@@ -7,7 +7,7 @@ import time
 from dulwich.repo import Repo
 
 if len(sys.argv) < 2:
-    print("usage: {} filename".format(sys.argv[0]))
+    print(f"usage: {sys.argv[0]} filename")
     sys.exit(1)
 
 r = Repo(".")

+ 1 - 1
examples/rename-branch.py

@@ -27,4 +27,4 @@ def update_refs(refs):
 
 
 client.send_pack(path, update_refs, generate_pack_data)
-print("Renamed {} to {}".format(args.old_ref, args.new_ref))
+print(f"Renamed {args.old_ref} to {args.new_ref}")

+ 24 - 1
pyproject.toml

@@ -73,11 +73,34 @@ select = [
     "D",
     "E",
     "F",
-    "I"
+    "I",
+    "UP",
 ]
 ignore = [
+    "ANN001",
+    "ANN002",
+    "ANN003",
     "ANN101",  # missing-type-self
+    "ANN102",
+    "ANN201",
+    "ANN202",
+    "ANN204",
+    "ANN205",
+    "ANN206",
+    "D100",
+    "D101",
+    "D102",
+    "D103",
+    "D104",
+    "D105",
+    "D107",
+    "D204",
+    "D205",
+    "D417",
+    "E501",  # line too long
+    "E741",  # ambiguous variable name
 ]
+target-version = "py37"
 
 [tool.ruff.pydocstyle]
 convention = "google"

+ 1 - 1
setup.py

@@ -7,7 +7,7 @@ import sys
 
 from setuptools import Extension, setup
 
-if sys.version_info < (3, 6):
+if sys.version_info < (3, 7):
     raise Exception(
         'Dulwich only supports Python 3.6 and later. '
         'For 2.7 support, please install a version prior to 0.20')