2
0
Jelmer Vernooij 10 сар өмнө
parent
commit
538442329d

+ 3 - 3
Cargo.lock

@@ -22,7 +22,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "diff-tree-py"
-version = "0.22.0"
+version = "0.22.2"
 dependencies = [
  "pyo3",
 ]
@@ -72,7 +72,7 @@ dependencies = [
 
 [[package]]
 name = "objects-py"
-version = "0.22.0"
+version = "0.22.2"
 dependencies = [
  "memchr",
  "pyo3",
@@ -86,7 +86,7 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "pack-py"
-version = "0.22.0"
+version = "0.22.2"
 dependencies = [
  "memchr",
  "pyo3",

+ 1 - 1
dulwich/archive.py

@@ -84,7 +84,7 @@ def tar_stream(store, tree, mtime, prefix=b"", format=""):
       Bytestrings
     """
     buf = BytesIO()
-    with closing(tarfile.open(None, "w:%s" % format, buf)) as tar:
+    with closing(tarfile.open(None, f"w:{format}", buf)) as tar:
         if format == "gz":
             # Manually correct the gzip header file modification time so that
             # archives created from the same Git tree are always identical.

+ 1 - 1
dulwich/bundle.py

@@ -99,7 +99,7 @@ def read_bundle(f):
         return _read_bundle(f, 2)
     if firstline == b"# v3 git bundle\n":
         return _read_bundle(f, 3)
-    raise AssertionError("unsupported bundle format header: %r" % firstline)
+    raise AssertionError(f"unsupported bundle format header: {firstline!r}")
 
 
 def write_bundle(f, bundle):

+ 12 - 12
dulwich/cli.py

@@ -202,14 +202,14 @@ class cmd_dump_pack(Command):
 
         basename, _ = os.path.splitext(args[0])
         x = Pack(basename)
-        print("Object names checksum: %s" % x.name())
-        print("Checksum: %s" % sha_to_hex(x.get_stored_checksum()))
+        print(f"Object names checksum: {x.name()}")
+        print(f"Checksum: {sha_to_hex(x.get_stored_checksum())}")
         if not x.check():
             print("CHECKSUM DOES NOT MATCH")
         print("Length: %d" % len(x))
         for name in x:
             try:
-                print("\t%s" % x[name])
+                print(f"\t{x[name]}")
             except KeyError as k:
                 print(f"\t{name}: Unable to resolve base {k}")
             except ApplyDeltaError as e:
@@ -284,7 +284,7 @@ class cmd_clone(Command):
                 branch=options.branch,
             )
         except GitProtocolError as e:
-            print("%s" % e)
+            print(f"{e}")
 
 
 class cmd_commit(Command):
@@ -465,7 +465,7 @@ class cmd_write_tree(Command):
     def run(self, args):
         parser = optparse.OptionParser()
         options, args = parser.parse_args(args)
-        sys.stdout.write("%s\n" % porcelain.write_tree("."))
+        sys.stdout.write("{}\n".format(porcelain.write_tree(".")))
 
 
 class cmd_receive_pack(Command):
@@ -510,12 +510,12 @@ class cmd_status(Command):
         if status.unstaged:
             sys.stdout.write("Changes not staged for commit:\n\n")
             for name in status.unstaged:
-                sys.stdout.write("\t%s\n" % name.decode(sys.getfilesystemencoding()))
+                sys.stdout.write(f"\t{name.decode(sys.getfilesystemencoding())}\n")
             sys.stdout.write("\n")
         if status.untracked:
             sys.stdout.write("Untracked files:\n\n")
             for name in status.untracked:
-                sys.stdout.write("\t%s\n" % name)
+                sys.stdout.write(f"\t{name}\n")
             sys.stdout.write("\n")
 
 
@@ -624,13 +624,13 @@ class SuperCommand(Command):
 
     def run(self, args):
         if not args and not self.default_command:
-            print("Supported subcommands: %s" % ", ".join(self.subcommands.keys()))
+            print("Supported subcommands: {}".format(", ".join(self.subcommands.keys())))
             return False
         cmd = args[0]
         try:
             cmd_kls = self.subcommands[cmd]
         except KeyError:
-            print("No such subcommand: %s" % args[0])
+            print(f"No such subcommand: {args[0]}")
             return False
         return cmd_kls().run(args[1:])
 
@@ -746,7 +746,7 @@ class cmd_help(Command):
         if options.all:
             print("Available commands:")
             for cmd in sorted(commands):
-                print("  %s" % cmd)
+                print(f"  {cmd}")
         else:
             print(
                 """\
@@ -810,14 +810,14 @@ def main(argv=None):
         argv = sys.argv[1:]
 
     if len(argv) < 1:
-        print("Usage: dulwich <%s> [OPTIONS...]" % ("|".join(commands.keys())))
+        print("Usage: dulwich <{}> [OPTIONS...]".format("|".join(commands.keys())))
         return 1
 
     cmd = argv[0]
     try:
         cmd_kls = commands[cmd]
     except KeyError:
-        print("No such subcommand: %s" % cmd)
+        print(f"No such subcommand: {cmd}")
         return 1
     # TODO(jelmer): Return non-0 on errors
     return cmd_kls().run(argv[1:])

+ 16 - 16
dulwich/client.py

@@ -130,7 +130,7 @@ class InvalidWants(Exception):
 
     def __init__(self, wants) -> None:
         Exception.__init__(
-            self, "requested wants not in server provided refs: %r" % wants
+            self, f"requested wants not in server provided refs: {wants!r}"
         )
 
 
@@ -216,7 +216,7 @@ class ReportStatusParser:
             elif status == b"ok":
                 yield rest, None
             else:
-                raise GitProtocolError("invalid ref status %r" % status)
+                raise GitProtocolError(f"invalid ref status {status!r}")
 
     def handle_packet(self, pkt):
         """Handle a packet.
@@ -416,13 +416,13 @@ def _read_shallow_updates(pkt_seq):
         try:
             cmd, sha = pkt.split(b" ", 1)
         except ValueError:
-            raise GitProtocolError("unknown command %s" % pkt)
+            raise GitProtocolError(f"unknown command {pkt}")
         if cmd == COMMAND_SHALLOW:
             new_shallow.add(sha.strip())
         elif cmd == COMMAND_UNSHALLOW:
             new_unshallow.add(sha.strip())
         else:
-            raise GitProtocolError("unknown command %s" % pkt)
+            raise GitProtocolError(f"unknown command {pkt}")
     return (new_shallow, new_unshallow)
 
 
@@ -451,7 +451,7 @@ class _v1ReceivePackHeader:
 
         for refname in new_refs:
             if not isinstance(refname, bytes):
-                raise TypeError("refname is not a bytestring: %r" % refname)
+                raise TypeError(f"refname is not a bytestring: {refname!r}")
             old_sha1 = old_refs.get(refname, ZERO_SHA)
             if not isinstance(old_sha1, bytes):
                 raise TypeError(
@@ -551,7 +551,7 @@ def _handle_upload_pack_head(proto, capabilities, graph_walker, wants, can_read,
                     break
                 else:
                     raise AssertionError(
-                        "%s not in ('continue', 'ready', 'common)" % parts[2]
+                        f"{parts[2]} not in ('continue', 'ready', 'common)"
                     )
         have = next(graph_walker)
     proto.write_pkt_line(COMMAND_DONE + b"\n")
@@ -1238,7 +1238,7 @@ class TraditionalGitClient(GitClient):
             elif pkt.startswith(b"ERR "):
                 raise GitProtocolError(pkt[4:].rstrip(b"\n").decode("utf-8", "replace"))
             else:
-                raise AssertionError("invalid response %r" % pkt)
+                raise AssertionError(f"invalid response {pkt!r}")
             ret = proto.read_pkt_line()
             if ret is not None:
                 raise AssertionError("expected pkt tail")
@@ -1282,7 +1282,7 @@ class TCPGitClient(TraditionalGitClient):
             self._host, self._port, socket.AF_UNSPEC, socket.SOCK_STREAM
         )
         s = None
-        err = OSError("no address found for %s" % self._host)
+        err = OSError(f"no address found for {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)
@@ -1487,7 +1487,7 @@ class LocalGitClient(GitClient):
                         ref_status[refname] = msg
                 else:
                     if not target.refs.remove_if_equals(refname, old_sha1):
-                        progress("unable to remove %s" % refname)
+                        progress(f"unable to remove {refname}")
                         ref_status[refname] = "unable to remove"
 
         return SendPackResult(new_refs, ref_status=ref_status)
@@ -1814,7 +1814,7 @@ class SSHGitClient(TraditionalGitClient):
 def default_user_agent_string():
     # Start user agent with "git/", because GitHub requires this. :-( See
     # https://github.com/jelmer/dulwich/issues/562 for details.
-    return "git/dulwich/%s" % ".".join([str(x) for x in dulwich.__version__])
+    return "git/dulwich/{}".format(".".join([str(x) for x in dulwich.__version__]))
 
 
 def default_urllib3_manager(
@@ -2008,7 +2008,7 @@ class AbstractHttpGitClient(GitClient):
         tail = "info/refs"
         headers = {"Accept": "*/*"}
         if self.dumb is not True:
-            tail += "?service=%s" % service.decode("ascii")
+            tail += "?service={}".format(service.decode("ascii"))
         url = urljoin(base_url, tail)
         resp, read = self._http_request(url, headers)
 
@@ -2035,7 +2035,7 @@ class AbstractHttpGitClient(GitClient):
                     ) from exc
                 if pkt.rstrip(b"\n") != (b"# service=" + service):
                     raise GitProtocolError(
-                        "unexpected first line %r from smart server" % pkt
+                        f"unexpected first line {pkt!r} from smart server"
                     )
                 return (*read_pkt_refs(proto.read_pkt_seq()), base_url)
             else:
@@ -2051,9 +2051,9 @@ class AbstractHttpGitClient(GitClient):
         """
         assert url[-1] == "/"
         url = urljoin(url, service)
-        result_content_type = "application/x-%s-result" % service
+        result_content_type = f"application/x-{service}-result"
         headers = {
-            "Content-Type": "application/x-%s-request" % service,
+            "Content-Type": f"application/x-{service}-request",
             "Accept": result_content_type,
         }
         if isinstance(data, bytes):
@@ -2061,7 +2061,7 @@ class AbstractHttpGitClient(GitClient):
         resp, read = self._http_request(url, headers, data)
         if resp.content_type.split(";")[0] != result_content_type:
             raise GitProtocolError(
-                "Invalid content-type from server: %s" % resp.content_type
+                f"Invalid content-type from server: {resp.content_type}"
             )
         return resp, read
 
@@ -2398,7 +2398,7 @@ def _get_transport_and_path_from_url(url, config, operation, **kwargs):
             parsed.path,
         )
 
-    raise ValueError("unknown scheme '%s'" % parsed.scheme)
+    raise ValueError(f"unknown scheme '{parsed.scheme}'")
 
 
 def parse_rsync_url(location: str) -> Tuple[Optional[str], str, str]:

+ 6 - 6
dulwich/config.py

@@ -203,7 +203,7 @@ class Config:
             return True
         elif value.lower() == b"false":
             return False
-        raise ValueError("not a valid boolean string: %r" % value)
+        raise ValueError(f"not a valid boolean string: {value!r}")
 
     def set(
         self, section: SectionLike, name: NameLike, value: Union[ValueLike, bool]
@@ -492,15 +492,15 @@ def _parse_section_header_line(line: bytes) -> Tuple[Section, bytes]:
     section: Section
     if len(pts) == 2:
         if pts[1][:1] != b'"' or pts[1][-1:] != b'"':
-            raise ValueError("Invalid subsection %r" % pts[1])
+            raise ValueError(f"Invalid subsection {pts[1]!r}")
         else:
             pts[1] = pts[1][1:-1]
         if not _check_section_name(pts[0]):
-            raise ValueError("invalid section name %r" % pts[0])
+            raise ValueError(f"invalid section name {pts[0]!r}")
         section = (pts[0], pts[1])
     else:
         if not _check_section_name(pts[0]):
-            raise ValueError("invalid section name %r" % pts[0])
+            raise ValueError(f"invalid section name {pts[0]!r}")
         pts = pts[0].split(b".", 1)
         if len(pts) == 2:
             section = (pts[0], pts[1])
@@ -540,7 +540,7 @@ class ConfigFile(ConfigDict):
                 if _strip_comments(line).strip() == b"":
                     continue
                 if section is None:
-                    raise ValueError("setting %r without section" % line)
+                    raise ValueError(f"setting {line!r} without section")
                 try:
                     setting, value = line.split(b"=", 1)
                 except ValueError:
@@ -548,7 +548,7 @@ class ConfigFile(ConfigDict):
                     value = b"true"
                 setting = setting.strip()
                 if not _check_variable_name(setting):
-                    raise ValueError("invalid variable name %r" % setting)
+                    raise ValueError(f"invalid variable name {setting!r}")
                 if value.endswith(b"\\\n"):
                     continuation = value[:-2]
                 elif value.endswith(b"\\\r\n"):

+ 14 - 14
dulwich/contrib/swift.py

@@ -138,7 +138,7 @@ def load_conf(path=None, file=None):
     else:
         confpath = path
     if not os.path.isfile(confpath):
-        raise Exception("Unable to read configuration file %s" % confpath)
+        raise Exception(f"Unable to read configuration file {confpath}")
     conf.read(confpath)
     return conf
 
@@ -312,7 +312,7 @@ class SwiftConnector:
             return None
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException(
-                "HEAD request failed with error code %s" % ret.status_code
+                f"HEAD request failed with error code {ret.status_code}"
             )
         return True
 
@@ -326,7 +326,7 @@ class SwiftConnector:
             ret = self.httpclient.request("PUT", self.base_path)
             if ret.status_code < 200 or ret.status_code > 300:
                 raise SwiftException(
-                    "PUT request failed with error code %s" % ret.status_code
+                    f"PUT request failed with error code {ret.status_code}"
                 )
 
     def get_container_objects(self):
@@ -342,7 +342,7 @@ class SwiftConnector:
             return None
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException(
-                "GET request failed with error code %s" % ret.status_code
+                f"GET request failed with error code {ret.status_code}"
             )
         content = ret.read()
         return json.loads(content)
@@ -361,7 +361,7 @@ class SwiftConnector:
             return None
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException(
-                "HEAD request failed with error code %s" % ret.status_code
+                f"HEAD request failed with error code {ret.status_code}"
             )
         resp_headers = {}
         for header, value in ret.items():
@@ -395,7 +395,7 @@ class SwiftConnector:
 
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException(
-                "PUT request failed with error code %s" % ret.status_code
+                f"PUT request failed with error code {ret.status_code}"
             )
 
     def get_object(self, name, range=None):
@@ -410,14 +410,14 @@ class SwiftConnector:
         """
         headers = {}
         if range:
-            headers["Range"] = "bytes=%s" % range
+            headers["Range"] = f"bytes={range}"
         path = self.base_path + "/" + name
         ret = self.httpclient.request("GET", path, headers=headers)
         if ret.status_code == 404:
             return None
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException(
-                "GET request failed with error code %s" % ret.status_code
+                f"GET request failed with error code {ret.status_code}"
             )
         content = ret.read()
 
@@ -437,7 +437,7 @@ class SwiftConnector:
         ret = self.httpclient.request("DELETE", path)
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException(
-                "DELETE request failed with error code %s" % ret.status_code
+                f"DELETE request failed with error code {ret.status_code}"
             )
 
     def del_root(self):
@@ -451,7 +451,7 @@ class SwiftConnector:
         ret = self.httpclient.request("DELETE", self.base_path)
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException(
-                "DELETE request failed with error code %s" % ret.status_code
+                f"DELETE request failed with error code {ret.status_code}"
             )
 
 
@@ -680,7 +680,7 @@ class SwiftObjectStore(PackBasedObjectStore):
             if entries:
                 basename = posixpath.join(
                     self.pack_dir,
-                    "pack-%s" % iter_sha1(entry[0] for entry in entries),
+                    f"pack-{iter_sha1(entry[0] for entry in entries)}",
                 )
                 index = BytesIO()
                 write_pack_index_v2(index, entries, pack.get_stored_checksum())
@@ -868,10 +868,10 @@ class SwiftRepo(BaseRepo):
         self.scon = SwiftConnector(self.root, self.conf)
         objects = self.scon.get_container_objects()
         if not objects:
-            raise Exception("There is not any GIT repo here : %s" % self.root)
+            raise Exception(f"There is not any GIT repo here : {self.root}")
         objects = [o["name"].split("/")[0] for o in objects]
         if OBJECTDIR not in objects:
-            raise Exception("This repository (%s) is not bare." % self.root)
+            raise Exception(f"This repository ({self.root}) is not bare.")
         self.bare = True
         self._controldir = self.root
         object_store = SwiftObjectStore(self.scon)
@@ -1014,7 +1014,7 @@ def main(argv=sys.argv):
 
     cmd = sys.argv[1]
     if cmd not in commands:
-        print("No such subcommand: %s" % cmd)
+        print(f"No such subcommand: {cmd}")
         sys.exit(1)
     commands[cmd](sys.argv[2:])
 

+ 4 - 4
dulwich/errors.py

@@ -95,14 +95,14 @@ class MissingCommitError(Exception):
 
     def __init__(self, sha, *args, **kwargs) -> None:
         self.sha = sha
-        Exception.__init__(self, "%s is not in the revision store" % sha)
+        Exception.__init__(self, f"{sha} is not in the revision store")
 
 
 class ObjectMissing(Exception):
     """Indicates that a requested object is missing."""
 
     def __init__(self, sha, *args, **kwargs) -> None:
-        Exception.__init__(self, "%s is not in the pack" % sha)
+        Exception.__init__(self, f"{sha} is not in the pack")
 
 
 class ApplyDeltaError(Exception):
@@ -158,8 +158,8 @@ class UnexpectedCommandError(GitProtocolError):
         if command is None:
             command = "flush-pkt"
         else:
-            command = "command %s" % command
-        super().__init__("Protocol got unexpected %s" % command)
+            command = f"command {command}"
+        super().__init__(f"Protocol got unexpected {command}")
 
 
 class FileFormatException(Exception):

+ 1 - 1
dulwich/fastexport.py

@@ -193,7 +193,7 @@ class GitImportProcessor(processor.ImportProcessor):
             elif filecmd.name == b"filedeleteall":
                 self._contents = {}
             else:
-                raise Exception("Command %s not supported" % filecmd.name)
+                raise Exception(f"Command {filecmd.name} not supported")
         commit.tree = commit_tree(
             self.repo.object_store,
             ((path, hexsha, mode) for (path, (mode, hexsha)) in self._contents.items()),

+ 1 - 1
dulwich/file.py

@@ -207,7 +207,7 @@ class _GitFile:
 
     def __del__(self) -> None:
         if not getattr(self, "_closed", True):
-            warnings.warn("unclosed %r" % self, ResourceWarning, stacklevel=2)
+            warnings.warn(f"unclosed {self!r}", ResourceWarning, stacklevel=2)
             self.abort()
 
     def __enter__(self):

+ 1 - 1
dulwich/greenthreads.py

@@ -57,7 +57,7 @@ def _split_commits_and_tags(obj_store, lst, *, ignore_unknown=False, pool=None):
                 tags.add(sha)
                 commits.add(o.object[1])
             else:
-                raise KeyError("Not a commit or a tag: %s" % sha)
+                raise KeyError(f"Not a commit or a tag: {sha}")
 
     jobs = [pool.spawn(find_commit_type, s) for s in lst]
     gevent.joinall(jobs)

+ 2 - 2
dulwich/ignore.py

@@ -240,7 +240,7 @@ class IgnoreFilter:
         if path is not None:
             return f"{type(self).__name__}.from_path({path!r})"
         else:
-            return "<%s>" % (type(self).__name__)
+            return f"<{type(self).__name__}>"
 
 
 class IgnoreFilterStack:
@@ -323,7 +323,7 @@ class IgnoreFilterManager:
           Iterator over Pattern instances
         """
         if os.path.isabs(path):
-            raise ValueError("%s is an absolute path" % path)
+            raise ValueError(f"{path} is an absolute path")
         filters = [(0, f) for f in self._global_filters]
         if os.path.sep != "/":
             path = path.replace(os.path.sep, "/")

+ 3 - 3
dulwich/index.py

@@ -306,7 +306,7 @@ def read_index(f: BinaryIO) -> Iterator[SerializedIndexEntry]:
     """Read an index file, yielding the individual entries."""
     header = f.read(4)
     if header != b"DIRC":
-        raise AssertionError("Invalid index file header: %r" % header)
+        raise AssertionError(f"Invalid index file header: {header!r}")
     (version, num_entries) = struct.unpack(b">LL", f.read(4 * 2))
     if version not in (1, 2, 3):
         raise UnsupportedIndexFormat(version)
@@ -329,7 +329,7 @@ def read_index_dict(f) -> Dict[bytes, Union[IndexEntry, ConflictedIndexEntry]]:
         else:
             existing = ret.setdefault(entry.name, ConflictedIndexEntry())
             if isinstance(existing, IndexEntry):
-                raise AssertionError("Non-conflicted entry for %r exists" % entry.name)
+                raise AssertionError(f"Non-conflicted entry for {entry.name!r} exists")
             if stage == Stage.MERGE_CONFLICT_ANCESTOR:
                 existing.ancestor = IndexEntry.from_serialized(entry)
             elif stage == Stage.MERGE_CONFLICT_THIS:
@@ -714,7 +714,7 @@ if sys.platform == "win32":
             super(PermissionError, self).__init__(
                 errno,
                 "Unable to create symlink; "
-                "do you have developer mode enabled? %s" % msg,
+                f"do you have developer mode enabled? {msg}",
                 filename,
             )
 

+ 1 - 1
dulwich/object_store.py

@@ -1416,7 +1416,7 @@ class ObjectStoreGraphWalker:
     def ack(self, sha):
         """Ack that a revision and its ancestors are present in the source."""
         if len(sha) != 40:
-            raise ValueError("unexpected sha %r received" % sha)
+            raise ValueError(f"unexpected sha {sha!r} received")
         ancestors = {sha}
 
         # stop if we run out of heads to remove

+ 19 - 19
dulwich/objects.py

@@ -110,13 +110,13 @@ def _decompress(string):
 def sha_to_hex(sha):
     """Takes a string and returns the hex of the sha within."""
     hexsha = binascii.hexlify(sha)
-    assert len(hexsha) == 40, "Incorrect length of sha1 string: %r" % hexsha
+    assert len(hexsha) == 40, f"Incorrect length of sha1 string: {hexsha!r}"
     return hexsha
 
 
 def hex_to_sha(hex):
     """Takes a hex sha and returns a binary sha."""
-    assert len(hex) == 40, "Incorrect length of hexsha: %s" % hex
+    assert len(hex) == 40, f"Incorrect length of hexsha: {hex}"
     try:
         return binascii.unhexlify(hex)
     except TypeError as exc:
@@ -153,7 +153,7 @@ def filename_to_hex(filename):
     """Takes an object filename and returns its corresponding hex sha."""
     # grab the last (up to) two path components
     names = filename.rsplit(os.path.sep, 2)[-2:]
-    errmsg = "Invalid object filename: %s" % filename
+    errmsg = f"Invalid object filename: {filename}"
     assert len(names) == 2, errmsg
     base, rest = names
     assert len(base) == 2 and len(rest) == 38, errmsg
@@ -242,7 +242,7 @@ def check_time(time_seconds):
     """
     # Prevent overflow error
     if time_seconds > MAX_TIME:
-        raise ObjectFormatException("Date field should not exceed %s" % MAX_TIME)
+        raise ObjectFormatException(f"Date field should not exceed {MAX_TIME}")
 
 
 def git_line(*items):
@@ -259,7 +259,7 @@ class FixedSha:
         if getattr(hexsha, "encode", None) is not None:
             hexsha = hexsha.encode("ascii")
         if not isinstance(hexsha, bytes):
-            raise TypeError("Expected bytes for hexsha, got %r" % hexsha)
+            raise TypeError(f"Expected bytes for hexsha, got {hexsha!r}")
         self._hexsha = hexsha
         self._sha = hex_to_sha(hexsha)
 
@@ -302,11 +302,11 @@ class ShaFile:
         try:
             int(size)  # sanity check
         except ValueError as exc:
-            raise ObjectFormatException("Object size not an integer: %s" % exc) from exc
+            raise ObjectFormatException(f"Object size not an integer: {exc}") from exc
         obj_class = object_class(type_name)
         if not obj_class:
             raise ObjectFormatException(
-                "Not a known type: %s" % type_name.decode("ascii")
+                "Not a known type: {}".format(type_name.decode("ascii"))
             )
         return obj_class()
 
@@ -368,7 +368,7 @@ class ShaFile:
     def set_raw_string(self, text: bytes, sha: Optional[ObjectID] = None) -> None:
         """Set the contents of this object from a serialized string."""
         if not isinstance(text, bytes):
-            raise TypeError("Expected bytes for text, got %r" % text)
+            raise TypeError(f"Expected bytes for text, got {text!r}")
         self.set_raw_chunks([text], sha)
 
     def set_raw_chunks(
@@ -845,7 +845,7 @@ class Tag(ShaFile):
                 assert isinstance(value, bytes)
                 obj_class = object_class(value)
                 if not obj_class:
-                    raise ObjectFormatException("Not a known type: %s" % value)
+                    raise ObjectFormatException(f"Not a known type: {value}")
                 self._object_class = obj_class
             elif field == _TAG_HEADER:
                 self._name = value
@@ -869,7 +869,7 @@ class Tag(ShaFile):
                         self._message = value[:sig_idx]
                         self._signature = value[sig_idx:]
             else:
-                raise ObjectFormatException("Unknown field %s" % field)
+                raise ObjectFormatException(f"Unknown field {field}")
 
     def _get_object(self):
         """Get the object pointed to by this tag.
@@ -956,7 +956,7 @@ class TreeEntry(namedtuple("TreeEntry", ["path", "mode", "sha"])):
     def in_path(self, path: bytes):
         """Return a copy of this entry with the given path prepended."""
         if not isinstance(self.path, bytes):
-            raise TypeError("Expected bytes for path, got %r" % path)
+            raise TypeError(f"Expected bytes for path, got {path!r}")
         return TreeEntry(posixpath.join(path, self.path), self.mode, self.sha)
 
 
@@ -976,11 +976,11 @@ def parse_tree(text, strict=False):
         mode_end = text.index(b" ", count)
         mode_text = text[count:mode_end]
         if strict and mode_text.startswith(b"0"):
-            raise ObjectFormatException("Invalid mode '%s'" % mode_text)
+            raise ObjectFormatException(f"Invalid mode '{mode_text}'")
         try:
             mode = int(mode_text, 8)
         except ValueError as exc:
-            raise ObjectFormatException("Invalid mode '%s'" % mode_text) from exc
+            raise ObjectFormatException(f"Invalid mode '{mode_text}'") from exc
         name_end = text.index(b"\0", mode_end)
         name = text[mode_end + 1 : name_end]
         count = name_end + 21
@@ -1000,7 +1000,7 @@ def serialize_tree(items):
     """
     for name, mode, hexsha in items:
         yield (
-            ("%04o" % mode).encode("ascii") + b" " + name + b"\0" + hex_to_sha(hexsha)
+            (f"{mode:04o}").encode("ascii") + b" " + name + b"\0" + hex_to_sha(hexsha)
         )
 
 
@@ -1023,7 +1023,7 @@ def sorted_tree_items(entries, name_order: bool):
         # Stricter type checks than normal to mirror checks in the C version.
         mode = int(mode)
         if not isinstance(hexsha, bytes):
-            raise TypeError("Expected bytes for SHA, got %r" % hexsha)
+            raise TypeError(f"Expected bytes for SHA, got {hexsha!r}")
         yield TreeEntry(name, mode, hexsha)
 
 
@@ -1180,21 +1180,21 @@ class Tree(ShaFile):
             stat.S_IFREG | 0o664,
         )
         for name, mode, sha in parse_tree(b"".join(self._chunked_text), True):
-            check_hexsha(sha, "invalid sha %s" % sha)
+            check_hexsha(sha, f"invalid sha {sha}")
             if b"/" in name or name in (b"", b".", b"..", b".git"):
                 raise ObjectFormatException(
-                    "invalid name %s" % name.decode("utf-8", "replace")
+                    "invalid name {}".format(name.decode("utf-8", "replace"))
                 )
 
             if mode not in allowed_modes:
-                raise ObjectFormatException("invalid mode %06o" % mode)
+                raise ObjectFormatException(f"invalid mode {mode:06o}")
 
             entry = (name, (mode, sha))
             if last:
                 if key_entry(last) > key_entry(entry):
                     raise ObjectFormatException("entries not sorted")
                 if name == last[0]:
-                    raise ObjectFormatException("duplicate entry %s" % name)
+                    raise ObjectFormatException(f"duplicate entry {name}")
             last = entry
 
     def _serialize(self):

+ 3 - 3
dulwich/pack.py

@@ -830,7 +830,7 @@ def read_pack_header(read) -> Tuple[int, int]:
     if not header:
         raise AssertionError("file too short to contain pack")
     if header[:4] != b"PACK":
-        raise AssertionError("Invalid pack header %r" % header)
+        raise AssertionError(f"Invalid pack header {header!r}")
     (version,) = unpack_from(b">L", header, 4)
     if version not in (2, 3):
         raise AssertionError("Version was %d" % version)
@@ -2343,7 +2343,7 @@ def apply_delta(src_buf, delta):
             raise ApplyDeltaError("Invalid opcode 0")
 
     if index != delta_length:
-        raise ApplyDeltaError("delta not empty: %r" % delta[index:])
+        raise ApplyDeltaError(f"delta not empty: {delta[index:]!r}")
 
     if dest_size != chunks_length(out):
         raise ApplyDeltaError("dest size incorrect")
@@ -2610,7 +2610,7 @@ class Pack:
             to determine whether or not a .keep file is obsolete.
         Returns: The path of the .keep file, as a string.
         """
-        keepfile_name = "%s.keep" % self._basename
+        keepfile_name = f"{self._basename}.keep"
         with GitFile(keepfile_name, "wb") as keepfile:
             if msg:
                 keepfile.write(msg)

+ 4 - 4
dulwich/patch.py

@@ -268,13 +268,13 @@ def gen_diff_header(paths, modes, shas):
     if old_mode != new_mode:
         if new_mode is not None:
             if old_mode is not None:
-                yield ("old file mode %o\n" % old_mode).encode("ascii")
-            yield ("new file mode %o\n" % new_mode).encode("ascii")
+                yield (f"old file mode {old_mode:o}\n").encode("ascii")
+            yield (f"new file mode {new_mode:o}\n").encode("ascii")
         else:
-            yield ("deleted file mode %o\n" % old_mode).encode("ascii")
+            yield (f"deleted file mode {old_mode:o}\n").encode("ascii")
     yield b"index " + shortid(old_sha) + b".." + shortid(new_sha)
     if new_mode is not None and old_mode is not None:
-        yield (" %o" % new_mode).encode("ascii")
+        yield (f" {new_mode:o}").encode("ascii")
     yield b"\n"
 
 

+ 7 - 7
dulwich/porcelain.py

@@ -386,7 +386,7 @@ def symbolic_ref(repo, ref_name, force=False):
     with open_repo_closing(repo) as repo_obj:
         ref_path = _make_branch_ref(ref_name)
         if not force and ref_path not in repo_obj.refs.keys():
-            raise Error("fatal: ref `%s` is not a ref" % ref_name)
+            raise Error(f"fatal: ref `{ref_name}` is not a ref")
         repo_obj.refs.set_symbolic_ref(b"HEAD", ref_path)
 
 
@@ -670,7 +670,7 @@ def remove(repo=".", paths=None, cached=False):
             try:
                 index_sha = index[tree_path].sha
             except KeyError as exc:
-                raise Error("%s did not match any files" % p) from exc
+                raise Error(f"{p} did not match any files") from exc
 
             if not cached:
                 try:
@@ -693,11 +693,11 @@ def remove(repo=".", paths=None, cached=False):
                         if blob.id != index_sha and index_sha != committed_sha:
                             raise Error(
                                 "file has staged content differing "
-                                "from both the file and head: %s" % p
+                                f"from both the file and head: {p}"
                             )
 
                         if index_sha != committed_sha:
-                            raise Error("file has staged changes: %s" % p)
+                            raise Error(f"file has staged changes: {p}")
                         os.remove(full_path)
             del index[tree_path]
         index.write()
@@ -1103,7 +1103,7 @@ def tag_delete(repo, name):
         elif isinstance(name, list):
             names = name
         else:
-            raise Error("Unexpected tag name type %r" % name)
+            raise Error(f"Unexpected tag name type {name!r}")
         for name in names:
             del r.refs[_make_tag_ref(name)]
 
@@ -1193,7 +1193,7 @@ def push(
                     try:
                         localsha = r.refs[lh]
                     except KeyError as exc:
-                        raise Error("No valid ref %s in local repository" % lh) from exc
+                        raise Error(f"No valid ref {lh} in local repository") from exc
                     if not force_ref and rh in refs:
                         check_diverged(r, refs[rh], localsha)
                     new_refs[rh] = localsha
@@ -1604,7 +1604,7 @@ def branch_create(repo, name, objectish=None, force=False):
             r.refs.set_if_equals(refname, None, object.id, message=ref_message)
         else:
             if not r.refs.add_if_new(refname, object.id, message=ref_message):
-                raise Error("Branch with name %s already exists." % name)
+                raise Error(f"Branch with name {name} already exists.")
 
 
 def branch_list(repo):

+ 7 - 7
dulwich/refs.py

@@ -590,11 +590,11 @@ class InfoRefsContainer(RefsContainer):
             if name.endswith(PEELED_TAG_SUFFIX):
                 name = name[:-3]
                 if not check_ref_format(name):
-                    raise ValueError("invalid ref name %r" % name)
+                    raise ValueError(f"invalid ref name {name!r}")
                 self._peeled[name] = sha
             else:
                 if not check_ref_format(name):
-                    raise ValueError("invalid ref name %r" % name)
+                    raise ValueError(f"invalid ref name {name!r}")
                 self._refs[name] = sha
 
     def allkeys(self):
@@ -1061,12 +1061,12 @@ def _split_ref_line(line):
     """Split a single ref line into a tuple of SHA1 and name."""
     fields = line.rstrip(b"\n\r").split(b" ")
     if len(fields) != 2:
-        raise PackedRefsException("invalid ref line %r" % line)
+        raise PackedRefsException(f"invalid ref line {line!r}")
     sha, name = fields
     if not valid_hexsha(sha):
-        raise PackedRefsException("Invalid hex sha %r" % sha)
+        raise PackedRefsException(f"Invalid hex sha {sha!r}")
     if not check_ref_format(name):
-        raise PackedRefsException("invalid ref name %r" % name)
+        raise PackedRefsException(f"invalid ref name {name!r}")
     return (sha, name)
 
 
@@ -1104,7 +1104,7 @@ def read_packed_refs_with_peeled(f):
             if not last:
                 raise PackedRefsException("unexpected peeled ref line")
             if not valid_hexsha(line[1:]):
-                raise PackedRefsException("Invalid hex sha %r" % line[1:])
+                raise PackedRefsException(f"Invalid hex sha {line[1:]!r}")
             sha, name = _split_ref_line(last)
             last = None
             yield (sha, name, line[1:])
@@ -1203,7 +1203,7 @@ def _set_default_branch(
         elif LOCAL_TAG_PREFIX + branch in refs:
             head_ref = LOCAL_TAG_PREFIX + branch
         else:
-            raise ValueError("%r is not a valid branch or tag" % os.fsencode(branch))
+            raise ValueError(f"{os.fsencode(branch)!r} is not a valid branch or tag")
     elif origin_head:
         head_ref = origin_head
         if origin_head.startswith(LOCAL_BRANCH_PREFIX):

+ 6 - 6
dulwich/repo.py

@@ -830,7 +830,7 @@ class BaseRepo:
         """
         if not isinstance(name, bytes):
             raise TypeError(
-                "'name' must be bytestring, not %.80s" % type(name).__name__
+                f"'name' must be bytestring, not {type(name).__name__:.80}"
             )
         if len(name) in (20, 40):
             try:
@@ -1078,7 +1078,7 @@ class BaseRepo:
         try:
             self.hooks["post-commit"].execute()
         except HookError as e:  # silent failure
-            warnings.warn("post-commit hook failed: %s" % e, UserWarning)
+            warnings.warn(f"post-commit hook failed: {e}", UserWarning)
         except KeyError:  # no hook defined, silent fallthrough
             pass
 
@@ -1406,8 +1406,8 @@ class Repo(BaseRepo):
                 fs_path = os.fsencode(fs_path)
             if os.path.isabs(fs_path):
                 raise ValueError(
-                    "path %r should be relative to "
-                    "repository root, not absolute" % fs_path
+                    f"path {fs_path!r} should be relative to "
+                    "repository root, not absolute"
                 )
             tree_path = _fs_to_tree_path(fs_path)
             full_path = os.path.join(root_path_bytes, fs_path)
@@ -1474,7 +1474,7 @@ class Repo(BaseRepo):
                     continue
                 except KeyError as exc:
                     raise KeyError(
-                        "file '%s' not in index" % (tree_path.decode())
+                        f"file '{tree_path.decode()}' not in index"
                     ) from exc
 
             st = None
@@ -1677,7 +1677,7 @@ class Repo(BaseRepo):
             return None
 
     def __repr__(self) -> str:
-        return "<Repo at %r>" % self.path
+        return f"<Repo at {self.path!r}>"
 
     def set_description(self, description):
         """Set the description for this repository.

+ 8 - 8
dulwich/server.py

@@ -260,12 +260,12 @@ class PackHandler(Handler):
                 continue
             if cap not in allowable_caps:
                 raise GitProtocolError(
-                    "Client asked for capability %r that " "was not advertised." % cap
+                    f"Client asked for capability {cap!r} that " "was not advertised."
                 )
         for cap in self.required_capabilities():
             if cap not in caps:
                 raise GitProtocolError(
-                    "Client does not support required " "capability %r." % cap
+                    "Client does not support required " f"capability {cap!r}."
                 )
         self._client_capabilities = set(caps)
         logger.info("Client capabilities: %s", caps)
@@ -273,7 +273,7 @@ class PackHandler(Handler):
     def has_capability(self, cap: bytes) -> bool:
         if self._client_capabilities is None:
             raise GitProtocolError(
-                "Server attempted to access capability %r " "before asking client" % cap
+                f"Server attempted to access capability {cap!r} " "before asking client"
             )
         return cap in self._client_capabilities
 
@@ -461,7 +461,7 @@ def _split_proto_line(line, allowed):
             return tuple(fields)
         elif command == COMMAND_DEEPEN:
             return command, int(fields[1])
-    raise GitProtocolError("Received invalid line from client: %r" % line)
+    raise GitProtocolError(f"Received invalid line from client: {line!r}")
 
 
 def _find_shallow(store: ObjectContainer, heads, depth):
@@ -648,7 +648,7 @@ class _ProtocolGraphWalker:
         want_revs = []
         while command == COMMAND_WANT:
             if sha not in values:
-                raise GitProtocolError("Client wants invalid object %s" % sha)
+                raise GitProtocolError(f"Client wants invalid object {sha}")
             want_revs.append(sha)
             command, sha = self.read_proto_line(allowed)
 
@@ -676,7 +676,7 @@ class _ProtocolGraphWalker:
 
     def ack(self, have_ref):
         if len(have_ref) != 40:
-            raise ValueError("invalid sha %r" % have_ref)
+            raise ValueError(f"invalid sha {have_ref!r}")
         return self._impl.ack(have_ref)
 
     def reset(self):
@@ -1119,7 +1119,7 @@ class UploadArchiveHandler(Handler):
         for pkt in self.proto.read_pkt_seq():
             (key, value) = pkt.split(b" ", 1)
             if key != b"argument":
-                raise GitProtocolError("unknown command %s" % key)
+                raise GitProtocolError(f"unknown command {key}")
             arguments.append(value.rstrip(b"\n"))
         prefix = b""
         format = "tar"
@@ -1166,7 +1166,7 @@ class TCPGitRequestHandler(socketserver.StreamRequestHandler):
 
         cls = self.handlers.get(command, None)
         if not callable(cls):
-            raise GitProtocolError("Invalid service %s" % command)
+            raise GitProtocolError(f"Invalid service {command}")
         h = cls(self.server.backend, args, proto)
         h.handle()
 

+ 1 - 1
dulwich/tests/utils.py

@@ -190,7 +190,7 @@ def ext_functest_builder(method, func):
 
     def do_test(self):
         if not isinstance(func, types.BuiltinFunctionType):
-            raise SkipTest("%s extension not found" % func)
+            raise SkipTest(f"{func} extension not found")
         method(self, func)
 
     return do_test

+ 1 - 1
dulwich/walk.py

@@ -275,7 +275,7 @@ class Walker:
         # Note: when adding arguments to this method, please also update
         # dulwich.repo.BaseRepo.get_walker
         if order not in ALL_ORDERS:
-            raise ValueError("Unknown walk order %s" % order)
+            raise ValueError(f"Unknown walk order {order}")
         self.store = store
         if isinstance(include, bytes):
             # TODO(jelmer): Really, this should require a single type.

+ 3 - 4
dulwich/web.py

@@ -217,7 +217,7 @@ def get_info_refs(req, backend, mat):
             yield req.forbidden("Unsupported service")
             return
         req.nocache()
-        write = req.respond(HTTP_OK, "application/x-%s-advertisement" % service)
+        write = req.respond(HTTP_OK, f"application/x-{service}-advertisement")
         proto = ReceivableProtocol(BytesIO().read, write)
         handler = handler_cls(
             backend,
@@ -308,7 +308,7 @@ def handle_service_request(req, backend, mat):
         yield req.not_found(str(e))
         return
     req.nocache()
-    write = req.respond(HTTP_OK, "application/x-%s-result" % service)
+    write = req.respond(HTTP_OK, f"application/x-{service}-result")
     if req.environ.get("HTTP_TRANSFER_ENCODING") == "chunked":
         read = ChunkReader(req.environ["wsgi.input"]).read
     else:
@@ -555,8 +555,7 @@ class WSGIServerLogger(WSGIServer):
     def handle_error(self, request, client_address):
         """Handle an error."""
         logger.exception(
-            "Exception happened during processing of request from %s"
-            % str(client_address)
+            f"Exception happened during processing of request from {client_address!s}"
         )
 
 

+ 1 - 1
examples/latest_change.py

@@ -18,7 +18,7 @@ w = r.get_walker(paths=[path], max_entries=1)
 try:
     c = next(iter(w)).commit
 except StopIteration:
-    print("No file %s anywhere in history." % sys.argv[1])
+    print(f"No file {sys.argv[1]} anywhere in history.")
 else:
     print(
         f"{sys.argv[1]} was last changed by {c.author} at {time.ctime(c.author_time)} (commit {c.id})"

+ 1 - 1
tests/__init__.py

@@ -85,7 +85,7 @@ class BlackboxTestCase(TestCase):
             if os.path.isfile(p):
                 return p
         else:
-            raise SkipTest("Unable to find binary %s" % name)
+            raise SkipTest(f"Unable to find binary {name}")
 
     def run_command(self, name, args):
         """Run a Dulwich command.

+ 5 - 5
tests/compat/test_client.py

@@ -356,7 +356,7 @@ class DulwichTCPClientTest(CompatTestCase, DulwichClientTestBase):
         DulwichClientTestBase.setUp(self)
         if check_for_daemon(limit=1):
             raise SkipTest(
-                "git-daemon was already running on port %s" % protocol.TCP_GIT_PORT
+                f"git-daemon was already running on port {protocol.TCP_GIT_PORT}"
             )
         fd, self.pidfile = tempfile.mkstemp(
             prefix="dulwich-test-git-client", suffix=".pid"
@@ -367,8 +367,8 @@ class DulwichTCPClientTest(CompatTestCase, DulwichClientTestBase):
             "daemon",
             "--verbose",
             "--export-all",
-            "--pid-file=%s" % self.pidfile,
-            "--base-path=%s" % self.gitroot,
+            f"--pid-file={self.pidfile}",
+            f"--base-path={self.gitroot}",
             "--enable=receive-pack",
             "--enable=upload-archive",
             "--listen=localhost",
@@ -584,8 +584,8 @@ class GitHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
             env.setdefault(k, "")
 
         self.wfile.write(b"HTTP/1.1 200 Script output follows\r\n")
-        self.wfile.write(("Server: %s\r\n" % self.server.server_name).encode("ascii"))
-        self.wfile.write(("Date: %s\r\n" % self.date_time_string()).encode("ascii"))
+        self.wfile.write((f"Server: {self.server.server_name}\r\n").encode("ascii"))
+        self.wfile.write((f"Date: {self.date_time_string()}\r\n").encode("ascii"))
 
         decoded_query = query.replace("+", " ")
 

+ 1 - 1
tests/compat/test_utils.py

@@ -85,4 +85,4 @@ class GitVersionTests(TestCase):
             self.assertRequireFails((1, 7, 1))
         except SkipTest as e:
             # This test is designed to catch all SkipTest exceptions.
-            self.fail("Test unexpectedly skipped: %s" % e)
+            self.fail(f"Test unexpectedly skipped: {e}")

+ 1 - 1
tests/contrib/test_swift.py

@@ -50,7 +50,7 @@ try:
 except ModuleNotFoundError:
     missing_libs.append("mock")
 
-skipmsg = "Required libraries are not installed (%r)" % missing_libs
+skipmsg = f"Required libraries are not installed ({missing_libs!r})"
 
 
 if not missing_libs:

+ 5 - 5
tests/contrib/test_swift_smoke.py

@@ -168,9 +168,9 @@ class SwiftRepoSmokeTest(unittest.TestCase):
         remote_shas = {}
         for branch in ("master", "mybranch", "pullr-108"):
             local_shas[branch] = local_repo.do_commit(
-                "Test commit %s" % branch,
+                f"Test commit {branch}",
                 "fbo@localhost",
-                ref="refs/heads/%s" % branch,
+                ref=f"refs/heads/{branch}",
             )
         swift.SwiftRepo.init_bare(self.scon, self.conf)
         tcp_client = client.TCPGitClient(self.server_address, port=self.port)
@@ -180,7 +180,7 @@ class SwiftRepoSmokeTest(unittest.TestCase):
         swift_repo = swift.SwiftRepo("fakerepo", self.conf)
         for branch in ("master", "mybranch", "pullr-108"):
             remote_shas[branch] = swift_repo.refs.read_loose_ref(
-                "refs/heads/%s" % branch
+                f"refs/heads/{branch}"
             )
         self.assertDictEqual(local_shas, remote_shas)
 
@@ -193,7 +193,7 @@ class SwiftRepoSmokeTest(unittest.TestCase):
         files = ("testfile", "testfile2", "dir/testfile3")
         i = 0
         for f in files:
-            open(os.path.join(self.temp_d, f), "w").write("DATA %s" % i)
+            open(os.path.join(self.temp_d, f), "w").write(f"DATA {i}")
             i += 1
         local_repo.stage(files)
         local_repo.do_commit("Test commit", "fbo@localhost", ref="refs/heads/master")
@@ -244,7 +244,7 @@ class SwiftRepoSmokeTest(unittest.TestCase):
         files = ("testfile11", "testfile22", "test/testfile33")
         i = 0
         for f in files:
-            open(os.path.join(self.temp_d, f), "w").write("DATA %s" % i)
+            open(os.path.join(self.temp_d, f), "w").write(f"DATA {i}")
             i += 1
         local_repo.stage(files)
         local_repo.do_commit("Test commit", "fbo@localhost", ref="refs/heads/master")

+ 1 - 1
tests/test_archive.py

@@ -99,5 +99,5 @@ class ArchiveTests(TestCase):
             self.assertEqual(
                 contents[0],
                 contents[1],
-                "Different file contents for format %r" % format,
+                f"Different file contents for format {format!r}",
             )

+ 2 - 2
tests/test_client.py

@@ -994,7 +994,7 @@ class HttpGitClientTests(TestCase):
         basic_auth = c.pool_manager.headers["authorization"]
         auth_string = "{}:{}".format("user", "passwd")
         b64_credentials = base64.b64encode(auth_string.encode("latin1"))
-        expected_basic_auth = "Basic %s" % b64_credentials.decode("latin1")
+        expected_basic_auth = "Basic {}".format(b64_credentials.decode("latin1"))
         self.assertEqual(basic_auth, expected_basic_auth)
 
     def test_init_username_set_no_password(self):
@@ -1048,7 +1048,7 @@ class HttpGitClientTests(TestCase):
         basic_auth = c.pool_manager.headers["authorization"]
         auth_string = f"{original_username}:{original_password}"
         b64_credentials = base64.b64encode(auth_string.encode("latin1"))
-        expected_basic_auth = "Basic %s" % b64_credentials.decode("latin1")
+        expected_basic_auth = "Basic {}".format(b64_credentials.decode("latin1"))
         self.assertEqual(basic_auth, expected_basic_auth)
 
     def test_url_redirect_location(self):

+ 2 - 2
tests/test_file.py

@@ -124,7 +124,7 @@ class GitFileTests(TestCase):
 
     def test_write(self):
         foo = self.path("foo")
-        foo_lock = "%s.lock" % foo
+        foo_lock = f"{foo}.lock"
 
         orig_f = open(foo, "rb")
         self.assertEqual(orig_f.read(), b"foo contents")
@@ -167,7 +167,7 @@ class GitFileTests(TestCase):
 
     def test_abort(self):
         foo = self.path("foo")
-        foo_lock = "%s.lock" % foo
+        foo_lock = f"{foo}.lock"
 
         orig_f = open(foo, "rb")
         self.assertEqual(orig_f.read(), b"foo contents")

+ 1 - 1
tests/test_index.py

@@ -540,7 +540,7 @@ class BuildIndexTests(TestCase):
             except OSError as e:
                 if e.errno == 92 and sys.platform == "darwin":
                     # Our filename isn't supported by the platform :(
-                    self.skipTest("can not write filename %r" % e.filename)
+                    self.skipTest(f"can not write filename {e.filename!r}")
                 else:
                     raise
             except UnicodeDecodeError:

+ 1 - 1
tests/test_object_store.py

@@ -208,7 +208,7 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
 
         # add temporary files to the loose store
         for i in range(256):
-            dirname = os.path.join(self.store_dir, "%02x" % i)
+            dirname = os.path.join(self.store_dir, f"{i:02x}")
             if not os.path.isdir(dirname):
                 os.makedirs(dirname)
             fd, n = tempfile.mkstemp(prefix="tmp_obj_", dir=dirname)

+ 5 - 5
tests/test_pack.py

@@ -86,17 +86,17 @@ class PackTests(TestCase):
     def get_pack_index(self, sha):
         """Returns a PackIndex from the datadir with the given sha."""
         return load_pack_index(
-            os.path.join(self.datadir, "pack-%s.idx" % sha.decode("ascii"))
+            os.path.join(self.datadir, "pack-{}.idx".format(sha.decode("ascii")))
         )
 
     def get_pack_data(self, sha):
         """Returns a PackData object from the datadir with the given sha."""
         return PackData(
-            os.path.join(self.datadir, "pack-%s.pack" % sha.decode("ascii"))
+            os.path.join(self.datadir, "pack-{}.pack".format(sha.decode("ascii")))
         )
 
     def get_pack(self, sha):
-        return Pack(os.path.join(self.datadir, "pack-%s" % sha.decode("ascii")))
+        return Pack(os.path.join(self.datadir, "pack-{}".format(sha.decode("ascii"))))
 
     def assertSucceeds(self, func, *args, **kwargs):
         try:
@@ -256,7 +256,7 @@ class TestPackData(PackTests):
         self.get_pack_data(pack1_sha).close()
 
     def test_from_file(self):
-        path = os.path.join(self.datadir, "pack-%s.pack" % pack1_sha.decode("ascii"))
+        path = os.path.join(self.datadir, "pack-{}.pack".format(pack1_sha.decode("ascii")))
         with open(path, "rb") as f:
             PackData.from_file(f, os.path.getsize(path))
 
@@ -1013,7 +1013,7 @@ class DeltaChainIteratorTests(TestCase):
         """Wrapper around store.get_raw that doesn't allow repeat lookups."""
         hex_sha = sha_to_hex(bin_sha)
         self.assertNotIn(
-            hex_sha, self.fetched, "Attempted to re-fetch object %s" % hex_sha
+            hex_sha, self.fetched, f"Attempted to re-fetch object {hex_sha}"
         )
         self.fetched.add(hex_sha)
         return self.store.get_raw(hex_sha)

+ 2 - 3
tests/test_patch.py

@@ -235,7 +235,7 @@ diff --git a/dulwich/tests/test_patch.py b/dulwich/tests/test_patch.py
  class DiffTests(TestCase):
 """
         text = (
-            """\
+            f"""\
 From dulwich-users-bounces+jelmer=samba.org@lists.launchpad.net \
 Mon Nov 29 00:58:18 2010
 Date: Sun, 28 Nov 2010 17:57:27 -0600
@@ -246,7 +246,7 @@ Content-Transfer-Encoding: 8bit
 
 Change-Id: I5e51313d4ae3a65c3f00c665002a7489121bb0d6
 
-%s
+{expected_diff}
 
 _______________________________________________
 Mailing list: https://launchpad.net/~dulwich-users
@@ -255,7 +255,6 @@ Unsubscribe : https://launchpad.net/~dulwich-users
 More help   : https://help.launchpad.net/ListHelp
 
 """
-            % expected_diff
         )
         c, diff, version = git_am_patch_split(BytesIO(text))
         self.assertEqual(expected_diff, diff)

+ 1 - 1
tests/test_porcelain.py

@@ -2001,7 +2001,7 @@ class SubmoduleTests(PorcelainTestCase):
 
     def test_add(self):
         porcelain.submodule_add(self.repo, "../bar.git", "bar")
-        with open("%s/.gitmodules" % self.repo.path) as f:
+        with open(f"{self.repo.path}/.gitmodules") as f:
             self.assertEqual(
                 """\
 [submodule "bar"]

+ 2 - 2
tests/test_repository.py

@@ -65,12 +65,12 @@ class CreateRepositoryTests(TestCase):
         barestr = b"bare = " + str(expect_bare).lower().encode("ascii")
         with repo.get_named_file("config") as f:
             config_text = f.read()
-            self.assertIn(barestr, config_text, "%r" % config_text)
+            self.assertIn(barestr, config_text, f"{config_text!r}")
         expect_filemode = sys.platform != "win32"
         barestr = b"filemode = " + str(expect_filemode).lower().encode("ascii")
         with repo.get_named_file("config") as f:
             config_text = f.read()
-            self.assertIn(barestr, config_text, "%r" % config_text)
+            self.assertIn(barestr, config_text, f"{config_text!r}")
 
         if isinstance(repo, Repo):
             expected_mode = "0o100644" if expect_filemode else "0o100666"

+ 1 - 1
tests/test_web.py

@@ -284,7 +284,7 @@ class DumbHandlersTestCase(WebTestCase):
     def test_get_info_packs(self):
         class TestPackData:
             def __init__(self, sha) -> None:
-                self.filename = "pack-%s.pack" % sha
+                self.filename = f"pack-{sha}.pack"
 
         class TestPack:
             def __init__(self, sha) -> None: